DynamoDB のシングルトンアイテム
シングルトンアイテム とは、固定でハードコードされたキーを持ち、アプリケーション全体の状態を保持する単一の行です — ユーザーごと・注文ごとに1レコードではなく、ただ1つ のレコード、それだけ。フィーチャーフラグ、設定の塊、グローバルなキルスイッチ — リレーショナルアプリなら1行の設定テーブルに入れておくようなものです。
SQL から来た人なら、id = 1 の config テーブルと SELECT * FROM config に手を伸ばすでしょう。DynamoDB では同じことをハードコードされたパーティションキーで行います — そしてそのキーが常に分かっているので、Query や Scan ではなく GetItem で読み取ります。
DynamoDB のシングルトンアイテムとは何ですか?
シングルトンアイテムとは、固定のハードコードされたキーの下に格納される DynamoDB の単一行で、ユーザーや注文ごとのレコードではなく、アプリケーション全体のグローバルな状態(フィーチャーフラグ、設定の塊、システム全体のバージョンなど)を保持します。キーが常に分かっているため、GetItem で読み取り、とで更新します。
- シングルトンは定数キーを持つ1つのアイテム。 ユーザー ID や注文 ID をテンプレートに埋め込む代わりに、
PK/SKをコードにハードコードする(例:CONFIG#GLOBAL)。 ScanではなくGetItemで読む。 完全なキーが常に分かっているので、ポイント読み取りは1回の一貫した予測可能な RCU で済む — フィルタもテーブル走査も無い。- 定義上ホットキーになる。 あらゆるリクエストが同じパーティションに触れうるので、キャッシュしてアイテムを小さく保つこと。書き込みのボトルネックにしてはいけない。
- 更新は安全に。 アプリ側の read-modify-write ではなく、update + 条件式で行う — lost-update の競合はそこに潜む。
パターンを見分ける
データがどのエンティティにもスコープされていないとき、それはグローバルな状態です。いくつかの兆候があります。
- 全員に共通のフラグ(
signup_enabled = false)。 - アプリが起動時に読み込む調整値の塊(レート制限、デフォルトのクォータ)。
- 行ごとではなく、システム全体のカウンターやバージョン番号。
ユーザー、テナント、注文にスコープされたものは シングルトンではありません — それはそのエンティティの ID をキーとする普通のアイテムです。シングルトンは、ほかに居場所のない、取り残されたグローバルな断片です。
定数キーを与える
このパターン全体は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 は、強い整合性または結果整合性の1回の読み取りでフラグを返します。AWS は ≤ 4 KB のアイテムの GetItem を、結果整合性なら 0.5 RCU、強い整合性なら 1 RCU として課金します(AWS の読み取り/書き込みキャパシティのドキュメント)。シングルトンを小さく保てば、そのコストは永遠にフラットなままです。
読み取りの経路はこれだけです。アプリが起動するかリクエストが到着し、固定キーを GetItem し、結果をキャッシュする。流れは次のとおりです。
固定キーは、グローバルなルックアップを、組み込みのデフォルト経路を備えた1回のポイント読み取りに変えます。
no の分岐に注目してください。シングルトンが欠けていても、決してクラッシュさせてはいけません。安全な値(機能は オフ、メンテナンスは オン)をデフォルトにして、初回デプロイの隙やキーの誤りが open ではなく closed に倒れるようにします。
競合なしに更新する
罠は、シングルトンをアプリ側の read-modify-write で更新することです。フラグを GetItem し、メモリ上で1つを反転し、PutItem で全体を書き戻す。2つの並行ライターが両方とも古いアイテムを読み、2番目の Put が1番目の変更を上書きします。Lost update です。
DynamoDB の2つの機能が、アプリ側のロックなしでこの競合を排除します。
- update 式 は、サーバー側で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 式ビルダーで組み上げられます。演算子のより深い解説は、update 式のイディオムガイドを参照してください。
ホットキーに気を配る
シングルトンは構造上 ホットキー です — アプリのあらゆる部分が同じパーティションを読みうるからです。キャッシュすれば読み取りには問題ありませんが、それがこのパターンの唯一の本当のリスクです。
- 積極的にキャッシュする。 フラグはリクエストごとではなく、プロセスごと(または N 秒ごと)に一度読む。シングルトンの値はメモ化するのに最も安いものです。
- 書き込みのホットスポットにしない。 管理者が日に数回切り替えるフラグなど何でもありません。リクエストごとにインクリメントするシングルトンは、パーティションのスループットのボトルネックです — それはシングルトンの問題ではなくカウンターの問題です。
- 小さく保つ。 読み取りコストはアイテムサイズに 4 KB ブロック単位で比例します。肥大化した設定の塊は、起動のたびに必要以上にコストをかけます。
本当に高書き込みのグローバルカウンターが必要なら、シングルトンは間違った形です — N 個のアイテムに分散させて読み取り時に合計します。それは別のパターンです。
シングルトン vs エンティティ単位アイテム
境界は単純に データが何にスコープされているか です。
| シングルトンアイテム | エンティティ単位アイテム | |
|---|---|---|
| キー | ハードコードされた定数(SETTINGS#APP) | ID でテンプレート化(USER#42) |
| 個数 | ちょうど1つ | ユーザー / 注文 / テナントごとに1つ |
| 典型的な読み取り | 既知のキーへの GetItem | エンティティ単位の GetItem または Query |
| スコープ | アプリケーション全体 | 単一のエンティティ |
| 用途 | グローバルフラグ、設定、システムバージョン | プロフィール、注文、ID 単位のあらゆるもの |
同じ種類の 2つ のシングルトンが欲しくなったら、それはシングルトンではありません — それはエンティティ単位アイテムで、キーにし忘れたエンティティ(たとえばテナント単位の設定)があるのです。
落とし穴と次のステップ
Scanで取りに行かない。 キーは分かっている。直接アドレス指定する。- read-modify-write しない。 update + 条件式を使う。
- 静かに欠落させない。 キャッシュミス時は安全な値をデフォルトにする。
- 高頻度の書き込みでオーバーロードしない。 それは分散カウンターの仕事だ。
シングルトンはシングルテーブル設計の中に心地よく収まります — エンティティの行と並ぶ、固定キーを持つもう1つのアイテムコレクションにすぎません。
DynoTable を試してテーブルを閲覧し、固定キーでシングルトンの行を見つけ、書き込み経路を構築する間に手作業でフラグを編集してみましょう。