2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【DynamoDB】ホットパーティションを起こせました。コストもしっかりかかりました【解決編】

2
Last updated at Posted at 2026-08-02
                                            
DynamoDB.png
Amazon DynamoDB

はじめに

前回、DynamoDB のホットパーティションを意図的に起こそうとして、見事に失敗しました。

パーティションキー設計の良し悪しによってスロットリングの発生に違いがあるかを確認しようとしましたが、いくつかの要因でなかなか発生せず。

今回はそのリベンジ編です。前回の要因を排除して再挑戦してみました。

まずは結論から

  • ホットパーティションを起こせました。 Bad 設計のテーブルでスロットル率 54.8%
  • WCU設定値を大きくしても1パーティションあたりの上限1000WCU/秒で頭打ち
  • オンデマンドでも発生しました。 ただし同じ負荷を繰り返したら消えました

想定される読者

  • DynamoDB のパーティションキー設計を検討している方
  • ホットパーティションの話は知っているが、実際の数字を見たことがない方

前回のおさらい

架空のコーヒー焙煎メーカー「Kiro Roasters」の在庫管理システムを題材にしています。倉庫は3拠点(東京 / 大阪 / 福岡)、商品は約5,000 SKU。朝の出荷ラッシュで東京倉庫に出庫が集中する、という設定です。

検証で使うパーティション設計は以下2種類です。

Bad 設計(カーディナリティ = 3)

PK: warehouseId   → "WH-TOKYO" / "WH-OSAKA" / "WH-FUKUOKA"
SK: itemId        → "ITEM#ETH-YIRG-G1-MEDIUM-200G" など

Good 設計(カーディナリティ = 約5,000)

PK: itemId        → "ITEM#ETH-YIRG-G1-MEDIUM-200G" など
SK: warehouseId   → "WH-TOKYO" / "WH-OSAKA" / "WH-FUKUOKA"

DynamoDB の物理パーティションには、1つのパーティションにつき書き込み 1,000 WCU/秒、読み取り 3,000 RCU/秒という上限があります。全体のキャパシティに余裕があっても、1つのパーティションキーにアクセスが集中すればその上限で頭打ちになります。これがホットパーティションです。

Bad 設計の場合は1つの物理パーティション(東京倉庫)に書き込みが集中するため、この上限に到達する想定でした。

ホットパーティション発生のメカニズム

検証を進めるうちに行き先を見失ってきたので、ホットパーティションが発生する流れを改めて整理しておきます。

DynamoDB はパーティションキーの値をハッシュ化して、書き込み先の物理パーティションを決めます。ここが全ての起点です。

Bad 設計のパーティションキーは warehouseId で、今回取り得る値は3種類しかありません。ハッシュの結果も3通りにしかならないため、書き込み先は最大3パーティションに固定されます。十分なテーブル容量があったとしても、特定のパーティションに書き込みが偏るとこうなります。

インプットの 4,000 req/s が東京80%と偏っていた場合、東京パーティションに 3,200 WCU/秒 のリクエストが届きます。ただし物理パーティション上限の 1,000 WCU/秒 までしか受け取れないので、あふれた 2,200 WCU/秒 が ThrottlingException になります。

テーブル全体に十分な容量があったとしても、空いているキャパシティは他のパーティションのものなので、東京の書き込みには回せません。容量を追加しても東京パーティションの上限は 1,000 WCU/秒 のままです。

一方 Good 設計はパーティションキーが itemId で約5,000種類あります。ハッシュの行き先が5,000通りに散るため、東京80%という偏りが残ったままでもパーティションの偏りにはならず、書き込みは成功します。

同じ倉庫の偏り、同じ負荷、同じテーブル容量。違いはパーティションキーの選び方だけです。

検証環境

前回からの変更点

前回は WCU の設定が小さすぎた(最大でも WCU 100)ため、物理パーティションが1つしか存在せず、Bad と Good でキー設計の差が出ませんでした。パーティションが1つなら、どんなキーを選んでも書き込み先は全部同じです。加えて、負荷投入時の実効 RPS が十分に上がらなかったため、想定通りのインプットが出来ていませんでした。

今回は WCU の設定値を十分に上げて、十分な負荷を投入した結果を確認していきます。

検証対象テーブル

当初から取り扱っていた Good/Bad テーブルだけでなく、GSI 設定付きテーブルとオンデマンドテーブルも比較対象に加えてみます。

論理名 PK / SK 課金モード 容量 GSI
bad warehouseId / itemId プロビジョンド WCU 10,000 / RCU 1,000 なし
good itemId / warehouseId プロビジョンド WCU 10,000 / RCU 1,000 なし
goodGsi itemId / warehouseId プロビジョンド WCU 10,000 / RCU 1,000 PK=warehouseId
badOnDemand warehouseId / itemId オンデマンド PAY_PER_REQUEST なし

goodGsi は「ベーステーブルは正しく設計したけれど、倉庫別に照会したいので GSI を足した」という、実際にありそうな構成です。badOnDemand は「オンデマンドにしておけば大丈夫じゃないの?」という根拠なき期待を確かめるためのものです。

負荷テストの条件

4パターンとも同じ条件で回します。

パラメータ
宣言 RPS 4,000 req/s(27ワーカー × 149 req/s)
継続時間 120秒
総リクエスト数 480,000(4,000 x 120 = 480,000)
倉庫分布 東京80% / 大阪10% / 福岡10%
データ量 各テーブル15,000レコード(5,000 SKU × 3倉庫)
リージョン us-west-2
SDK リトライ 無効(maxAttempts: 1

東京への集中率を前回の 70% から 80% に上げました。4,000 × 0.8 = 3,200 WCU/秒 が1つのパーティションに向かう計算で、上限 1,000 WCU/秒 を大きく超えさせる狙いです。

アーキテクチャ図

DynamoDB負荷テスト_アーキテクチャ図2.png

基本的には前回検証時と同様です。変更点は以下3点。

  • DynamoDB を 4テーブル(bad / good / goodGsi / badOnDemand)に追加
  • 負荷投入 Lambda のメモリが 1024MB → 2048MB
  • ワーカー数も増加(4 → 27)

検証結果

まず全体の結果です。総リクエスト数は 480,000。しっかりと負荷を叩き込んでます。

# テーブル 成功 スロットル スロットル率 実効 RPS 消費 WCU ピーク
bad 216,860 263,140 54.8% 4,034 1,805/s
good 480,000 0 0% 4,034 3,995/s
goodGsi 352,340 127,660 26.6% 4,034 3,238/s
badOnDemand 215,851 264,149 55.0% 4,034 1,800/s

個々の結果については以降で説明しますが、スロットリングもしっかりと発生しておりひと安心。

検証① Bad Table vs ② Good Table

ここが本来やりたかった比較です。同じ負荷・同じ容量・同じデータ量で、パーティションキーの設計だけが違う2つのテーブルを並べます。

結果

指標 ① Bad(PK=warehouseId) ② Good(PK=itemId)
総リクエスト 480,000 480,000
成功 216,860 480,000
スロットル 263,140 0
スロットル率 54.8% 0%
消費 WCU ピーク 1,805/s 3,995/s

Bad Table でスロットリングが発生する一方で、Good Table であれば負荷が集中しても全てのリクエストが成功していることが分かります。キー設計が異なるだけで、スロットル率 54.8% → 0% です。

CloudWatch で両テーブルの消費 WCU とスロットリング発生数を並べたグラフです。

DynamoDB負荷テスト結果①②.png

Bad(青)はスロットル(緑)が大量に発生し、処理できたリクエストが半分以下に抑えられています。Good(赤)はスロットルゼロで、投げた分がそのまま処理されています。

スロットル率は理論値どおり

前半説明したホットパーティションのメカニズムがそのまま数字に出ています。東京パーティションに 3,227 req/s が集中し、上限 1,000 WCU/秒 を超えた 2,227 req/s が弾かれた結果、スロットル率は理論値 55.2% に対して実測 54.8% でした。

一方 Good は、パーティションキーが約5,000種類に分散しているため、東京80%の偏りがあってもどのパーティションも上限に到達しません。スロットルゼロで全リクエストが成功しています。

容量をいくら積んでも、キー設計が悪ければ1パーティション 1,000 WCU/秒 の壁で止まる。キー設計が正しければ、同じ偏りでも 4,000 RPS を素通しできる。 これが本記事の一番見せたかった数字です。

検証③ Good Table + GSI

ベーステーブルは Good 設計、そこに GSI を3本足した構成です。3本とも PK は warehouseId にしてあります。「倉庫別に一覧したい」「倉庫別に棚番号で引きたい」「倉庫別に単価で並べたい」という、普通に出てきそうな要件を想定しました。

結果

書き込みの 26.6% が失敗

検証②(GSIなし)では 0% だったスロットルが、GSI を足しただけで 26.6% に跳ね上がりました。ベーステーブルのキー設計は同じ Good 設計のままです。

原因

GSI の PK が warehouseId なので、GSI 側でホットパーティションが発生

ベーステーブルは PK=itemId で分散していますが、GSI は PK=warehouseId なので結局3つのパーティションに集中します。GSI のパーティションが詰まると、ベーステーブルへの書き込みリクエスト自体が拒否されます。アプリケーションから見ると、ベーステーブルのキー設計を正しくしたのにスロットルが返ってくる、という状態です。

CloudWatch で Good(青)と Good + GSI(オレンジ)の消費 WCU を並べたグラフです。

DynamoDB負荷テスト結果③.png

同じ Good 設計なのに、GSI を足しただけで消費 WCU が頭打ちになり、約19%落ちていることが見て取れます。

GSI への書き込みは非同期なので「ベーステーブルには影響しない」と考えていましたが、GSI のキャパシティが詰まるとベーステーブルの書き込み自体がブロックされます。ベーステーブルのキー設計を正しくしても、GSI のキー設計を間違えるとホットパーティションは戻ってきます。

検証④ オンデマンド

「オンデマンドなら DynamoDB が勝手にスケールしてくれるのでは?」を確かめます。

結果

初回は Bad Table と同じく 55.0% でスロットリング

パーティション単位の 1,000 WCU/秒 という制限は、課金モードに関係なく適用されます。オンデマンドはホットパーティションの対策にはなりません。

ただ、同じ負荷を繰り返したところ挙動が変わりました。

実行 スロットル率 消費 WCU ピーク
1回目 55.0% 1,800/s
2回目 30.0% 2,797/s
3回目 0.0% 4,005/s

CloudWatch で結果を見ると、回を追うごとにスロットル率(オレンジ)が下がり、3回目でゼロになりました。その分、消費 WCU が伸びています。

DynamoDB負荷テスト結果④.png

原因

split-for-heat が働いたと思われます

DynamoDB には、ホットなパーティションを検知するとそのパーティションを内部的に2つに分割する仕組みがあります。これが split-for-heat です。

通常、1パーティションの上限は 1,000 WCU/秒 です。しかし分割されると、同じパーティションキーの範囲を2つの物理パーティションが受け持つようになるため、実質的な上限が 2,000 WCU/秒 に上がります。さらに負荷が続けばもう一度分割されて 4,000 WCU/秒 に、という具合です。

今回の実測はこの動きと一致しています。

1回目: 上限 1,000 → 消費WCU 1,800/s で頭打ち、スロットル 55%
2回目: 上限が上がった? → 消費WCU 2,797/s、スロットル 30%
3回目: さらに上がった? → 消費WCU 4,005/s、スロットル 0%

ただしパーティション数を直接確認できる API はないので、これは間接証拠からの推測です。「パーティションが分割されたから上限が上がった」と断定はできません。

まとめ

無事、DynamoDB のホットパーティションを故意に発生させる事ができました。
当初想定していたパターンの実証だけでなく、GSIやオンデマンドなど追加パターンの検証もでき、有意義な内容になりました。

ちなみにこのために使った DynamoDB のコストは、176ドル になりました。追加検証を行った1日だけの金額です。プロビジョンドが大半を占めており、オンデマンドはごくわずか(1ドル未満)です。
今回の検証でオンデマンドモードでもホットパーティションが発生することは確認できたので、もし似た検証を検討されている方はオンデマンドでの検証をオススメします。

さらに実運用を考える上でもオンデマンドの適応力(split-for-heat?)は魅力的に感じました。
今回はいきなり極端に高い負荷を叩き込みましたが、本来徐々に負荷が上がるような状況が想定されるため、仮にパーティションキーの設計が甘くても負荷状況に応じて良い感じにパーティション分割が働いてくれるのではないかと期待できるのではないかと。もちろん、キー設計をしっかりやるに越したことはないですけどね。

参考リンク

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?