データベーストランザクション内での Topology Service クレーム
このページには今後予定されている製品・機能・機能性に関する情報が含まれています。ここに示す情報は参考目的のみです。購入・計画の決定にこの情報を使用しないでください。製品・機能・機能性の開発、リリース、タイミングは変更または延期される可能性があり、GitLab Inc. の独自の判断に委ねられています。
コンテキスト
GitLab の Cells アーキテクチャでは、Topology Service のトランザクションで詳しく説明されているように、メールアドレスやルートなどのクラスター全体で一意なリソースをクレームするために Topology Service との調整が必要です。 重要な決定事項は、これらのクレームリクエストをデータベーストランザクションの内部で行うか外部で行うかという点です。
主な要件:
- 複数の Cell をまたいだ競合を防ぐためにリソースをクレームする。
- Rails DB と Topology Service の間のデータ整合性を維持する。
- パフォーマンスクレームのバッチ処理をサポートする。
- 平均 50 クレーム/秒を処理し、最大 6 倍の 300 クレーム/秒に対応できること。
- データベースの安定性に影響を与えないこと。
技術的な制約:
- Topology Service へのネットワーク呼び出し:P99.95 で約 200ms
- 現在のコネクションプール:58 コネクションで 200ms で約 290 クレーム/秒をサポート
- user.id など、Postgres のプライマリキーが必要。これはトランザクション内でレコードをデータベースに保存するときに生成される。
- Rails はトランザクション内で作成/更新を自動的にラップする
決定事項
ActiveRecord トランザクション内部でコールバックを使用してクレームを実装します。実装では以下を行います:
after_save(公式にドキュメント化)またはbefore_commit(ドキュメント未掲載)コールバックを使用し、トランザクション内で実行します。- INSERT 後、COMMIT 前(すべての ID が利用可能になった時点)にリソースをクレームします。
- 可能な場合は同一トランザクションからの複数のクレームをバッチ処理します。
- Topology Service リクエストにクライアント側 200ms タイムアウトを設定します。
- トランザクション内の Topology Service リクエストが
Nクレーム/秒になった時点でクライアント側サーキットブレーカーを実装します。Nは設定可能で、データベース負荷を考慮します。 - クレームのロールアウトでは、フィーチャーフラグを使用して実施するクレームの割合を段階的に増やし、データベースへの悪影響を観察します。正確なロールアウト手順はまだ決定されておらず、レガシー Cell として GitLab.com を Cell クラスターに採用の一部として決定されます。
監視要件:
- コネクションプールの使用率と待機時間。
- クレーム関連操作のトランザクション時間。
- Topology Service リクエストのレイテンシ(P50、P99、P99.95)。
- クライアント側サーキットブレーカーのヒット数。
- クライアント側タイムアウトのヒット数。
- クライアントとサーバーからの失敗したクレームとロールバック率。
結果と影響
ポジティブ:
- シンプルな実装:既存の Rails パターンとコールバック内で動作する
- 早期本番投入:最小限のアーキテクチャ変更が必要
- アトミックなロールバック:クレームが失敗した場合にトランザクションがクリーンにロールバックされる
- 副作用の問題なし:コミット前にクレームが発生するため、Topology Service への確認前にデータベースにコミットしてしまう状況を防ぐ
- 十分な容量:現在のインフラストラクチャがピーク負荷の 6 倍をサポート
- バッチ処理サポート:すべての ID が利用可能になった後、同一トランザクション内でクレームをバッチ処理できる
ネガティブ:
- コネクションプールの使用:ネットワーク呼び出し中はクレームのためにデータベースコネクションを保持する。
- トランザクション時間:約 200ms のより長いトランザクションはデータベースへの負荷増加につながる可能性:
- WAL の蓄積
- 各トランザクションがコネクションを保持するため、コネクションプールの枯渇
- スケーラビリティの上限:コネクションプールサイズに制限される(現在 290 クレーム/秒)
代替案
オプション 1:トランザクション外でのクレーム
以下の理由で延期:
- 大規模なリファクタリングが必要。例えば何千もの
User.create!呼び出しの変更が必要で、クレームするモデルごとに必要となる。 - トランザクションの自動ラップという Rails の慣習に反する。
- 実装の複雑さとエラーハンドリングが高い。
- ID がまだ利用できない場合にクレームのバッチ処理が難しい。
- 現在の容量(ピークの 6 倍)は最適化を時期尚早にする。
オプション 2:データベーストリガー
以下の理由で却下:
- データベース以外の副作用を処理できない。
- Rails アプリケーション層の外に複雑さを追加する。
- テストと保守が難しい。
- カスケード削除ケースはすでに検証ループで処理されている。
- クラスター全体でリソースがクレーム可能かどうかを検証できない。
オプション 3:ActiveRecord の save/update のオーバーライド
以下の理由で却下:
- 複数の属性を効率的にバッチ処理できない。
- バッチリクエストではなく属性ごとに 1 つのクレームをトリガーする。
update_columnなどのメソッドでは動作しない。
