【インターンシップ】DBの疎結合化に向けて注文情報のINSERTタイミングを適正化した話

こんにちは。出前館で2025年10月から11月にサーバーサイドエンジニアとしてインターンをしている木元です。

出前館では現在、創業以来20年以上にわたって積み上げられてきた巨大なシステムを刷新する全社的なリアーキテクチャプロジェクトが進行しています。その第一歩として、複雑に絡み合ったデータベースの依存関係を解消するDB疎結合を推進しています。

今回のインターンで私は、オーダーサービスにおいて、DB疎結合化を阻んでいた注文データ作成フローの正常化というタスクを担当しました。 20年の歴史を持つシステムにおいて「データの整合性を保ちながらフローを変える」という、非常に影響範囲が大きいタスクでした。 本記事では、かつて存在したレガシーなサービスが残した制約と、それを乗り越えて実現したINSERTタイミングの適正化についてご紹介します。

 

そもそも「オーダーサービス」とは?

私が配属されたのは、出前館のマイクロサービスの一つであるオーダーサービスチームです。これはその名の通り、ユーザーが注文を行う際に呼び出されるサービスで、出前館ビジネスロジックの中でも特にトラフィックが集中する部分です。

この図のように、BtoCサービスである出前館は、食事時になると高トラフィックが発生します。

20年の歴史が生んだ「カオステーブル」

まず、今回のプロジェクトが始まる前の状態をご覧ください。出前館のシステムは長年の運用の過程で、複数のサービスが巨大な共有DBを直接参照し合う、典型的な密結合状態になっていました。

Before:従来の密結合な状態

この図のように、複数のサービス(Service-A, B, C)が、中央にある共有DB内の、いわゆる「カオステーブル」を直接読み書きしています。本来は他サービスの責務であるはずのデータを直接参照してしまっているため、テーブル構造の変更が全サービスに影響を及ぼすという、変更に非常に弱い状態でした。

目指す「疎結合

今回のプロジェクトの目的は、この状態を脱し、各サービスが自分たちのデータに責任を持つ疎結合アーキテクチャへ移行することです。 具体的には、オーダーサービスが必要とする店舗や商品などの情報を、自サービス内でスナップショットとして保持する形を目指しました。

After:目指す疎結合な状態

この図のように、各サービス(Service-A, B, C)がそれぞれのテーブル(Service-A Tableなど)を持ち、必要なデータを自分たちの管理下に置くことで、他サービスへの直接参照(共有DBへの依存)を断ち切ることができます。

レガシーサービスの制約

理想の形は描けていましたが、これを実現するためには、長年解消したくてもできない構造的な課題を乗り越える必要がありました。 それは、データ作成(INSERT)タイミングの不整合です。

出前館のDBには、歴史的経緯により以下の2種類のスキーマが混在しています。

スナップショットを正しく作成するためには、注文登録の瞬間に両方のスキーマへ同時にデータが書き込まれる必要があります。しかし、最近まで稼働していたレガシーサービスの仕様により、それが不可能な状態でした。

「注文登録時、オーダーサービスを介さずに、直接共通スキーマにのみデータをINSERTしてしまう」

この仕様が存在する限り、オーダーサービス側では以下のような変則的な実装を続けざるを得ませんでした。

  1. 注文登録フェーズ: 共通スキーマのみデータ作成(レガシーサービスの制約)

  2. 注文連携フェーズ: 遅れてオーダーサービスのスキーマへのデータ作成とステータス更新

このようにデータが作成されるタイミングがバラバラであることは以前から課題として認識されていましたが、レガシーサービスが稼働している限り着手できず、これがスナップショットを実現するための大きなブロッカーとなっていました。

INSERTタイミングの統一

しかし、直近でそのレガシーサービスの提供が終了しました。これにより、長年システムを縛っていた最大の制約が解消されました。 そこで、スナップショット導入の前提条件として、分散していたINSERT処理を本来あるべきタイミングである注文登録フェーズに集約することを決定しました。

修正後のフロー

  • 注文登録フェーズ:

    • 共通スキーマへ INSERT

    • オーダーサービススキーマへ INSERT (ここを追加!)

    • 同時にスナップショットも保存可能に

  • 注文連携フェーズ:

    • ステータスの更新や外部連携のみを行う

これにより、注文が確定した瞬間に完全なデータが揃うようになり、Afterの図で示したような、自サービス内でのスナップショットが可能になりました。

テストと実装

方針は固まりましたが、注文処理というサービスの根幹機能を扱う以上、いきなりINSERTのタイミングを丸ごと切り替えることには高いリスクが伴います。 そこでチームの方針に基づき、移行プロセスを2段階に分けるという安全策が取られました。

  • 第1段階(検証フェーズ)

    • 本来のテーブルへの書き込みフローは変えず、注文登録のタイミングで検証用の「Temporary Table(一時テーブル)」に対してINSERTを行う処理を追加する。

    • 本番環境相当の環境でテストを行い、INSERT増加によるトランザクション負荷を計測する。

    • これを本番環境で稼働させ、データ不整合やエラーが起きないかを確認する。

  • 第2段階(移行フェーズ)

    • 検証完了後、実際に本番テーブルへのINSERTタイミングを切り替える。

私がインターンとして参加した時点では、メンターによって既に第1段階の実装が完了していました。そこで私は、「第1段階のテストと負荷試験」を行い、その安全性が確認できた後に「第2段階の実装」を行うという役割を担当しました。

1. 既存コードの理解と「テスト」による検証

まず取り組んだのは、メンターが実装した第1段階のコードと、20年以上の歴史を持つ大規模かつ複雑な既存コードベースの理解です。 マイクロサービスの中で、データがどこで生まれ、どう流れるのか。その依存関係をコードから丁寧に読み解くことから始めました。

注文登録時に一時テーブルへもデータをINSERTし、連携処理で本番データとの差分があればWarningログを出力する実装がなされていたため、この仕組みを利用してデータ整合性のテストを行いました。

  • データ整合性の検証: 本番環境相当の環境でも一時テーブルへの書き込みを行い、「期待通りのデータがINSERTされているか」「欠損が発生していないか」を詳細に検証しました。

  • カオスモンキーによる障害試験: 意図的にエラーを発生させる「カオスモンキー」を使用し、一時テーブルへの比較処理でエラーを出し、システムがそれを適切にハンドリング出来るかを確認しました。

2. AWS DLTによる負荷試験(ピーク時の3倍)

機能面の検証と同じくらい重要だったのが負荷試験です。INSERTタイミングを変更し、書き込みを一点(注文登録時)に集中させることは、データベースへの負荷を劇的に変化させます。

そこで、AWS Distributed Load Testing (DLT) と JMeter を用いて、比較検証を行いました。

  • 環境: 他のマイクロサービスはモック化しつつ、重要な「税計算」などは実稼働させて実施。

  • シナリオ: ピーク時トラフィック3倍の負荷。

  • 比較対象developブランチ(現行) vs 改修後ブランチ

今回、私自身JMeterやDLTを扱うのは初めての経験でしたが、メンターのサポートを受けつつスクリプトを一から作成しました。これにより、今回の検証だけでなく、今後チームが負荷試験を行う際の再利用可能な資産(スクリプトを残すことができました。

これはサンプルですが、DLTではこのように負荷試験を行なった結果がダッシュボードで確認できます。

この条件下で、現行のブランチと改修後のブランチのパフォーマンスを比較計測しました。書き込みが集中してもレイテンシやスループットに劣化が見られないことを定量的なデータで実証し、安全なリリースを担保しました。

3. フェーズ2の実装

検証フェーズで安全性が確認できた後、第2段階である本番テーブルへのINSERTタイミング切り替えの実装を行いました。

実装にあたっては、メンターとペアプログラミングも実施しました。 ドライバーとナビゲーターに分かれて実装を進めることで、設計方針のすり合わせを行い、手戻りを防ぐことができました。また、単に「やるべきこと」を実装するだけでなく、複雑な既存コードの中で改善できる箇所を見つけ、リファクタリングも並行して行いました。

検証用テーブルから本番テーブルへの書き込み先切り替えという実装でしたが、事前の検証と深いコード理解のおかげで、自信を持ってリリースできる状態を作り上げることができました。

4. リリース体験

今回のメインタスクとは別に、インターン期間中にはTerraformを用いたインフラ設定変更のリリース作業も担当しました。

リリース作業は、障害時の影響を最小限にするため食事時のピークタイムを避けて実施し、2人体制でのダブルチェックを実施しました。 出前館ではデプロイに GitHub Actions が採用されており、インフラ構成は Terraform でコード管理(IaC)されています。コードを見ればインフラ構成が一目でわかり、変更も安全かつ迅速に行える環境が整っていました。

実際のリリースを通じて、コードを書くだけではない「安全に本番環境へデプロイする仕組み」を肌で感じられたことも、今回のインターンでの大きな収穫です。

まとめ

今回のインターンでは、疎結合アーキテクチャという理想形を目指す過程で、その実現を阻む歴史的負債と向き合い、根本から解消するという貴重な経験を得ることができました。 単に機能を追加するのではなく、複雑に絡み合ったレガシーシステムを紐解き、あるべき姿へ正常化させる。そのエンジニアリングの難しさと面白さを実感できたことは、私にとって大きな財産です。

最後に

今回のインターンで最も印象に残っているのは、現在進行形で高いトラフィックを捌いているシステムを書き換えるというプレッシャーと責任感です。

新規開発であれば、自分の書いたコードが動くことそのものが喜びになります。しかし、今回のような歴史あるシステムの改修では、過去の経緯を正しく読み解き、既存の挙動を破壊することなく安全にリリースすることが何よりも求められました。

また、出前館というBtoCサービスならではの「スケール感」を肌で感じながら開発できたことも、非常に刺激的な経験でした。 個人開発や小規模な環境では、機能が実装できた時点でゴールになりがちです。しかし、食事時にリクエストが増加する出前館の環境では、「動くだけでなく、高負荷に耐え続けること」が絶対条件です。 私にとって初めて触れる負荷試験ツール(AWS DLTやJMeter)を駆使し、パフォーマンスと信頼性を自分の手で担保していくプロセスには、大規模開発でしか味わえないエンジニアリングの面白さと高揚感がありました。

レガシーシステム」という言葉はネガティブに捉えられがちですが、それは同時に20年間ビジネスを支え続けてきた実績でもあります。その重みと向き合いながら、現代の技術でシステムを止めることなく進化させる作業は、エンジニアとしての醍醐味が詰まっていました。

これからのキャリアにおいて、ただ新しいものを作るだけでなく、複雑化した現状を深く理解し、システムを健全な姿へと再構築できるエンジニアになりたいと強く感じています。 未熟な私を信じ、重要なタスクを任せてくださったメンター、そしてチームの皆様に心から感謝いたします。