DynamoDB のシングルトンアイテム
シングルトンアイテムとは、固定でハードコードされたキーを持つ単一の行で、アプリケーション全体の状態を 保持します — ユーザーごとや注文ごとに1レコードではなく、1つのレコード、それだけです。機能フラグ、 設定のブロブ、グローバルなキルスイッチ — リレーショナルアプリなら1行の設定テーブルに保持するようなものです。
SQL から来ると、id = 1 を持つ config テーブルと SELECT * FROM config に手を伸ばすでしょう。
DynamoDB では、ハードコードされたパーティションキーで同じことを行います — そして、そのキーを常に知っているので、
Query や Scan ではなく GetItem で読み取ります。
DynamoDB のシングルトンアイテムとは何か?
シングルトンアイテムとは、固定でハードコードされたキーの下に格納された単一の DynamoDB の行で、ユーザーごとや
注文ごとに1レコードではなく、アプリケーション全体のグローバルな状態 — 機能フラグ、設定のブロブ、システム全体の
バージョン — を保持します。キーを常に知っているので、GetItem で読み取り、と条件式で
更新します。
- シングルトンは定数キーを持つ1つのアイテム。 ユーザー ID や注文 ID をテンプレートに埋め込む代わりに、
PK/SK(例:CONFIG#GLOBAL)をコードにハードコードします。 ScanではなくGetItemで読む。 完全なキーを常に知っているので、ポイント読み取りは平坦で予測可能な コスト(小さなアイテムなら最大 1 RCU)です — フィルターも、テーブルの走査もありません。- 定義上、。 すべてのリクエストが同じパーティションに触れうるので、キャッシュして アイテムを小さく保ちましょう。書き込みのボトルネックにしてはいけません。
- 更新式と条件式で安全に変更する、アプリ内での読み取り-変更-書き込みではなく — 失われた更新の競合はそこに 潜んでいます。
パターンを見分ける
データがどの1つのエンティティにもスコープされていないとき、あなたはグローバルな状態を抱えています。 いくつかの兆候があります。
- 全員に対して同じフラグ(
signup_enabled = false)。 - アプリが起動時に読み込む調整項目のブロブ(レート制限、デフォルトのクォータ)。
- 行ごとではなく、システム全体のカウンターやバージョン番号。
ユーザー、テナント、注文にスコープされたものは、シングルトンではありません — それはそのエンティティの ID で キー付けされた通常のアイテムです。シングルトンは、他にどこにも住む場所のない、余った global なスライスです。
定数キーを与える
パターン全体は1つの決定にかかっています。キーはテンプレートではなくリテラルであることです。 オーバーロードされた単一テーブル内のグローバルな機能フラグアイテムには、固定のプレフィックスと固定の値を選びます。
| PK | SK | attributes |
|---|---|---|
| SETTINGS#APP | FLAGS#V1 | signup_enabled, maintenance_mode, ai_search_enabled |
PK = "SETTINGS#APP" と SK = "FLAGS#V1" はコードに焼き付けられています。ユーザー ID もテナント ID も
ありません — アプリケーションは毎回、まさにこのアイテムを要求します。その予測可能性こそが要点です。既知の
キーは GetItem であり、GetItem は DynamoDB が提供する最も安価で最も一貫した読み取りです。
V1 サフィックスは意図的です。後でフラグのスキーマの形が変わったら、稼働中のアイテムをその場で変更する
代わりに、FLAGS#V2 アイテムを書き込んで読み手を切り替えます。シングルトンキーにバージョンを付けることで、
きれいなマイグレーションの継ぎ目が手に入ります。
GetItem で読む
キーが完全にわかっているので、シングルトンに対して Query も Scan もしません。Scan はテーブル全体を
読み取ってクライアント側でフィルタリングします — 古典的な Scan の落とし穴 —
そして、直接アドレス指定できる1行を取得するには馬鹿げたほどの過剰さです。
SETTINGS#APP / FLAGS#V1 に対する GetItem は、単一の強い整合性または結果整合性のある読み取りで
フラグを返します。AWS は 4 KB 以下のアイテムの GetItem を、結果整合性で 0.5 RCU、強い整合性で 1 RCU として
課金します(AWS の読み取り/書き込みキャパシティのドキュメント)。
シングルトンを小さく保てば、そのコストは永遠に横ばいのままです。
読み取りの経路はこれだけです。アプリが起動するかリクエストが到着し、固定キーを GetItem し、結果を
キャッシュします。以下がその流れです。
固定キーは、グローバルな参照を、組み込みのデフォルト経路を持つ1回のポイント読み取りに変えます。
no の分岐に注意してください。シングルトンが見つからなくても、決してクラッシュすべきではありません。
初回デプロイの隙間や不正なキーが開いた状態ではなく閉じた状態で失敗するよう、安全な値
(機能はオフ、メンテナンスはオン)にデフォルトします。
競合なしに更新する
罠は、アプリ内での読み取り-変更-書き込みでシングルトンを更新することです。フラグを GetItem し、メモリ内で
1つを反転させ、それから全体を PutItem で書き戻す。2つの同時書き込み手が両方とも古いアイテムを読み、
2つ目の Put が1つ目の変更を上書きします。失われた更新です。
2つの DynamoDB の機能が、アプリ側のロックなしにこの競合を消し去ります。
- は1つの属性をサーバー側で変更し、残りには触れません。アイテム全体を
Putし直す必要はありません。 - は、アイテムがまだ期待どおりに見える場合にのみ書き込みを成功させるので、
古い書き込みは
ConditionalCheckFailedExceptionで拒否されます (AWS の条件式のドキュメント)。
1つのフラグを反転させるには、SET でその属性だけを対象にし、同時書き込み手が互いを踏みつけられないように
バージョンの繰り上げでガードします。
# UpdateItem
Key PK=SETTINGS#APP SK=FLAGS#V1
UpdateExpression SET signup_enabled = :on, schema_version = :next
ConditionExpression schema_version = :current
2つの書き込み手が競合すると、2つ目の schema_version = :current チェックが失敗し、新しい値に対して
再試行します。名前、値、そしてこの正確な式の形は、コードに組み込む前に
DynamoDB 式ビルダー でひな型化できます。演算子をより深く見るには、
更新式のイディオム ガイドを参照してください。
ホットキーに気を配る
シングルトンは、構造上、ホットキーです — アプリのあらゆる部分が同じパーティションを読みうるからです。 キャッシュすれば読み取りには問題ありませんが、それがこのパターンの唯一の本当のリスクです。
- 積極的にキャッシュする。 フラグはリクエストごとではなく、プロセスごと(または N 秒ごと)に一度読みます。 シングルトンの値はメモ化するのに最も安価なものです。
- 書き込みのホットスポットにしない。 管理者が1日に数回切り替えるフラグは何でもありません。リクエストごとに インクリメントするシングルトンは、パーティションのスループットのボトルネックです — それはシングルトンの問題では なく、カウンターの問題です。
- 小さく保つ。 読み取りコストはアイテムサイズに応じて 4 KB ブロック単位でスケールします。肥大した設定 ブロブは、必要以上にすべての起動を高コストにします。
高い書き込み負荷のグローバルなカウンターが本当に必要なら、シングルトンは間違った形です — N 個のアイテムに シャーディングして、読み取り時に合計します。それは別のパターンです。
シングルトン対エンティティごとのアイテム
線引きは単に データが何にスコープされているか です。
| シングルトンアイテム | エンティティごとのアイテム | |
|---|---|---|
| キー | ハードコードされた定数(SETTINGS#APP) | ID でテンプレート化(USER#42) |
| 数 | ちょうど1つ | ユーザー / 注文 / テナントごとに1つ |
| 典型的な読み取り | 既知のキーへの GetItem | エンティティによる GetItem か Query |
| スコープ | アプリケーション全体 | 単一のエンティティ |
| 用途 | グローバルなフラグ、設定、システムバージョン | プロフィール、注文、ID ごとのあらゆるもの |
同じ種類の 2つ のシングルトンが欲しくなっているなら、あなたが持っているのはシングルトンではありません — それはエンティティごとのアイテムであり、キー付けし忘れたのがそのエンティティ(たとえばテナントごとの設定)です。
落とし穴と次のステップ
- それを
Scanしない。 キーはわかっています。直接アドレス指定しましょう。 - それを読み取り-変更-書き込みしない。 更新式と条件式を使いましょう。
- 黙って消えるに任せない。 キャッシュミス時は安全な値にデフォルトしましょう。
- 高頻度の書き込みで過負荷にしない。 それはシャーディングされたカウンターの仕事です。
シングルトンは シングルテーブル設計 の中に心地よく収まります — エンティティの行の隣にある、固定キーを持つもう1つのアイテムコレクションにすぎません。
DynoTable を試して テーブルを閲覧し、固定キーでシングルトンの行を見つけ、書き込み経路を 構築しながらフラグを手で編集してみましょう。