AKARI Tech Blog

燈株式会社のエンジニア・開発メンバーによる技術ブログです

接続は切れるという前提で設計するKubernetes × WebSocket × Redis で支える“安定した通知基盤”──その設計思想と見えてきた課題や今後の展望

皆さんこんにちは!

今週のAKARI Tech Blogは、AI SaaS 事業本部 Dev の大澤が担当します。

はじめに

私たちが提供している発注書サービスでは、PDF の生成、電子署名の付与、S3 へのアップロードといった一連の処理が、外部 APIAWS電子署名サービスなど)との接続を伴うため、処理時間が長くなりがちです。

従来はこれらの処理をユーザーが画面上で送信ボタンを押した後に実行しており、特にファイルのアップロード(S3 への送信)や電子契約の処理などで待ち時間が発生し、最大で 1 分以上もの時間がかかるケースもありました。

この“画面上での待機”は、操作がブロックされてしまうだけでなく、ネットワークの不安定さやタイムアウトのリスク、ユーザー体験の低下など多くの課題を含んでいました。

そこで私たちは、時間のかかる処理をバックグラウンド化し、その進行状況をリアルタイムに通知する構成への刷新に取り組みました。

本記事では、その刷新にあたっての技術的課題や構成の選定理由、実際に採用したアーキテクチャ、さらにその運用を通して見えてきた今後の改善ポイントについて、課題→対策→実装の三段階で整理して紹介し、その結果と今後の展望について紹介させていただきます。

課題

本サービスは Kubernetes 上で稼働しており、Pod の自動再起動やスケールアウトによって構成が動的に変化する設計となっています。その結果、WebSocket のように「接続の継続」が求められる通信では特有の課題が発生します。

たとえば、ユーザーが WebSocket 接続を開始するたびに、Kubernetes のサービス構成上、どの Pod にルーティングされるかはランダムであるため、途中で接続が切れた際に再接続された Pod が異なれば、それまでの進捗状態が失われてしまいます。

また、ユーザーの通信環境が不安定な場合、WebSocket 接続が断続的に切れることもあります。このような前提下では、フロントエンド側で状態を保持する設計では限界があり、安定した通知体験を実現するには「接続は必ず切れるもの」として設計し、すべての状態をサーバーサイドで管理する構成が求められました。

図では、どの Pod にルーティングされるかはランダムで決定されます。たとえば、WebSocket 接続は pod1 に確立されたものの、その後の発注処理は pod1〜3 のいずれかにランダムにルーティングされてしまう可能性があります

考えられる対策

このような課題に対して、いくつかの技術的な対策を検討しました。

まず、WebSocket 再接続時に進捗情報を復元できるよう、通知の状態をフロントではなくバックエンドで一元管理する対策が考えられます。これにより、ユーザーのネットワーク切断やブラウザ操作によって WebSocket 接続が切れたとしても、再接続後に進捗状態を継続できるようになります。

また、KubernetesのServiceによる抽象化とPodレベルのロードバランシングにより、Sticky Session を導入しても、必ずしも同一 Pod にルーティングされるとは限らず、接続が固定されないという問題がありました。そのため、Pod の状態に依存せず状態情報を管理・共有できる仕組みとして、インメモリデータベースである Redis を導入するという選択肢が浮上しました。Redis が、高速な読み書きと、ネットワーク越しでも安定してアクセスできる特性を持ち、再接続時の状態取得や複数 Pod 間での情報共有に適していたためです。

採用した実装方法

これらを踏まえ、私たちは、通知の安定性と再接続時の復元性を両立するため、Redis を活用しバックエンドを中心に据えた構成で通知基盤を実装しました。
WebSocket 経由で通知を行う際には、各処理の進捗状況(例:アップロード中、署名処理中など)をすべて Redis 上で一元的に管理するよう設計しています。これは、フロント側で進行状況を保持してバックエンドに逐次送信するのではなく、進行状態自体を常にバックエンドが管理・制御する方式を採用したためです。これにより、接続断やブラウザの画面更新があっても、再接続時に直ちに正確な状態を復元できるようになっています。

Redis は、複数の Pod 間で進捗状態を共有し、再接続時の状態復旧を実現する共通インフラとして機能しています。高速な読み書き性能とネットワーク越しの安定性により、たとえクライアント側の接続が一時的に不安定になっても、バックエンド側で進捗情報を確実に保持し続け、通知の信頼性を担保します。

また、Redis の Pub/Sub 機能を活用し、段階的な進捗(例:1/3、2/3、完了)をリアルタイムで配信。フロントエンド側は、通知の受信と表示に専念するシンプルな構成とし、実装負担を最小限に抑えながら高いユーザー体験(UX)を実現しています。

このような構成により、Pod の入れ替えやスケールアウトといった Kubernetes 環境特有の動的な変化にも柔軟に対応でき、継続的かつ安定した通知体験を提供できるようになりました。

この構成によって実現した通知体験

WebSocket 経由でバックエンドからリアルタイムに進捗通知を届けることで、ユーザーは「今どの処理段階なのか」を即座に確認できるようになりました。これにより、何も起こらない画面を見つめ続けたり、完了を待つ間に操作を止めたりする必要がなくなり、処理中も他の作業を進められるようになりました。

Redis Pub/Sub を活用し、段階的な進行メッセージ(例:1/3、2/3、完了)を数秒ごとに WebSocket でクライアントに送信することで、処理が着実に進んでいることが視覚的に伝わり、フィードバックの質が向上しました。

また、WebSocket 接続が切断された場合でも、処理自体はバックエンドで継続される構成となっているため、ユーザーが画面を閉じても処理は止まりません。再訪時には自動的に再接続され、最新の進捗が復元されます。処理完了時にはトースト通知で即時に知らせることで、シームレスな体験を実現しました。

今後の展望

今回構築した通知基盤は、現時点では発注書送信処理のみに限定して活用されていますが、今後はより広範な用途への展開を見込んでいます。社内には、「時間がかかる処理なのに、完了までの見通しが立たない」といったタスクが他にも多く存在しています。こうした課題に対して、今回設計した Redis Pub/Sub + WebSocket を活用した通知基盤をテンプレート化し、重たい処理全般に共通して適用可能な仕組みとして整備していくことで、UX の改善を社内全体に広げていく方針です。

また、非同期処理をより柔軟かつ安定的に制御するため、AWS SQS と NestJS + BullMQ を組み合わせたジョブキューの導入も検討しています。ジョブキューを導入することで、処理の順序を厳密に制御したり、処理実行のタイミングを遅延させたり、失敗時の自動リトライといった高度な制御が可能になります。さらに、こうした制御はコードの変更なしに設定として柔軟に変更できるため、運用負荷の軽減や保守性の向上にもつながると考えています。

まとめ

発注処理に伴う重たいバックエンド処理を、ユーザー操作から切り離して非同期に実行し、進捗をリアルタイムに通知する構成を Redis・WebSocket・Kubernetes を組み合わせて構築しました。

「接続は必ず切れる」という前提に立ち、通知状態を Redis に集約して管理することで、接続断や Pod の再起動といったイベントが発生しても安定した通知体験を維持できる仕組みを実現しました。

本構成により、ユーザーは画面を閉じたり別操作をしながらでも進捗を把握できるようになり、操作の自由度と信頼性が大きく向上しました。

今後はこの通知基盤の社内展開や、ジョブキューによるさらなる安定性・柔軟性の向上に向けた改善を進めていきます。

We’re Hiring!

燈では、フルスタックで開発するソフトウェアエンジニアを募集しております!

興味がある方は、ぜひカジュアル面談でお話しましょう!🔥
akariinc.co.jp