freeeの開発情報ポータルサイト

セッションキャッシュによる SPOF(単一障害点)問題の解消

こんにちは、サービス基盤の横塚です。 freee のサービス基盤は、各チームがアプリケーション開発に専念できる環境を作ることをミッションに掲げるチームです。開発者の生産性を高めるような共通コンポーネントを整備したり、freee 全体が堅牢なシステムとなるような取り組みを日々続けています。

今回のテーマは、「認証サーバーの単一障害点問題を克服する」です。

freee の認証基盤

freee の中心にはログインするためのID情報や認証情報の管理を一手に引き受ける gRPC サーバーがあります。 freee の全アプリケーションはこの認証サーバーにアクセスしないと始まらないので非常に重要なコンポーネントです。もし認証サーバーがダウンしていたら、外からやってきたリクエストがどのユーザーからのものなのかさっぱりわからなくなってしまいます。当然アプリケーションは動かなくなるでしょう。

freee の中心にある認証サーバー

freee は会計や人事労務、販売管理など多種多様なサービスを提供していますが、それらは全て認証サーバーの上に成り立っています。 つまり freee にとってこの認証サーバーは落ちることが許されない単一障害点(single point of failure, SPOF)ということになります。

余談ですがこの認証サーバーに関しては過去にブログ記事や YouTube チャンネルで取り上げられています。是非こちらも見てください!

developers.freee.co.jp


www.youtube.com

SPOF 対策

freee 全体を障害に強いシステムにするためには SPOF を克服する必要があります。 今回我々は認証サーバーの gRPC レスポンスを Redis にキャッシュすることによって、一時的に認証サーバーがダウンしていても認証を行えるような世界を目指しました。

具体的なフローは以下のようになります。

  1. まずキャッシュが置かれている Redis に問い合わせる
  2. キャッシュ Hit すればそれを使う、なければ認証サーバーに問い合わせ
  3. 認証サーバーはレスポンスを返すと同時に、Redis へと書き込む

キャッシュを用いた認証のフロー
キャッシュを用いた認証のフロー

こうすればキャッシュがあるときには認証サーバーへのアクセスが必要なくなります。 しかし、このキャッシュという戦略は、言うは易く行うは難しです。認証サーバーが返すレスポンスが変わるような操作が行われた場合は、キャッシュを無効化する必要があります。キャッシュの無効化が正しく行われなければ、ユーザー情報を編集したのに画面に反映されなかったり、最悪の場合、ログアウトしたのに実際にはログアウトできていない、といった現象が発生します。

工夫したポイント

dual check

セッションキャッシュにはリスクがあり、キャッシュの無効化が漏れなく正しく行われている保証がなければ、むしろサービス障害の原因になります。キャッシュの無効化が正しく行われていることをユニットテストや E2E テストで担保することは難しいです。一般的なユースケースに対してはテストできますが、ユーザー数が多いサービスでは思わぬエッジケースが潜んでいるものです。

この問題に対抗するべく、まずはキャッシュをおいた Redis と認証サーバーの両方に、アプリケーションからアクセスすることにしました。つまりキャッシュと、認証サーバーからの本物のレスポンスを比較するのです。キャッシュが正しく扱われていれば差分はないはずで、差分があるなら通知を飛ばして本物のレスポンスを採用すればアプリケーションが壊れることはなく安心安全です。このチェック機構は社内で 「dual check」 と呼ばれています。

似たような戦略は freee ではよく採用されています。以前に認証基盤を数年かけてリプレースしたときも新旧両方の認証サーバーにアクセスして結果を比較していました。

dual check のおかげで各アプリケーションのオーナーチームにもリスクが低いことを説明しやすくなり、トラブルなくキャッシュ機構を導入することができました。(dual check の差分の原因を探し出して修正するのはかなり大変でしたが)

Redis の RBAC 機能

キャッシュを書き込む Redis には user 認証と RBAC(Role Based Access Control) 機能を採用しました。

具体的には、アプリケーションの user と管理者向けの user を分けておき、RBAC 機能を使ってアプリケーションで使用するユーザーからは keys や scan といった一覧系のコマンドを実行できないように設定しました。これにはシステム内部に侵入された場合に、ログインセッションの窃取を難しくする意図があります。

キャッシュの無効化(invalidation)

認証サーバーのレスポンスが変化するような操作が行われた場合、関係するキャッシュを全て無効にする必要があります。具体的な操作としては、ログアウトや事業所のマスタ情報の更新などが挙げられます。freee のアプリケーションではユーザーが複数の事業所に所属できるので、ログインしている事業所を切り替えた時もキャッシュを無効にする必要があります。

キャッシュの無効化を考える上で問題になるのが、キャッシュが事業所情報を含んでいるという点です。一回の事業所情報変更で所属する全ユーザのキャッシュを無効化する必要があり、大規模な事業所だと数百、あるいはそれ以上のユーザーが所属しています。ログインセッションの数はユーザーの数くらいは存在する可能性があるので、事業所に所属している全ユーザーのセッションをルックアップして無効化処理を行うと処理時間が長くなってしまい許容できませんでした。

そこで我々はキャッシュの無効化が必要になった時に、直接キャッシュを無効化するのではなく、revision の更新という形で Redis に保存することにしました。この revision は単調増加することが保証されているアトミックなカウンターで、キャッシュ作成時点より値が大きくなっていたらそのキャッシュは無効になっていることを意味します。 キャッシュを取得した後に中身を見ると、ユーザーや事業所のIDがわかるため、ID に紐づく revision を取得して、キャッシュの中に埋め込まれた revision と比較すれば、そのキャッシュが無効化されているかどうかを判定できます。

キャッシュ無効化の流れ
キャッシュ無効化の流れ

いざ検証!

そんなこんなでセッションキャッシュ機能を実装・リリースしました。キャッシュを導入したことで認証サーバーへのリクエストは激減し、CPU 使用率などのメトリクスからも認証サーバーへの負荷が下がったことが読み取れました。 そして、今回の本題である認証サーバーのSPOF対策についても検証を行いました。

社内に用意されている検証環境を使って、実際に認証サーバーを一定時間ダウンさせてみます。認証サーバーを初めとする freee のサービス群は Kubernetes 上でホストされているので、認証サーバーの Pod 数を0にすることで、サービス障害の状態を簡単に再現できます。

認証サーバーがダウンしている状態だと freee のアプリケーションは認証のプロセスを完了できず正常に動作しないはずですが、先にも見たようにキャッシュが生きている限りは認証サーバーへのアクセスなしで認証のプロセスを完了できます。

実際の検証では、認証サーバーの Pod 数が0の状態で freee 会計の取引登録処理を実行し、成功することを確認しました!

freee 会計の取引登録画面
freee 会計の取引登録画面

今後の展望

認証サーバーなしで認証された API 呼び出しができることを証明しましたが、これだけではSPOF 対策としては不十分でした。認証サーバーにはセッション情報の取得以外にもアカウント管理周りの gRPC が存在し、かつ各アプリケーションの必須な共通処理で呼び出されているという状態になっています。今回の施策は、セッション情報を取得する gRPC のレスポンスをキャッシュするだけなので、その他の gRPC 呼び出しが共通処理に含まれていると認証サーバーが落ちた時に操作不可能になってしまい SPOF 対策になりません。

認証サーバーを SPOF にしないためには、無駄に認証サーバーの gRPC 呼び出しをしている箇所を減らしたり、エラーになっても後続の処理が行えるようにエラーハンドリングしていくことが必要です。つまり今回のセッションキャッシュ機構は SPOF 克服の第一歩に過ぎないということです。各アプリケーションと認証サーバーを疎結合にしていくことが求められます。

まとめ

今回は認証サーバーを単一障害点(SPOF)にしないための取り組みを紹介しました。アプリケーションの数が多くなってくると自然と切り出される認証周りの機能ですが、切り出すと、全てのアプリケーションが認証サーバーなしでは動かなくなってしまいます。

キャッシュによって耐障害性を高めるというのはシンプルなアプローチですが、決して簡単ではありません。実際に、全サービスにセッションキャッシュを導入するためには基盤を作るエンジニアだけでなく、各アプリケーションを詳しく知っているエンジニアの協力が不可欠でした。

そしてセッションキャッシュが本領を発揮するためには、認証サーバーへの不要な依存関係を断ち切ることが必要です。それが実現すれば、例え認証サーバーが落ちたとしても、freee は動き続け、ユーザーさんの業務には影響しません。サービスを安定して提供することも立派なユーザー価値であり、やりがいのある仕事なのでこれからもサービス基盤として努力を続けていきたいです。