コーポレートの課題をエンジニアが解決した話

医療・介護・ヘルスケア・シニアライフの4つの領域で高齢社会の情報インフラを構築している株式会社エス・エム・エスでサービス横断で技術的な課題を解決して回っている@okazu_dmです。 直近はSREとしての業務などがメインでしたが、本日は私が運用を担当していた全社横断の利用規約とプライバシーポリシー(以下、利用規約等とします)の管理&配信サービス(以降、社内のコードネームであるnomosと呼びます)について紹介します。

nomosとは

nomosは、上述したとおり利用規約等を管理、配信するサービスです。2019年の夏には一旦開発が完了した段階で私が引き継ぎました。引き継ぎから実際に活用されるまでは半年ほど間があり、その時間はシステムの一部を単純化、再実装するなどブラッシュアップに使っていました。大まかにどのような変更があったのかについては後のシステム構成の説明の中で適宜触れます。

nomosを作った背景

弊社では複数の事業領域に渡って約40のサービス展開しており、サービスの数がエンジニアに対して多く、したがって管理対象の利用規約も多くなっています。それらが各サービスのリソースとして分散されて管理されていた時期は以下のような問題がありました。

  • 法務担当から利用規約等の文言の更新を依頼したい場合、誰に依頼をすればいいかわからない

  • 一部同じ文言が入っている複数の利用規約など、一斉に利用規約を更新したい場合でも、直接の管理主体が分散しているため更新漏れが発生しやすい構造だった

こういった問題の対処として利用規約等を集約管理する、またパーツ化して共通部分を再利用できるようにする、などの機能を備えた管理システムと、編集された利用規約をユーザに対して表示する配信システムがnomosとして開発されました。

また、2020年の民法改正(ここでは詳細な内容は省きます)への対応のため、利用規約の変更実施前に変更後の利用規約等を一定期間ユーザに対して表示する機能があります。

nomosのシステム構成

nomosは上述したように大きく管理システムと配信システムに分かれます。 それを踏まえて以下の図をご覧ください。

f:id:bm-sms-product-dev-saiyo-pr:20211201155309p:plain
nomosのシステム構成(現システムに合わせた最新版)

管理システム

まず管理システムから見ていきますが、こちらはGoogle App Engine(GAE)のNode.jsランタイム上でNuxtのアプリケーションをWebUIとして運用しています。 認証部分はFirebase Authentication、利用規約等のストレージとしてFirestoreを利用しています。

また、nomosでは利用規約をパーツに分割して管理、そして各サービスの利用規約はパーツを組み合わせて構成されているため、これらのパーツが更新されたときに関連するサービスの利用規約を全て更新する必要があります。 そういった管理画面の操作によって発生するある程度複雑な処理についてはCloud Functions上に実装されており、Firestore上のデータが更新された際にCloud Functions上の更新処理が呼び出されるようにトリガー機能*1を設定しています(参考: https://cloud.google.com/functions/docs/calling/cloud-firestore)。

当初はCloud Functions内でCloud Pub/Subを利用して更に多重でCloud Functionsを呼び出す仕組みなどもあったのですが、単純な呼び出しコストやシステム自体の複雑さ、数年スパンで見たデータ量などの観点からも並列化のメリットがあまりなかったため設計を単純化しています。

また、早すぎる抽象化がなされていたり全体的に規模に対して各所のコードの複雑すぎた部分は引き継ぎ後に整理して単純なものに置き換えました。

以下が管理画面の一部ですが、利用規約やプライバシーポリシーの文言の編集や編集履歴の参照、ユーザ管理などが全て管理機能に含まれています。

f:id:bm-sms-product-dev-saiyo-pr:20211201155804p:plain

配信システム

もう一方の配信システムですが、こちらは現在はGAEのRubyランタイム(引き継ぎ当初はGoで実装されていました)上で動作しているSinatraのアプリケーションがFastly経由でユーザに対して利用規約を配信しています。

基本的にはFirestore上の利用規約等を配信するだけなのですが、利用規約が更新されたタイミングでは以下の画像のように、更新後の利用規約と更新前(現行)の利用規約を同時に配信するような仕組みが実装されています。

f:id:bm-sms-product-dev-saiyo-pr:20211201155929p:plain

その他の要素ですが、一部の静的なコンテンツについてCloud Storageを利用、監視についてはMackerelの外形監視のみとしています。

運用していく中で見つかった課題とその対処

また、開発当時に考慮されていなかった(または優先順位が低いと考えられていた)問題の中で運用していく中で対処が必要だったものとしては以下のようなものがありました。

1. サービスへの導入の際は移行期間の設定をオフにしたい需要があった

nomosでは、編集作業を行うと利用規約の移行期間に入り新旧双方の表示が行われるようになります。

そのため、サービスの新規リリース時などで細かい調整が直前まで入るケースであっても新旧双方の利用規約が表示されている、という状態になってしまうという問題があり、エンドユーザに対して混乱を与える懸念がありました。

これに対してはサービスごとに移行期間の設定を変えるなどのアイデアもありましたが、対応コストの観点から最低限の対応しかできない状態だったため、以下の画像のように強制的に最新版への移行をするボタンを管理画面につけることで対応しました。このボタンを押すことで、新旧双方の表示を行っていた状態のサービスは強制的に旧利用規約を撤廃し新利用規約のみの表示となる、という仕様です。

f:id:bm-sms-product-dev-saiyo-pr:20211201160209p:plain

2. 検索順位への影響についての意見が上がった

当初、nomosは https://policy.bm-sms.co.jp のドメインに対してサービスからリンクを貼ってもらう想定で開発されていました。しかし、実際にサービスに導入する中でSEO戦略への影響についての声が上がり、機能の開発の検討なども含めた議論が発生しました。結論としては機能を追加することはなく、ドメインを統一したい場合はサービス側でリバースプロキシを立てるだけの対応に留める方針となりました。

これについてはシステム的に問題があったという話ではないのですが、開発時のドキュメントを参照する限りプロジェクト発足時点でサービス運用者からの意見を吸い上げられていなかった仕様策定プロセス上の問題という側面はあるものと見ています。なお、現状nomos導入の前後で大きく順位が下がったという現象は観測されておりません。

3. コストが想定より高くなっていた

私が前任者(開発者)から引き継いだ後に1ヶ月ほど運用して、コストが想定より高くなっていることに気づきました。

そこでGAEのインスタンス数を確認したところ通常ユーザアクセスが発生するとは考えにくい時間帯でも常にインスタンスが立ち上がり続けていることが分かり、原因を調べた結果Fastlyのヘルスチェックに対してレスポンスを返すためにインスタンスが立ち上がり続けていることが判明しました。

多少間隔を長くしても全てのPOPが独立してヘルスチェックをする都合上、オリジンサーバへのリクエスト間隔自体を正確にコントロールすることは難しいことと、実質的にはMackerelによる外形監視で事足りることを踏まえてFastlyのヘルスチェックを使うことをやめました。

その結果、コストを当初の半分以下に抑制することができ、おおよそ想定の範囲に収まりました。

おわりに

私自身は現在は特定の事業のためのシステム開発をメインで行っていますが、このような社内ツールにエンジニアとして関わることもあります。こういった社内ツールはサービスと直接関係があるわけではないため、設計や運用で普段とは違った面白さや難しさがあります。また、引き継いだものをベースに更に低コストで手離れの良いシステムを考えるというのは良い経験になりました。