DynamoDB の強い整合性のある読み取りと結果整合性のある読み取り
アイテムを更新し、すぐにそれを読み戻すと、古い値 が返ってきます。書き込みは 成功しています — 少し経つと同じ読み取りが新しい値を返します。何も壊れていません。 DynamoDB のデフォルトである 結果整合性のある読み取り に当たったのであり、 リクエストごとにそれをオプトアウトできます。
これは DynamoDB があなたに直接手渡す数少ない正しさのつまみのひとつで、実際の代償が ついて回ります。これを正しく使うには、各モードが何を保証し、いくらかかり、そして どこで強い読み取りがそもそも使えないのかを知る必要があります。
DynamoDB の強い整合性のある読み取りと結果整合性のある読み取りの違いは何ですか?
結果整合性のある読み取り(デフォルト)はいずれかのレプリカが処理するため、書き込み直後に一瞬だけ古いデータを返すことがありますが、コストは半分で済みます。強い整合性のある読み取りはリクエストごとに ConsistentRead=true でオプトインし、パーティションのリーダーへルーティングされて、コミット済みのすべての書き込みを常に反映します — ただし読み取りキャパシティは 2 倍です。
- 結果整合性(デフォルト) — 書き込み直後に一瞬だけ古いデータを返すことがあります。 最も安い読み取りモードです。
- 強い整合性 — 読み取り前にコミットされたすべての書き込みを常に反映します。
ConsistentRead=trueでリクエストごとにオプトインします。 - 強い読み取りは結果整合性の 2 倍のコスト。 強い整合性のある読み取りは、同じデータに ついて結果整合性のある読み取りの 2 倍の読み取りキャパシティを消費します。
- どこでも使えるわけではない。 強い読み取りはベーステーブルと ローカルセカンダリ インデックス で使えます。グローバルセカンダリインデックスは結果整合性のみ で、 オプトインはできません。
- デフォルトは結果整合性に。 自分が今書いたばかりのデータを読み、一瞬の古さが 問題になる場合にのみ強い読み取りに手を伸ばしましょう。
問題:最新の書き込みが見えない読み取り
ユーザーアカウントを運用しているとします。ユーザーが通知用メールを変更し、アプリが 更新を書き込み、確認画面がすぐにプロフィールを読み直して新しいアドレスを表示します。 デフォルトの読み取りモードでは、その読み直しがまだ変更を受け取っていないレプリカに 当たることがあります — その結果、ユーザーは 古い メールを見て、保存が失敗したと 思い込みます。
その窓は小さく(通常は 1 秒を大きく下回ります)、自然に閉じます。しかし read-after-write の確認には「だいたい正しい」では不十分です。それこそが強い整合性が 存在する理由のケースです。
なぜ結果整合性が起きるのか
DynamoDB はすべてのパーティションを、別々のアベイラビリティゾーンにまたがる 3 つのストレージノード — 1 つのプライマリと 2 つのレプリカ — に保存します。書き込みは プライマリと 1 つのレプリカに着地した時点で承認され、その後 3 つ目のノードへ 非同期に 伝播します。
読み取りは負荷を分散するため、3 つのノードの いずれか が処理しうります。結果整合性の ある読み取りは、まだ最新の書き込みを受け取っていないノードに当たることがあります — その ため少し古い値を返します。強い整合性のある 読み取りはパーティションのリーダーへ ルーティングされ、リーダーは常に最新のコミット済みデータを保持しているため、古い結果を 返すことはありません。
その複製遅延こそが違いのすべてです。それは 2 倍のコスト の理由でもあります。強い 読み取りは結果整合性の読み取りのようにレプリカ間で負荷分散できないため、DynamoDB は キャパシティを 2 倍で課金します。
コストを具体的に
読み取りは 読み取りキャパシティユニット(RCU) で計測され、各 RCU は最大 4 KB を
カバーします。1 RCU で、4 KB のアイテムの 強い整合性のある読み取り 1 回 または
結果整合性のある読み取り 2 回 が買えます。つまりホットな読み取りパスで
ConsistentRead=true を切り替えると、その読み取りコストは倍になります — トラフィックの
多いエンドポイントでは目に見えて分かる行項目です。
強い読み取りをデフォルトにする前に、自分のアイテムサイズとリクエストレートで差を DynamoDB 料金計算ツール でモデル化しましょう — 全面的に 2 倍を払う価値があることはめったにありません。
強い読み取りが使える場所(と使えない場所)
| 読み取り対象 | 強い整合性は可能か? |
|---|---|
| ベーステーブル | 可能 — ConsistentRead=true でオプトイン |
| ローカルセカンダリインデックス(LSI) | 可能 — ベーステーブルと同じオプトイン |
| グローバルセカンダリインデックス(GSI) | 不可 — 結果整合性のみ、上書き不可 |
GSI はベーステーブルから非同期にレプリケートされる独自のデータコピーを保持するため、 強い読み取りを提供することは決してできません。あるアクセスパターンが本当に read-after-write を必要としていて、それを GSI で提供する予定だったなら、それは代わりに ベーステーブルまたは LSI で提供すべきというサインです。
落とし穴と次のステップ
- 強い読み取りをデフォルトにしない。 ほとんどの読み取りは 1 秒未満の古さの窓を 許容できます。どこでも 2 倍を払うのは無駄遣いです。
- GSI に read-after-write を期待しない。 設計上、結果整合性です — なぜ GSI は結果整合性なのか を参照。
- トランザクションは強く読む。
TransactGetItemsは常に強い整合性です — DynamoDB トランザクション を参照。 - 整合性はキャパシティと相互作用する。 2 倍の乗数は オンデマンドとプロビジョンド のコスト計画に 直結します。
API 呼び出しを書かずに DynamoDB のテーブルとインデックスを調べたいですか? DynoTable をダウンロード してデータを直接調べましょう。
RCU の実例比較
ベーステーブル上の同じ 6 KB のアイテムを 2 通りで読む場合:
| モード | 4 KB ブロック | 消費 RCU | 使いどころ |
|---|---|---|---|
| 強い整合性 | 2(6 KB は切り上げ) | 2 RCU | 自分の書き込み後の確認画面 |
| 結果整合性 | 2 | 1 RCU | ダッシュボード、一覧、分析 |
このような読み取りが秒間 1,000 件あると、強いモードは結果整合性モードのおよそ 2 倍の
オンデマンド読み取り費用になります — ホットなパスを全面的に ConsistentRead=true へ
切り替える前に、料金計算ツール で差分を
モデル化しましょう。
BatchGetItem の整合性
BatchGetItem では、テーブルごとのエントリが ConsistentRead を個別に設定できます。
ユーザープロフィール(強い整合性)と関連する設定(結果整合性)を読み込む
ダッシュボードは、1 回のバッチ呼び出しの中でフラグを混在させられます — もちろん
テーブルごとの強い読み取りの可否のルール(GSI では強い読み取り不可)には従います。
アプリケーションコードでの read-after-write
プロフィール更新の確認画面のパターン:
- 新しいメールで
UpdateItemする。 - ベーステーブルに対して
ConsistentRead: trueを付けて即座にGetItemする。
ステップ 2 は結果整合性の読み取りの 2 倍の RCU を消費しますが、確認画面が書き込みと 一致することを保証します。1 秒未満の遅れを許容できるバックグラウンドの集計処理では、 強い読み取りは省きましょう。
DynoTable のデフォルト
DynoTable での探索的な読み取りは、詳細設定でより強いセマンティクスにオプトインしない 限り、結果整合性のあるベーステーブルのクエリを使います — ほとんどのダッシュボード的な 用途に合わせた挙動です。書き込みをステージングした後、アイテムエディタの更新は、 一般的な経路では整合性の切り替えを別途行わなくても、成功したレスポンスのコミット済みの 値を表示します。
read-after-write の保証が必要なサービスに SDK コードをコピーするときは、
クエリビルダー を使って ConsistentRead を明示的に
設定したサンプルの読み取りを出力しましょう。
グローバルテーブルに関する注意
グローバルテーブルはリージョンをまたいで非同期にレプリケートします。強い整合性が
適用されるのは 1 つのリージョンのレプリカ内 であり、グローバルにではありません。
us-east-1 での書き込みが eu-west-1 で即座に強い整合性で読めるわけではありません —
リージョン横断の UX はそれを前提に計画しましょう。レプリケーション遅延の目安は
グローバルテーブル を参照してください。