【実務・中級編】 スループット制限とスロットリング – Cloud Spanner

Cloud Spannerのスループット制限とスロットリング:真の限界を見極める分散アーキテクチャの極意

こんにちは。テックリードの私だ。
今日のコードレビュー、あるいはアーキテクチャ設計レビューで、こんなセリフを吐いていないか?

> 「Spannerはフルマネージドだから、トラフィックが急増しても勝手にスケールするんでしょ?」

もしそう考えているなら、今すぐその甘い認識を改めてもらう。Cloud Spannerは魔法の箱ではない。厳格な物理法則と分散システムの制約のうえに成り立つ、極めて精緻なマシンだ。

今回は、Spannerのコアアーキテクチャの心臓部である「ノードあたりのリソース制限」と「過負荷時のスロットリング制御」について、実務の現場で生き残るための知見を余すところなく伝授する。

—

1. Spannerの「見えない壁」:なぜスロットリングは起きるのか

Spannerのストレージとコンピュートは密に結合されているようで、実は完全に抽象化されている。データを保持する最小単位はスプリット(Split)であり、これが複数のノード(またはノードグループ)上に分散配置されることで水平スケールを実現している。

しかし、ここでエンジニアが陥りがちなしこたま厄介な罠がある。それが 「ホットスポット(Hotspot)」 だ。

スプリットとリーダーレプリカの物理的制約

Spannerの各スプリットには、Raftコンセンサスグループの「リーダー」が存在する。書き込み(Mutations)や読み取り(Read/Query)の大部分は、まずこのリーダーに集中する。

もし、あなたのアプリケーションが以下のようなアンチパターンを踏んでいるとどうなるか?

[クライアントA] ──┐
[クライアントB] ──┼──> [単一のスプリット/リーダー] ──> CPU使用率 100% ──> スロットリング発動!
[クライアントC] ──┘

  • 単調増加するプライマリキー(UUIDv1、タイムスタンプ、オートインクリメント等)
  • 特定のテナントIDにトラフィックが極度に集中する構造

リーダーを担う単一のノード(あるいはスプリット)のCPUが100%に張り付いた瞬間、Spannerの内部制御機構が働き、リクエストはスロットリング(流量制御)される。

—

2. スロットリングの正体:エラーコードと内部挙動

Spannerがリクエストを制限し始めると、アプリケーション層には以下のエラーが返ってくる。

  • `RESOURCE_EXHAUSTED` (gRPC Status: 8)
  • 稀に `DEADLINE_EXCEEDED` やレイテンシの異常な跳ね上がり

Spannerはどうやって身を守っているのか?

Spannerのオーバーロード制御は、単に「エラーを返す」だけではない。
過負荷を検知すると、Spannerはバックプレッシャー(Backpressure)をかけ、システム全体のカスケード障害(雪崩現象)を防ぐ。具体的には、キューイングの遅延、優先度制御、そしてキャパシティを超えたリクエストの即時拒否(スロットリング)を動的に行う。

ここで重要なのは、「1つのホットスポットが、データベース全体の健全性を巻き添えにしてダウンさせないようにするために、あえてスロットリングさせている」という点だ。この防衛機構があるおかげで、Spannerは全体としての可用性を保つことができる。

—

3. 堅牢な設計パターン:スロットリングを回避するアーキテクチャ

では、我々エンジニアはこの「見えない壁」をどう回避し、極限のスループットを引き出すべきか。設計レビューで必ずチェックすべき3つのパターンを提示する。

パターンA:プライマリキーのハッシュ化(Salting / Sharding)

単調増加キーによる書き込みの集中を防ぐ最も古典的かつ強力な手法が、キーの先頭へのプレフィックス(ハッシュ値やシャードID)の付与だ。

— 【アンチパターン】これだと全ての書き込みが単一のスプリットに集中する
CREATE TABLE Events (
EventId INT64,
EventTime TIMESTAMP,
Data STRING(MAX)
) PRIMARY KEY (EventId);

— 【推奨パターン】ハッシュプレフィックスにより、書き込みをN個のスプリットに分散させる
CREATE TABLE EventsSharded (
ShardId INT64, — 0 から N-1 の値(例: 0〜15の16シャード)
EventId INT64,
EventTime TIMESTAMP,
Data STRING(MAX)
) PRIMARY KEY (ShardId, EventId);

アプリ側で `ShardId = hash(EventId) % 16` のように計算して書き込む。これでスループットの物理限界は理論上、最大16倍に跳ね上がる。

パターンB:読み取りキャッシュとCQRSの活用

高頻度で参照されるが更新頻度が低いマスターデータなどは、Spannerに直接クエリを叩き続けるべきではない。

  • アプリケーション層でのインメモリキャッシュ(Guava, Redis等)
  • SpannerのStale Read(古いデータの読み取り)の積極活用

特に `BOUNDED_STALeness` を用いたStale Readは、リーダーレプリカをバイパスしてローカルのリードレプリカからデータを取得できるため、スループット制限の回避において最強の武器となる。

// JavaでのStale Read(Bounded Staleness)の活用例
TimestampBound bound = TimestampBound.ofMaxStaleness(
Duration.ofSeconds(10), // 最大10秒古いデータを許容
Duration.ofMinutes(1)
);
ResultSet resultSet = dbClient.singleUse(bound)
.executeQuery(Statement.of(“SELECT FROM HotConfigTable”));

—

4. パフォーマンス上の注意点とモニタリングの極意

「なんとなく遅い」で済ませるな。Spannerのパフォーマンスチューニングは科学だ。Cloud Monitoringで以下のメトリクスを常に監視・ダッシュボード化しておけ。

1. CPU Utilization (High Priority)

  • SpannerのCPU使用率。特に「高優先度CPU(High Priority CPU)」が65%を超えたら黄色信号、85%を超えたらスロットリングのカウントダウンが始まっていると認識せよ。

2. Transaction Abort Rate

  • 競合によるトランザクションのロールバック頻度。これもスループット低下の大きな要因になる。

3. Storage / Split Distribution

  • 特定のスプリットにストレージやQPSが偏っていないか、Spannerのインサイト機能を使って常時プロファイリングせよ。

—

チーフアーキテクトからの最終通告

Cloud Spannerは、正しく設計されれば無限とも思えるスケーラビリティを発揮する。しかし、それは「適当にコード書いても動く」という意味ではない。

データモデル、プライマリキーの選定、アクセスパターンの分散——これらをロジカルに突き詰めた者だけが、Spannerの真のパフォーマンスを手にすることができる。

次の設計レビューでは、ただ「動くもの」を作るな。「極限の負荷に耐え、スロースタートやスロットリングを華麗にいなすシステム」を私に見せてくれ。期待している。

コメント

タイトルとURLをコピーしました