規模が大きくなると「マイクロサービス化」の話が出ますが、
分割は運用コストを大幅に増やします。その前にできることを整理します。
分割で増えるもの
- ネットワーク越しの通信(遅延・失敗を扱う必要が出る)
- トランザクションが跨げない(整合性の担保が難しくなる)
- デプロイ対象と監視対象の増加
- 障害の切り分けが複雑になる
チームが分かれていないのに、システムだけ分けるのは、
利点が出ないままコストだけ増える典型です。
効果の大きい順
1. クエリの改善(最大)
多くの「遅い」は、単純に N+1 かインデックス不足です。
# 遅いエンドポイントを特定して、クエリ数を数える
reset_queries()
response = client.get("/orders/")
print(len(connection.queries)) # 200 とか出たら N+1
サーバーを増やす前に、必ずここを確認します。
効果は数倍から数十倍になることがあり、コストはゼロです。
2. キャッシュ
集計値や、更新頻度の低いデータをキャッシュします。
これも構成を変えずに済みます。
3. 重い処理の非同期化
レポート生成やメール一括送信をリクエストから外します。
ワーカーが専有されなくなり、同時接続数の余力が生まれます。
4. 読み取り専用レプリカ
読み取りが多いなら、参照用のDBを分けます。
アプリのコードはほぼ変えずに負荷を分散できます。
DATABASES = {"default": {...}, "replica": {...}}
class ReadReplicaRouter:
def db_for_read(self, model, **hints):
return "replica"
def db_for_write(self, model, **hints):
return "default"
レプリケーション遅延があるため、
書き込み直後の読み取りはプライマリに向ける必要があります。
5. アプリサーバーの水平展開
モノリスでも、ステートレスなら台数を増やせます。
- セッションをDBかキャッシュに置く(メモリに持たない)
- アップロードファイルを共有ストレージに置く
- 定期処理が二重実行されないようにする
この3つを満たせば、モノリスのまま台数を増やせます。
それでも分割するとき
次の条件が揃ったときに、初めて分割を検討します。
- 特定の機能だけ極端に負荷が高く、独立してスケールさせたい
- チームが分かれていて、デプロイの独立性が必要
- 技術要件が根本的に違う(例:機械学習の推論だけ別基盤)
まず1つだけ切り出すのが安全です。全体を一度に分けると、
問題が起きたときに原因が特定できなくなります。
モジュール分割は先にやっておく
サービスとして分割しなくても、
コードの中でモジュール境界を明確にしておくことには価値があります。
apps/
orders/ … 注文
billing/ … 請求
inventory/ … 在庫
モジュール間の呼び出しを限定しておくと、
将来分割する必要が出たときの切り出しが容易になります。
まとめ
- クエリ改善(効果最大・コストゼロ)
- キャッシュ
- 重い処理の非同期化
- 読み取りレプリカ
- アプリサーバーの水平展開
ここまでやってもなお足りない場合に、初めて分割を検討します。
分割はスケールの手段であって、目的ではありません。