- Twilio Segmentは数百のマイクロサービス構成を運用していたが、複雑さと保守負荷のため**単一サービス(モノリス)**へ移行した
- 当初は各デスティネーションAPIを分離して障害分離とスケーラビリティを確保していたが、サービス数が140以上に増えるにつれて運用オーバーヘッドが急増した
- 多数のリポジトリと共有ライブラリの管理が難しくなり、テスト・デプロイ時に全サービスへ影響が及ぶ問題が発生した
- これを解決するためCentrifugeシステムとモノレポ構成を導入し、テスト自動化のためにTraffic Recorderを構築した
- 結果として開発速度と安定性が大きく向上し、Twilio Segmentは生産性と運用効率のためモノリシック構成を維持している
マイクロサービス導入と限界
- Twilio Segmentは顧客データ基盤のためにマイクロサービスアーキテクチャを採用し、各用途別サービスが独立してイベントを処理するよう設計した
- 数百のサーバーサイドデスティネーション(例: Google Analytics、Optimizelyなど)へデータを送信
- 当初は単一キューを使っていたが、特定デスティネーションの障害時に全体の遅延が発生するヘッド・オブ・ライン・ブロッキングの問題が起きた
- これを解決するため、各デスティネーションごとに別個のサービスとキューを構成し、障害分離と独立したスケーリングを実現した
- しかしサービス数が増えるにつれ、運用の複雑さと保守コストが急増し、開発速度の低下と欠陥率の上昇につながった
個別リポジトリと共有ライブラリの問題
- 各デスティネーションは異なるAPIフォーマットを使うため、カスタム変換コードが必要だった
- 当初は単一リポジトリで管理していたが、テスト失敗が全体に影響するためリポジトリ分割を実施した
- その後50以上の新規デスティネーション追加により、50以上のリポジトリが生まれた
- 共通機能のために共有ライブラリを導入したが、バージョン不整合とデプロイ負荷が大きくなった
- サービスごとに負荷パターンが異なるため、オートスケーリング設定が難しく、運用担当者が手動調整しなければならない場合もあった
モノリシック移行とCentrifuge導入
- 140以上のサービスを単一サービスへ統合することを決定
- 個別キューを置き換えるためにCentrifugeシステムを開発し、すべてのイベントを単一サービスへ送るようにした
- Centrifugeはその後、Twilio SegmentのConnectionsバックエンドインフラへ発展した
- 単一サービス構造へ移行することで、運用負荷の軽減と障害対応の単純化を実現した
モノレポとテスト自動化
- すべてのデスティネーションコードを1つのリポジトリに統合し、120以上の依存関係を単一バージョンへ統一
- テスト自動化のためにTraffic Recorderを導入
- 実際のHTTPリクエスト・レスポンスを記録して再生し、外部ネットワーク依存を排除
- テスト速度は数分からミリ秒単位まで短縮され、安定性が大幅に向上
- テスト失敗率が下がり、開発者の生産性も大きく改善した
モノリシック構成の効果とトレードオフ
- 単一サービスへ統合後、デプロイ速度と開発効率が大きく向上
- 1年間で共有ライブラリ改善回数が32件から46件へ増加
- 単独のエンジニアが数分以内にデプロイ可能
- 運用効率も改善され、負荷急増時でも大規模ワーカープールで吸収可能となった
- ただし、欠陥分離の難しさ、キャッシュ効率の低下、依存関係更新リスクなどの欠点もある
結論
- マイクロサービスは初期の性能問題を解決したが、大規模な拡張と一括更新には不向きだった
- モノリシック移行によって運用の安定性と開発速度の両方を改善
- 成功した移行のためには、堅牢なテスト体制とトレードオフの受容が不可欠
- Twilio Segmentは一部インフラではなおマイクロサービスを維持しているが、サーバーサイドデスティネーションではモノリシックのほうが適した構造と評価されている
2件のコメント
すべてを細かく分割して正規化するのは、リスクがある気がします。
Hacker Newsの意見
すべての宛先向けコードを1つのrepoに集約したことで、単一サービスへ統合できた
その結果、開発生産性が大きく向上した。今では共有ライブラリを1つ修正するたびに140以上のサービスをデプロイする必要がない
1人のエンジニアが数分でデプロイできるようになった
もしライブラリ変更のたびに全サービスを再デプロイしなければならないなら、それは真のサービスではなく分散モノリス構造だ
共有ライブラリを全サービスに強制的に同期させるという発想自体が、サービスアーキテクチャの哲学に合っていない
これは「ライブラリ更新のたびに全再デプロイ」というより、Amazon式の共有ビルド・デプロイシステムに近い
中央管理された単一ソースからライブラリを取得していて、バージョンが違えば互換性の問題で全体を移行する必要がある
セキュリティ脆弱性のため特定バージョンを排除しなければならない場合は全体再デプロイが必要だが、中央集権的な管理の利点のほうがはるかに大きい
こうしたシステムは依然としてマイクロサービスに分類されるが、コストと運用効率の面では共有環境のように振る舞う
これを分散モノリスと呼ぶのは拡大解釈だ
マイクロサービスのパターンに従うとデプロイリスクは増えるが、最初は見えにくい
たとえば金銭関連ライブラリのバグを修正した場合、現実的には全サービスを再デプロイすべきか悩むことになる
脆弱性のあるライブラリは、システム設計とは無関係に全面的に置き換える必要がある
そういう場合はむしろモノリシック構造のほうが扱いやすい
本当のマイクロサービスならメッセージをやり取りし、JSONを使うべきだ
コードではなくAPIだけを知っていれば十分であるべきだ。そうしてこそ各自が独立してデプロイとスケールを行える
共有モジュールを活用するほうが合理的ではないか?
前の会社ではあらゆるものをマイクロサービスで運用していて、その前の会社はAWSサーバーレスだった
どちらの場合もサービス間通信が最大の問題だった。契約(contracts)の同期が難しく、デプロイも複雑だった
初期は素早く動けたが、時間がたつにつれて複雑性が爆発した。恐怖ベースの開発が起こり、会議が多すぎた
今の会社はモノリシック構造だが、はるかに扱いやすい。型が明確で、リファクタリングも簡単だ
自社プラットフォーム上に構築されたAIエージェントがコードベース内で自律的に改善していくのを見るのは興味深い
欠点はビルド時間が長いことだけだが、ツールチェーンの進歩によって2026年には10倍速いデプロイを期待している
私の結論は、モノリシック構造のおかげで、はるかに速く成長しスケールできたということだ
モノリシック構造では常に関心の分離が壊れ、チーム間の結合が強かった
本当の速度とスケールは、チームが分離されているときにしか実現できなかった
ORMからDTOへの移行には2年、50チーム、150人以上が必要だった
こうした複雑な作業は、マイクロサービスでなければ不可能だった
この記事を見ると、問題の核心はマイクロサービス vs モノリスという技術的選択ではなく、
エンジニアリング組織の品質と構造にある
コードリポジトリとテスト構造が、そのまま組織のレベルを映し出している
「それはやめよう」と言える人がいなければ、複雑性は爆発する
権限あるリーダーがいてこそ、チームは立ち止まって考えられる
APIの問題が起きても原因を分析せず、データだけ修正してチケットを閉じていた
同じ問題が繰り返されても根本原因を解決しない
面接をするだけで、その会社のコードベース構造をある程度予測できるほどだ
これは本当のモノリスへの転換というより、依然としてSOA構造だ
ただサービスのスコープが大きくなっただけだ
140サービスを1つのチームが管理するなら、SOAはサービスをスケールさせるためではなく、チームをスケールさせるための構造だ
1つのチームがすべての共有ライブラリを管理すると、バージョン不一致やAPIの混乱が生じる
結局、組織構造がアーキテクチャを決める。1つのチームが複雑性を減らすために統合したのだ
これは「モノリス」ではなく、チーム単位で適切にスコープ調整されたサービスレベルだ
こうした構造が最も理想的だと思う。チームが大きくなれば再び分離すべきだ
私はマイクロサービス支持者ではないが、「モノレポ vs マイクロサービス」という偽りの二分法が目につく
あまりに多くのツールがサービスとrepoを1:1で想定している
しかし、すべてを1つのrepoに置いても独立してデプロイすることはできる
GitHubのような場所でフォルダ単位を独立したサービスとして扱えるとよいのだが
Bazelで依存関係ツリーを管理し、
bazel queryで影響を受けるターゲットを見つけてテストを自動実行していたGitHub Actionsと連携してPRをブロックするワークフローも作った
うまく動いたが、構築には数か月かかった
実際の問題は運用とツールの不足にあった — CI、自動スケーリング、オンコール体制のどれも不十分だった
どちらのアプローチも失敗しうる
Node.jsやPythonのような環境では、イベントループが処理できるコード量に限界がある
6〜8人で200サービスを管理したこともあれば、80人で1つのモノリスを管理したこともある
マイクロサービスは小さな変更には有利だが、全体変更は難しく、
モノリシックはその逆だ
結局重要なのはアーキテクチャではなく、抽象化とテスト、デカップリングのやり方だ
マイクロの基準は技術ではなくビジネス単位だ
それ以下に分割するとナノサービスになる
Beam、JVM、Rust、Goのような環境では、すでに解決済みの問題だ
CPUキャッシュの問題なのか?
普通はGo、Java、C#を使うものだと思っていた
ほとんどの会社ではマイクロサービスはむしろ問題の90%の原因だった
AWS、Google、Netflixのような巨大組織でなければ向いていない
システムを組み立て可能な単位に分割すること自体がすでに難しいのに、そこへネットワーク境界を追加するのは愚かだ
次のトレンドはReactやSPAから離れ、サーバー中心への回帰になる気がする
マイクロサービスに移行した理由が「テストがよく壊れるから」というのは、あまりに本末転倒なアプローチに見える
テストが壊れるからといってコードベース構造を丸ごと変えるのはおかしい
チームごとにVMとCI/CD設定を分離したら、テストの衝突はなくなった
欠点は機能間の衝突を即座に見つけられないことだが、コード所有権が明確だったので大きな問題はなかった
タイトルに[2018]を追加してほしいという要望があった
「テストが壊れると無関係なコードまで修正しなければならなかった」という理由でrepoを分離したらしいが、
テスト実行方式を変えたり手動デプロイを許可したりするなど、別の解決策もあったはずだ
repo分離が唯一の解法ではなかった