DynamoDB の並列スキャン
並列スキャンは、1つの Scan を N 個の独立した Scan リクエストに分割し、それぞれがテーブルの Segment を担当することで、複数のワーカーが同時に読み取れるようにします。テーブル全体を、1パーティションのスループットが許す以上に速く読む唯一の方法です。
DynamoDB の並列スキャンとは何ですか?
DynamoDB の並列スキャンは、1つの Scan を N 個の独立したリクエストに分割し、それぞれが Segment と TotalSegments によってテーブルの Segment を担当することで、複数のワーカーが同時に読み取れるようにします。テーブル全体を単一パーティションのスループットが許す以上に速く読む唯一の方法ですが、それでもフルリードであるため、スキャンしたすべてのアイテムに対して課金されます。
- 逐次
Scanは一度に1パーティションを読む — その速度はテーブルがどれだけ大きくても、単一パーティションのスループットで頭打ちになる。 Segment+TotalSegmentsが読み取りをシャーディングする —TotalSegments個のワーカーにまたがり、各ワーカーが自分のスライスを並列にスキャンする。- DynamoDB はパーティションキーをハッシュしてセグメントを割り当てる ので、スライスは偏りうる — ワーカーを増やしても必ずしも速くならない。
- それでも
Scanであることに変わりない。 すべてのアイテムの読み取りに支払い、大きな並列スキャンはライブトラフィックの足元からテーブルのスループットを奪い去りうる。
逐次 Scan が遅い理由
SQL から来ると、フルテーブル読み取りは1つのストリーミング操作のように感じられます。DynamoDB ではそうではありません。テーブルのデータは多くの物理パーティションにまたがって存在しますが、単一の Scan はそれらを 一度に1つずつ、1ページあたり 1 MB で歩きます。
つまりプレーンな Scan は、テーブルが余裕のあるキャパシティを持つ何十ものパーティションに分散していても、ある瞬間に引き出せるのは1パーティションのスループット予算からだけです。テーブルが大きいほど、這うように長くかかります。(AWS: 並列スキャン)
Segment と TotalSegments で読み取りを分割する
並列スキャンはこのボトルネックを直します。ワーカー数を選び、TotalSegments をその数にセットし、各ワーカーに 0 始まりの異なる Segment を与えます。各ワーカーが自分の Scan を発行し、DynamoDB はそれらを並行して処理します。
Worker 0 → Scan Segment=0 TotalSegments=4
Worker 1 → Scan Segment=1 TotalSegments=4
Worker 2 → Scan Segment=2 TotalSegments=4
Worker 3 → Scan Segment=3 TotalSegments=4
各ワーカーは依然として LastEvaluatedKey で独立してページングします — 最初のページから最後まで自分のセグメントを所有します。アプリケーションが4つのストリームを縫い合わせて戻します。これで1つではなく、4パーティション分のスループットを一度に読んでいることになります。
実例: 夜間エクスポート
テレメトリのテーブル sensor-readings を運用しているとします。各アイテムはフィールドデバイスからの1つの読み取り値です。
PK = "DEVICE#a83f" (partition key — the device id)
SK = "TS#2026-06-22T03:14" (sort key — ISO timestamp)
batteryMv = 3120
tempC = 41.8
firmwareTag = "fw-7.2.1"毎晩 cron ジョブが分析ウェアハウス向けにテーブル全体を S3 へダンプします。80 GB の逐次 Scan は数時間かかり、プロビジョンドされた読み取りキャパシティをほとんど使い切りません。そこで8つのワーカーにファンアウトします。
Scan sensor-readings Segment=0 TotalSegments=8 ConsistentRead=false
…
Scan sensor-readings Segment=7 TotalSegments=8 ConsistentRead=false
8ワーカー、8セグメント、1回のテーブル読み取りがおよそ8倍速くなります。最近の読み取り値だけが必要なら、行が回線に乗る前に古いタイムスタンプを落とす FilterExpression を追加します — その式の構築と確認は式ビルダーで行いましょう。
FilterExpression: begins_with(SK, :today)DynamoDB はアイテムをセグメントにどう割り当てるか
ここが人々をつまずかせる部分です。DynamoDB は各アイテムを パーティションキーのハッシュ でセグメントに割り当てます — 行数でもバイト数でもありません。
そのため、PK を共有するすべてのアイテムは同じセグメントに着地します。sensor-readings では、DEVICE#a83f のすべての読み取り値が、そのデバイスのタイムスタンプの数やアイテムコレクションの大きさに関わらず、1つのワーカーに行きます。(AWS: 並列スキャン)
結果として、セグメントは 不均一 になります。1つのワーカーが何百万もの読み取り値を持つおしゃべりなデバイス3台を担当し、別のワーカーは空のスライスを引くかもしれません。パーティションキーが固まっていると、TotalSegments を上げても助けになりません — ホットなワーカーを待つアイドルなワーカーを増やすだけです。均等なキー分散こそが、ファンアウトを報われるものにします。
実行前に読み取りコストを見る
並列スキャンはスループットのイベントであって、ただ飯ではありません。正直な問いは「このテーブル全体を読むのに読み取りユニットがいくつかかるか?」です — そして DynoTable は、実テーブルに対するスキャンの計測された読み取りコストをセグメント単位で見せるので、夜間ジョブが請求で驚かせることがありません。
落とし穴と、やる価値がないとき
- スループットの崖。 高い
TotalSegmentsのスキャンは、数秒でテーブルの読み取りキャパシティ全体を消費し、ライブトラフィックを飢えさせうる。ユーザーにサービスするテーブルでは、Limitパラメータで各ワーカーをスロットルするか、ピーク外にスキャンする。(AWS: 並列スキャン) - それでもアクセスパターンには間違ったツール。 並列スキャンは意図的なフルテーブルジョブ向けです — エクスポート、バックフィル、マイグレーション。繰り返すクエリに答えるために手を伸ばしているなら、それはモデリングのシグナルです — GSIを追加してQueryにしましょう。
- PartiQL の
SELECT *は同じスキャンの変装。 逐次Scanにコンパイルされます。アイテムをまたぐ分析 —GROUP BY、JOIN、集計 — が本当に必要なときは、DynoTable の SQL Workbench が範囲を区切った結果セットに対してクライアント側でそれらを実行し、テーブルを叩きません。 - 強い整合性は請求を倍にする。
Scanはデフォルトで結果整合性の読み取りです。エクスポートでは、ある時点のスナップショットが本当に必要でない限りConsistentRead=falseのままにしましょう。
次のステップ
日々の読み取りが決してスキャンを必要としないようにキーをモデリングしましょう — シングルテーブル設計とQuery と Scanから始めます。フルテーブルジョブが本当に正しい判断のときは、DynoTable を試して自分のテーブルに対して並列スキャンを実行し、読み取りコストをリアルタイムで見ましょう。