アーキテクチャ論争は終わらない。なぜならみんなベストケースを比較するから。モノリス派はクリーンなDjangoアプリを見せる。マイクロサービス派はNetflixを見せる。サーバーレス派は月額$0.02のLambdaを見せる。ワーストケースを見せる人はいない。
3つのワーストケースすべてを見せよう。それから判断してくれ。
1つのコードベース、1つのデータベース、1つのデプロイ。小さなチームにとって、これが最もシンプルに素早く構築・リリースする方法だ。
app/
├── users/
├── billing/
├── orders/
├── notifications/
└── main.py
デプロイは3分。1人でシステム全体を理解できる。grepがコードベース全体で使える。デバッグはスタックトレースで、12サービスをまたぐ宝探しではない。
コードベースが成長すると問題が出る。 カートのちょっとした修正がアプリ全体の再デプロイを必要とする。1つの悪いリリースですべてがダウンする。テストスイートは45分かかる。すべてのマージがコンフリクト抽選だ。
でも誰も言わないことがある:ほとんどのチームはこの問題に当たらない。 同じリポジトリに毎日20人以上がコミットしないと、モノリスの調整コストがマイクロサービスの運用コストを実際に超えることはない。
Product、Cart、Orderがそれぞれ独立して動き、別々にスケールし、自分のデータを管理する。Cartへの変更を残りに影響なくリリースできる。
でも今、複数の可動部品を扱っている:
サービスディスカバリ、分散トレーシング、サービス間のリクエストルーティングが必要になる。0.1msだった関数呼び出しが、5msかかり14通りの失敗が可能なネットワークホップになる。
正直な真実: マイクロサービスはコードの複雑さを運用の複雑さと交換する。チームがKubernetesを確実に運用できないなら、サービスを追加しても午前3時のアラートが増えるだけだ。
サーバーを管理する代わりに、トリガーされたときに実行される関数を書く。クラウドプロバイダーがスケーリングを処理する。関数が実際に実行されたときだけ支払う。
コールドスタートがレイテンシを追加する — JVMベースのランタイムでは500ms以上になることもある。数十のステートレス関数をまたぐデバッグは混乱する。そして1つのクラウドのランタイムに依存するほど、後で乗り換えが難しくなる。
課金モデルもスケールで反転する。低トラフィックではサーバーレスはほぼ無料。高トラフィックでは、月額$50のEC2インスタンスが月額$500のLambda呼び出しに勝ち始める。
ほとんどの本番システムは1つのアプローチだけを使わない。通常はコアにモノリスがあり、時間とともに独立したスケーリングやより速いデプロイが必要なところでいくつかのサービスを切り出す。サーバーレスは通知、画像処理、cronジョブなどに後から登場する。
典型的な進化:
| 要素 | モノリス | マイクロサービス | サーバーレス | |------|---------|----------------|------------| | チーム規模 | 20人未満 | 50人以上 | 任意 | | デプロイ速度 | 数分 | サービスごとに数分 | 関数ごとに数秒 | | デバッグの難しさ | 低い | 高い | 中〜高 | | インフラのオーバーヘッド | 低い | 非常に高い | 低い | | ベンダーロックイン | 低い | 中 | 高い | | コールドスタートの痛み | なし | なし | 現実 | | 最適な用途 | ほとんどのスタートアップ | 大規模組織 | イベント駆動の接着剤 |
最もうまく機能するアーキテクチャは、チームが運用できるものだ。カンファレンスのステージのものでもなく、Netflixが使っているものでもない。
5人のエンジニアで、機能よりインフラに多くの時間を費やしているなら — どれを選んでもアーキテクチャの選択を間違えている。
チームの運用成熟度に合ったアーキテクチャを選べ。野心に合ったものではなく。
— blanho
NetflixはKafka、Cassandra、3つのキャッシュレイヤーを引き剥がした。すべてのキャッシュは嘘だから。
同期呼び出しは動くまでは動く。動かなくなったらメッセージキューが必要だ。その理由がこれ。
サーバーに隠れた状態があるから「箱を増やす」だけではダメなんだ。
# 必要だと思ったもの:
services:
- user-service
- auth-service
- order-service
- payment-service
- notification-service
- api-gateway
# それらを運用するために実際に必要なもの:
infrastructure:
- service-discovery
- distributed-tracing
- circuit-breakers
- container-orchestration
- centralized-logging
- secret-management# デモでは美しく見える
def handler(event, context):
user_id = event["pathParameters"]["id"]
user = table.get_item(Key={"id": user_id})
return {"statusCode": 200, "body": json.dumps(user)}