中級読了 3 分

DynamoDB のシングルトンアイテム

シングルトンアイテムとは、固定でハードコードされたキーを持つ単一の行で、アプリケーション全体の状態を 保持します — ユーザーごとや注文ごとに1レコードではなく、1つのレコード、それだけです。機能フラグ、 設定のブロブ、グローバルなキルスイッチ — リレーショナルアプリなら1行の設定テーブルに保持するようなものです。

SQL から来ると、id = 1 を持つ config テーブルと SELECT * FROM config に手を伸ばすでしょう。 DynamoDB では、ハードコードされたパーティションキーで同じことを行います — そして、そのキーを常に知っているので、 QueryScan ではなく GetItem で読み取ります。

DynamoDB のシングルトンアイテムとは何か?

シングルトンアイテムとは、固定でハードコードされたキーの下に格納された単一の DynamoDB の行で、ユーザーごとや 注文ごとに1レコードではなく、アプリケーション全体のグローバルな状態 — 機能フラグ、設定のブロブ、システム全体の バージョン — を保持します。キーを常に知っているので、GetItem で読み取り、と条件式で 更新します。

  • シングルトンは定数キーを持つ1つのアイテム。 ユーザー ID や注文 ID をテンプレートに埋め込む代わりに、 PK/SK(例: CONFIG#GLOBAL)をコードにハードコードします。
  • Scan ではなく GetItem で読む。 完全なキーを常に知っているので、ポイント読み取りは平坦で予測可能な コスト(小さなアイテムなら最大 1 RCU)です — フィルターも、テーブルの走査もありません。
  • 定義上、 すべてのリクエストが同じパーティションに触れうるので、キャッシュして アイテムを小さく保ちましょう。書き込みのボトルネックにしてはいけません。
  • 更新式と条件式で安全に変更する、アプリ内での読み取り-変更-書き込みではなく — 失われた更新の競合はそこに 潜んでいます。

パターンを見分ける

データがどの1つのエンティティにもスコープされていないとき、あなたはグローバルな状態を抱えています。 いくつかの兆候があります。

  • 全員に対して同じフラグ(signup_enabled = false)。
  • アプリが起動時に読み込む調整項目のブロブ(レート制限、デフォルトのクォータ)。
  • 行ごとではなく、システム全体のカウンターやバージョン番号。

ユーザー、テナント、注文にスコープされたものは、シングルトンではありません — それはそのエンティティの ID で キー付けされた通常のアイテムです。シングルトンは、他にどこにも住む場所のない、余った global なスライスです。

定数キーを与える

パターン全体は1つの決定にかかっています。キーはテンプレートではなくリテラルであることです。 オーバーロードされた単一テーブル内のグローバルな機能フラグアイテムには、固定のプレフィックスと固定の値を選びます。

PKSKattributes
SETTINGS#APPFLAGS#V1signup_enabled, maintenance_mode, ai_search_enabled

PK = "SETTINGS#APP"SK = "FLAGS#V1" はコードに焼き付けられています。ユーザー ID もテナント ID も ありません — アプリケーションは毎回、まさにこのアイテムを要求します。その予測可能性こそが要点です。既知の キーは GetItem であり、GetItem は DynamoDB が提供する最も安価で最も一貫した読み取りです。

V1 サフィックスは意図的です。後でフラグのスキーマの形が変わったら、稼働中のアイテムをその場で変更する 代わりに、FLAGS#V2 アイテムを書き込んで読み手を切り替えます。シングルトンキーにバージョンを付けることで、 きれいなマイグレーションの継ぎ目が手に入ります。

GetItem で読む

キーが完全にわかっているので、シングルトンに対して QueryScan もしません。Scan はテーブル全体を 読み取ってクライアント側でフィルタリングします — 古典的な Scan の落とし穴 — そして、直接アドレス指定できる1行を取得するには馬鹿げたほどの過剰さです。

SETTINGS#APP / FLAGS#V1 に対する GetItem は、単一の強い整合性または結果整合性のある読み取りで フラグを返します。AWS は 4 KB 以下のアイテムの GetItem を、結果整合性で 0.5 RCU、強い整合性で 1 RCU として 課金します(AWS の読み取り/書き込みキャパシティのドキュメント)。 シングルトンを小さく保てば、そのコストは永遠に横ばいのままです。

読み取りの経路はこれだけです。アプリが起動するかリクエストが到着し、固定キーを GetItem し、結果を キャッシュします。以下がその流れです。

はいいいえアプリ / リクエストGetItem PK=SETTINGS#APPSK=FLAGS#V1アイテムはあった?フラグを使い、ローカルにキャッシュ安全な既定値にフォールバック

固定キーは、グローバルな参照を、組み込みのデフォルト経路を持つ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#APPID でテンプレート化(USER#42
ちょうど1つユーザー / 注文 / テナントごとに1つ
典型的な読み取り既知のキーへの GetItemエンティティによる GetItemQuery
スコープアプリケーション全体単一のエンティティ
用途グローバルなフラグ、設定、システムバージョンプロフィール、注文、ID ごとのあらゆるもの

同じ種類の 2つ のシングルトンが欲しくなっているなら、あなたが持っているのはシングルトンではありません — それはエンティティごとのアイテムであり、キー付けし忘れたのがそのエンティティ(たとえばテナントごとの設定)です。

落とし穴と次のステップ

  • それを Scan しない。 キーはわかっています。直接アドレス指定しましょう。
  • それを読み取り-変更-書き込みしない。 更新式と条件式を使いましょう。
  • 黙って消えるに任せない。 キャッシュミス時は安全な値にデフォルトしましょう。
  • 高頻度の書き込みで過負荷にしない。 それはシャーディングされたカウンターの仕事です。

シングルトンは シングルテーブル設計 の中に心地よく収まります — エンティティの行の隣にある、固定キーを持つもう1つのアイテムコレクションにすぎません。

DynoTable を試して テーブルを閲覧し、固定キーでシングルトンの行を見つけ、書き込み経路を 構築しながらフラグを手で編集してみましょう。

更新日