どのアーキテクチャ図もマイグレーションをきれいに見せる。古い箱、矢印、新しい箱。完了。
実際にやると、古い箱には8年分の隠れた振る舞いがあることに気づく。
Redditのバックエンド:1つのPythonサービス、4つのコアモデル — Comments、Accounts、Posts、Subreddits — 異なるチームがお互いの足を踏んでいた。
彼らはCommentsを最初に選んだ。最大のデータセット。最高の書き込みスループット。大胆な選択だ。
トラフィックを新しいGoサービスにルーティングする。レスポンスを生成し、古いPythonエンドポイントも呼び、両方を比較し、差分をログに記録。Pythonのレスポンスをユーザーに返す。
Tap Compareの仕組み: リクエストがGoサービスに来て、レスポンスを生成する。GoはシャドーモードでPythonも呼び、結果を比較し、差分をログに記録。でも常にPythonのレスポンスをユーザーに返す — 完全に安全。
Goにバグがあっても、ユーザーには見えない。リスクゼロ。
Commentsは3箇所に同時に書き込む:Postgres、Memcached、Redis(CDCイベント)。Tap Compareは使えない — コメントIDはユニークだから。
書き込みの問題: ユーザーが「Hello」というコメントを作成すると、Postgres(プライマリ)、Memcached(キャッシュ)、Redis(CDCイベント)に書き込む。Goはシャドーできない — IDが衝突する!
Redditのソリューション:シスターデータストア。Goだけが書き込む完全なミラー。
Goにバグがあっても、分離されたコピーだけが壊れる。本番は安全なまま。
シリアライゼーションの不一致。 PythonとGoはシリアライズの仕方が違う。
隠れたORMマジック。 PythonのORMには誰もドキュメント化していない最適化があった。Goは同じロジックでデータベースを3倍叩いた。
レースコンディション。 ユーザーがコメントを「hello」に編集。Goが書き込む。別のユーザーが「hello again」に編集。比較失敗。バグかタイミングか?解明に何時間も。
P99レイテンシ:
Python: 時々15秒(!)
Go: 一貫して100ms未満
マイグレーション期間: 18ヶ月
比較コードの行数: 10,000行以上
短期的な痛みは報われた。
マイグレーションは新システムが動いたら完了ではない。古いシステムをオフにできたら完了だ。
— blanho
部分障害、ネットワークの嘘、クロックドリフト。分散システムを悪夢にするすべて。
APIゲートウェイは外部のカオスを処理する。サービスメッシュは内部のカオスを処理する。
どちらも同じAPIを呼んでいる。違いはエージェントがどう呼び出すかだ — そしてその違いは思っている以上に重要だ。
# シスターデータストアパターン
def create_comment(content):
# 1. トラフィックがGoに来る
# 2. GoはREALの書き込みのためにPythonを呼ぶ
real_id = python_service.create_comment(content)
# 3. Goは分離されたシスターストアに書き込む
sister_postgres.insert(content)
sister_memcached.set(content)
sister_redis.publish(content)
# 4. 本番 vs シスターデータを比較
compare_and_log(real_id, sister_id)
return real_id # 常にPythonの結果を返す# Python: {"created_at": "2024-01-15T10:30:00Z"}
# Go: {"created_at": "2024-01-15T10:30:00+00:00"}
# 同じタイムスタンプ。異なる文字列。比較失敗。