スタートアップには4人のエンジニア、12人のユーザー、47個のマイクロサービスがある。今や機能をリリースするよりネットワーク呼び出しのデバッグに時間をかけている。
友人の会社がマイクロサービスに移行した。Djangoモノリスがあって問題なく動いていた。デプロイに3分。4人のエンジニア。users、auth、email、notificationsがきれいにフォルダで分かれていた。
CTOがNetflixのブログ記事を読んだ。2スプリント後、5つの別々のサービスになった:user-svc、auth-svc、email-svc、notification-svc、api-gateway。デプロイは45分に膨らんだ。Kubernetes専任のDevOpsを採用しなければならなかった。4人のエンジニアのまま。
かつては5つのDjangoアプリだったものに5つのリポジトリ。
3ヶ月後:ジュニア開発者が機能を追加できなくなった—3つのサービスに触る必要があるから。スケーリングの問題はゼロ解決。運用の問題を15個作った。
「Netflixがやってる」と言うけど、本当の理由は履歴書の飾りだ。Netflixには2,000人のエンジニアがいるという現実を見ない。
「スケールが必要」と言うけど、本当の理由はFOMOだ。500ユーザーには必要ないという現実を見ない。
「独立デプロイ」と言うけど、本当の理由はあるチームが本番を壊したからだ。代わりにテストを直せばいい。
「テクノロジーの自由」と言うけど、本当の理由は誰かがRustを使いたいからだ。3人しかいないのに。
モノリスなら関数呼び出し一発だ。0.1ミリ秒、絶対失敗しない、型チェック済み。
マイクロサービスになると地獄が始まる:
これをすべてのサービス呼び出しに掛け算しろ。さらにサービスディスカバリ、ロードバランシング、TLS、認証トークンを追加しろ。すべてのサービス境界がもう1つの障害点だ。
モノリスの障害モードはシンプル—データベースダウン、メモリ不足、コードのバグ。マイクロサービスはそれら全部に加えて、ネットワーク分断、サービスが見つからない、タイムアウト、サーキットブレーカー発動、メッセージキューのバックアップ、分散トランザクション失敗、サービス間のデータ不整合。
同じコードベース。同じデプロイ。明確な内部境界。
モジュールは契約付き関数呼び出しで通信する。型チェック済み、即座、信頼できる。HTTPじゃない、ネットワークじゃない、ただのコード。後でbillingを抽出したくなったら境界はすでにある—importをHTTPクライアントに置き換えるだけ。
モジュール性の90%、分散税の0%。
Shopifyはモジュラーモノリスを運用している。Black Fridayを捌いている。彼らにマイクロサービスが必要ないなら、君にも必要ない。
50人以上のエンジニアがいてチーム調整が問題になっているなら。独立したスケーリングが「いつか」ではなく今必要なら。異なるランタイムが必要なら—MLチームはPython、APIチームはGoとか。分割が解決するEXACTな問題を説明できるなら。
必要ないのは:20人以下、トラフィックが1つの良いサーバーに収まる、「Netflixがやってる」、分割が何を解決するか明確に言えない、「スケールするかもしれない」。
今日のスケールのために作れ。夢見ているスケールのためではなく。
— blanho
try:
response = requests.get(
f"http://user-service/users/{user_id}",
timeout=5,
headers={"X-Request-ID": trace_id}
)
response.raise_for_status()
user = response.json()
except Timeout:
# リトライ?サーキットブレーカー?フォールバック?
logger.error("User service timeout", extra={"trace_id": trace_id})
except ConnectionError:
# サービスダウン?ネットワーク問題?DNS問題?
pass# モジュラーモノリス構造
app/
├── modules/
│ ├── users/
│ │ ├── api.py # パブリックインターフェース
│ │ ├── service.py # ビジネスロジック
│ │ └── models.py # 内部モデル(プライベート)
│ ├── billing/
│ │ ├── api.py
│ │ └── ...
│ └── notifications/
│ └── ...
└── main.py