はじめに
プラットフォーム開発本部 第一開発部 認証グループの田松です。
本記事では、弊社プロダクトで長年利用されてきた年齢認証システムのクラウド移行プロジェクトについてご紹介します。 移行にあたっては、レガシーな技術基盤からの脱却と、安全な移行を両立するため、以下のようなアプローチをとりました。
- 旧システムの課題と挙動を精査し、ドキュメント化
- モダンなアーキテクチャの技術選定と構築
- 本番トラフィックを活用した段階的な移行戦略の実施
旧年齢認証システムは、PHP + Zend Frameworkの構成で実装され、10年以上にわたりオンプレミス環境で稼働してきました。ユーザーが年齢確認が必要なサービスを利用する際は、一部を除き必ずこのシステムの認証を通過する必要があります。そのため、年齢認証の障害は、それらのサービスの停止につながってしまいます。 また年齢認証はピーク時にはリクエストが2,500rpsを超えることもあり、可用性やスケーラビリティの確保が大きな課題となっていました。
旧システムの課題整理から始まり、技術選定、リリースまでの取り組みをご紹介します。 レガシーシステムのリプレイスやクラウド移行に取り組む方の参考になれば幸いです。
旧システムの課題
10年以上運用されてきた年齢認証システムは、以下のような課題があり、維持困難な状態に陥りつつありました。
- PHP + Zend Frameworkによる実装
- Zend Frameworkはすでに公式サポートが終了しているフレームワークでもあり、保守可能な人材の確保が困難に。
- オンプレミスサーバでの運用のため、スケーラビリティが低い
- 突発的なトラフィック変動への対応が難しく、安定性や可用性にリスクを抱えていた。
- 属人的な運用とドキュメントの欠如
- 長年の運用の中で、仕様や運用手順を体系的にまとめたドキュメントの整備が不十分で、コードの意図や実際の挙動を把握するためには、システムに詳しい一部のメンバーの知識に頼らざるを得ない状態に。
システム理解に向けた観測と整理
旧システムの理解を目的に、まずは現状の挙動を可視化・分析し、それをベースに仕様を構築する取り組みを行いました。
Datadog による運用状況の可視化
年齢認証が稼働していた、オンプレミスサーバにDatadogを導入し、以下のようなメトリクスを収集しました。
- 時間帯ごとのリクエスト数
- 実際にリクエストされているパラメータの種類
- エラー発生率やステータスコードの分布
- サーバリソース(CPU・メモリ等)の負荷状況
これにより、サーバにどのくらい負荷がかかっているのか、どのタイミングで負荷が集中するかといった情報を明確に把握できました。
コードの精査と処理フローの再構築
並行して、コードを1ステップずつ精査し、リクエストからレスポンスまでの処理フローを洗い出しました。 書かれたのが10年以上前のコードだったため、現行サービスに必要な処理かを確認しながら、不要な処理や複雑な分岐を整理しました。
たとえば特定のパラメータに応じた分岐処理のように、条件付きで実行されるロジックについては、実際にそのパラメータを含むリクエストが現在も発生しているかをアクセスログから確認し、処理の必要性を一つひとつ見極めていきました。
ドキュメントの整備
これらの調査結果を元に、以下のような資料を整備しました。
- リクエストからレスポンスまでの処理フロー図
- リクエストパラメータごとの挙動を整理・記述したドキュメント
- 期待される仕様や動作を再定義・明文化したドキュメント
また移行方針を明文化したPRD(Product Requirements Document)や、アーキテクチャ設計の指針となるDesign Docを作成し、以降の開発・レビュー・運用の共通認識の土台としました。
技術選定
主な構成要素
- フレームワーク:Next.js,Express
- インフラ:Google Kubernetes Engine[1],Amazon CloudFront
- 監視基盤:Datadog
- CI/CD:GitHub Actions + Argo CD
Next.js、SSR(サーバーサイドレンダリング)の採用
フレームワークには、Next.jsを選定しました。 主な理由は以下です。
- バックエンドとフロントエンドのロジックを一体的に管理できるため、保守性が高い
- チーム内にNext.jsの知見がすでに、豊富に蓄積されていたため、属人化せず運用できる体制がある
また、年齢認証ページでは、アクセスごとに異なるリダイレクト先や言語、アクセス元の国判定など、リクエストごとに異なる処理を行う必要があり、都度の動的描画が求められます。この要件を満たすため、getServerSidePropsによるSSR(サーバーサイドレンダリング)を採用しています。 当初はSSG(静的サイト生成)によるパフォーマンス最適化も検討しましたが、パラメータによって表示内容やリダイレクト先が変わる構造であるため、あらかじめ静的に用意するのが困難と判断し、SSRを選択しました。
Expressによるカスタムサーバ構成
本システムでは、Next.jsが提供するビルトインサーバではなく、Expressを用いたカスタムサーバ構成を採用しています。これは、すべてのルートに共通する処理を、Expressのミドルウェアとして一元的に管理することを目的としています。
具体的には、アクセスログの作成、ヘッダーの整形、ルーティング制御などの処理をExpressのミドルウェアに集約することで、ルート間で共通に実行されるべき処理の抜け漏れを防ぎ、システム全体の構成を明確かつ保守しやすいものにしています。
CloudFrontによるアセットのキャッシュ
Next.jsでSSRを行う一方で、ハイドレーション用のJavaScriptや画像などの静的アセットはCloudFrontでキャッシュする構成としました。 これにより、以下の効果を得ています。
- 静的ファイルの配信速度向上
- オリジンサーバの負荷軽減
動的コンテンツと静的リソースの配信の分割により、高パフォーマンスかつ負荷変動にも強い構成を実現しています。
リリース戦略:CloudFront + Lambda@Edge による移行
今回のリリースでは、以下の2点を重視して構成を設計しました。
- 年齢認証で障害が発生すると、本システムを利用しているサービス全体の停止につながる可能性があるため、不具合発生時の影響を最小限に抑えること
- すでに多くのサービスに年齢認証のURLが記述されているため、既存のドメインやパス構成といったインターフェイスを変更しないこと
そのため、同じドメインを引き続き使用し、CloudFrontのディストリビューションも旧システムのものを継続利用する構成としました。CloudFrontのオリジン先のみを旧環境から新環境へ段階的に切り替えることで、同一ディストリビューション上でもカナリアリリースが可能な構成を採用しています。
この段階的移行を実現するために、用途の異なる2種類の Lambda@Edgeを活用しました。
1つ目:オリジンリクエストフェーズでの「オリジンの制御」
CloudFrontのオリジンリクエストフェーズにLambda@Edgeをアタッチし、リクエストごとに一定の確率で新環境へ振り分けるロジックを実装しました。
このLambda@Edgeは、以下のように確率を使って制御する処理をJavaScriptで実装しました。
// 新環境へ流す割合を、確率で制御 if (Math.random() < 0.1) { req.origin.custom.domainName = 'cloudDomainName'; }
このLambda@Edgeにより、リクエストの特定の割合を新環境にルーティングする制御が可能となりました。実際の移行では以下の順にトラフィックを段階的に増加させていきました。
1% → 10% → 30% → 50% → 100%
主なメリット:
- 本番環境の実トラフィックを使いながら挙動を確認できるため、問題の早期発見と対応が可能
- 万が一のトラブル時も、Lambda@Edgeを外すだけで迅速に切り戻しが可能
2つ目:オリジンレスポンスフェーズでの「オリジン固定制御」
年齢認証のフローでは、以下の2種類のリクエストが存在します。
- 年齢認証画面のSSRによる初回アクセス
- 「はい/いいえ」の選択に応じた認証結果送信リクエスト
これらのリクエストをカナリアリリース期間中に旧環境と新環境の両方で処理すると、動作に不整合が生じる恐れがあり、障害発生時の切り分けも難しくなります。
この課題を解消するため、初回リクエスト時に確率を用いて振り分けられたオリジンの情報をCookieに保持し、それ以降のリクエストではそのCookieを参照することで、同じオリジンにルーティングされるようにしました。
具体的には、HTTPレスポンスにSet-Cookieヘッダーを追加する処理を実装したLambda@Edgeを作成し、CloudFrontのオリジンレスポンスフェーズにアタッチしました。
主なメリット:
- オリジン間の不整合を防止
- デバッグや障害調査のトレーサビリティ向上
この2種類のLambda@Edgeを組み合わせることで、段階的に移行しつつ、各リクエストの整合性も確保できました。
まとめと展望
長年稼働していた年齢認証システムを、モダンな技術スタックへ移行しました。 これにより、保守のしやすさ・機能追加の柔軟さ・システムの可視性といった、長年の課題を解消し、今後のサービス展開にスムーズに対応できるようになりました。
長く運用されてきたシステムには、積み重なった技術的負債があることも少なくありません。 そうした課題に向き合うことで得られる経験は大きく、設計や運用の力を高めるとともに、エンジニアとしての成長にもつながります。
DMMでは、こうした難しさと面白さの両方が詰まった開発に一緒に取り組んでくれる仲間を募集しています。
興味があれば、ぜひ募集ページをご覧ください!
補足
- [1]Google Kubernetes Engine上に構築されたクラスタから成る共通アプリケーション基盤「マイクロサービスプラットフォーム」上に構築しています。 このプラットフォームは、弊社のマイクロサービスアーキテクトグループが、社内のプラットフォームサービス向けに提供・運用しています。