Amplify Gen 2 Lambda バッチで GSI を使ったら古いデータを読んでしまった話
はじめに
Amplify Gen 2 の Scheduling Function(Lambda + EventBridge)でバッチ処理を実装していたとき、
DynamoDB の Global Secondary Index(GSI) が “結果整合性しか保証されない” 仕様に足をすくわれました。
この記事では 「未処理 → 要処理 → 処理済み」 の 3 ステータスを持つテーブルを例に、
- どんなバグが起きたのか
- なぜ GSI だと起こり得るのか
- サービス稼働中でも取れたシンプルな対策
をまとめます。
背景
Amplify Gen 2 環境でシステムを開発している際に、バッチ処理のステータスを管理する Check テーブルを作成しました。
テーブルの status カラムは次の 3 段階です。
- 未処理
- 要処理
- 処理済み
バッチは Lambda(Amplify Gen 2 の Scheduling Function)として定期実行し、ステータスが 未処理 と 要処理 のレコードに対してそれぞれ異なる処理を行います。
起きていたこと
レコードのステータスを 未処理 → 要処理 に更新した直後に Lambda を実行すると、
本来 要処理 であるはずのレコードが 未処理 と判定される事象が発生しました。
原因 ―― GSI は常に “結果整合性”
Amplify Gen 2 で DynamoDB を扱う場合、セカンダリインデックスには LSI(ローカル)と GSI(グローバル)の 2 種類があります。
今回のクエリは GSI 経由 で行っており、ここが落とし穴でした。
これは DynamoDB の
「グローバルセカンダリインデックスはベーステーブルに対して非同期に複製されるため、
書き込み内容がインデックスへ伝搬するまでわずかな遅延が発生する」
という特性によるものです。
ポイントは次のとおりです。
インデックス種別 | 読み取りのデフォルト | 強整合性読取 (ConsistentRead=true ) |
---|---|---|
GSI | 結果整合性のみ | ❌(指定不可) |
LSI | 結果整合性 | ✅(オプションで指定可) |
つまり GSI では「常に最新の値を返す」ことが保証されません。
書き込み直後〜数秒程度は古い状態が返る可能性があります。
今回の誤判定は、まさにこの伝搬遅延が原因でした。
補足
-
LSI でも
ConsistentRead=true
を付けなければ結果整合性 である点に注意してください。 - 既存テーブルに LSI を後付けすることはできない(テーブル作成時のみ定義可能) ため、
大規模リファクタリングなしには GSI → LSI への置き換えは困難でした。
対策 ―― 更新から 1 分以内のレコードを処理対象から除外
GSI の伝搬遅延は一般的に 数百ミリ秒〜数秒 とされていますが、100 % 保証はありません。
ビジネス要件上「多少遅れて処理されても問題ない」ケースだったため、
更新タイムスタンプと現在時刻の差が 1 分未満 のレコードはスキップする実装に変更しました。
const now = Date.now();
const items = queryCheckTableViaGSI(...);
const targets = items.filter(item => now - item.updatedAt >= 60 * 1000);
これにより、伝搬が完全に終わるまで十分なバッファを確保しつつ、誤判定を回避できました
(遅延を許容できないユースケースでは別解が必要です)。
追加で検討できる選択肢
-
DynamoDB Streams + SQS/Lambda
ステータス書き込みをトリガにキューへ流し、キューを基準に処理する構成にすれば整合性遅延を意識せずに済みます。 -
トランザクション API
TransactWriteItems
を利用して「ステータスが未処理のものだけ更新する」等の条件付き書き込みを行い、二重処理を防ぐ。 -
スリープ/リトライ
書き込み直後に同一 Lambda で読み取るワークフローなら、最短数秒のスリープや指数バックオフを入れてリトライする手もあります。
まとめ
- GSI で読み取るデータは「結果整合性」かつ「非同期伝搬」 のため、書き込み直後は古い値が返ることがある。
-
LSI は
ConsistentRead=true
を指定すれば強整合性 で読めるが、テーブル作成後に追加できない制約 がある。 - 伝搬遅延を考慮したバッファ(今回の例では 1 分)や、Streams/キュー/トランザクション API などを組み合わせ、要件に応じた整合性担保策を取る必要がある。
同じようなバッチを組む方の参考になれば幸いです 🙌
Discussion