Item singleton in DynamoDB
Un item singleton è una singola riga con una chiave fissa e hardcoded che contiene stato per l'intera applicazione — non un record per utente o per ordine, ma un record, punto. Feature flag, un blob di config, un kill-switch globale: il tipo di cosa che un'app relazionale terrebbe in una tabella di impostazioni a una riga.
Venendo da SQL, ricorreresti a una tabella config con id = 1 e un SELECT * FROM config. In DynamoDB fai la stessa cosa con una partition key hardcoded —
e poiché conosci sempre quella chiave, la leggi con un GetItem, non con una
Queryo unoScan.
Cos'è un item singleton in DynamoDB?
Un item singleton è una singola riga DynamoDB memorizzata sotto una chiave fissa e hardcoded che contiene stato globale per l'intera applicazione — feature flag, un blob di config, una versione di sistema — anziché un record per utente o per ordine. Poiché conosci sempre la chiave, la leggi con un GetItem e la aggiorni con più condition expression.
- Un singleton è un item con una chiave costante. Hardcodi la
PK/SKnel tuo codice (es.CONFIG#GLOBAL) invece di interpolare l'id di un utente o un ordine. - Leggilo con
GetItem, mai conScan. Conosci sempre la chiave completa, quindi una lettura puntuale è una RCU coerente e prevedibile — niente filtro, niente scansione della tabella. - È una hot key per definizione. Ogni richiesta può toccare la stessa partizione, quindi mettila in cache e mantieni l'item piccolo; non farne un collo di bottiglia in scrittura.
- Modificalo in sicurezza con update + condition expression, non con read-modify-write nella tua app — è lì che vive la race del lost update.
Riconosci il pattern
Hai stato globale quando i dati non sono associati a nessuna singola entità. Alcuni segnali:
- Un flag uguale per tutti (
signup_enabled = false). - Un blob di parametri che la tua app legge all'avvio (rate limit, quote di default).
- Un contatore o numero di versione per l'intero sistema, non per riga.
Tutto ciò che è associato a un utente, tenant o ordine non è un singleton — quello è un item ordinario con chiave basata sull'id di quell'entità. Il singleton è la fetta globale residua che non ha altro posto dove vivere.
Dagli una chiave costante
L'intero pattern si regge su una decisione: la chiave è un letterale, non un template. Per un item di feature flag globali in una single table sovraccaricata, scegli un prefisso fisso e un valore fisso:
| PK | SK | attributes |
|---|---|---|
| SETTINGS#APP | FLAGS#V1 | signup_enabled, maintenance_mode, ai_search_enabled |
PK = "SETTINGS#APP" e SK = "FLAGS#V1" sono incorporati nel codice. Non c'è
id utente, né id tenant — l'applicazione chiede esattamente questo item ogni
volta. Quella prevedibilità è il punto: una chiave nota è un GetItem, e un
GetItem è la lettura più economica e coerente che DynamoDB offra.
Il suffisso V1 è deliberato. Se lo schema del flag cambia forma in seguito,
scrivi un item FLAGS#V2 e sposti i reader su di esso, invece di mutare quello
live sul posto. Versionare la chiave del singleton ti compra una giuntura di
migrazione pulita.
Leggilo con GetItem
Poiché la chiave è completamente nota, non usi mai Query né Scan per un
singleton. Uno Scan legge l'intera tabella e filtra lato client — il classico
footgun dello Scan — ed è un eccesso assurdo per
recuperare una riga che puoi indirizzare direttamente.
Un GetItem contro SETTINGS#APP / FLAGS#V1 restituisce i flag in una singola
lettura fortemente o eventualmente coerente. AWS fattura un GetItem di un item
≤ 4 KB come 0,5 RCU eventualmente coerente o 1 RCU fortemente coerente
(documentazione AWS sulla capacità di lettura/scrittura).
Mantieni il singleton piccolo e quel costo resta piatto per sempre.
Il percorso di lettura è semplicemente: l'app si avvia o arriva una richiesta,
fai GetItem sulla chiave fissa, metti in cache il risultato. Ecco il flusso.
La chiave fissa trasforma una ricerca globale in una lettura puntuale con un percorso di default integrato.
Nota il ramo no: un singleton mancante non dovrebbe mai mandarti in crash.
Imposta come default il valore sicuro (feature off, manutenzione on) così
che una mancanza al primo deploy o una chiave sbagliata falliscano in modo chiuso,
non aperto.
Aggiornalo senza una race
La trappola è aggiornare un singleton con read-modify-write nella tua app: fai
GetItem sui flag, ne ribalti uno in memoria, poi rimetti l'intera cosa con un
PutItem. Due writer concorrenti leggono entrambi il vecchio item e il secondo
Put calpesta la modifica del primo. Lost update.
Due funzionalità di DynamoDB uccidono la race senza locking lato app:
- Le update expression mutano un attributo lato server, lasciando il resto
intatto. Non serve rifare il
Putdell'intero item. - Le condition expression fanno riuscire la scrittura solo se l'item appare
ancora come ti aspetti, così una scrittura stantia viene rifiutata con
ConditionalCheckFailedException(documentazione AWS sulle condition expression).
Per ribaltare un flag, mira solo a quell'attributo con un SET e proteggilo con
un bump di versione così che i writer concorrenti non possano calpestarsi a
vicenda:
# UpdateItem
Key PK=SETTINGS#APP SK=FLAGS#V1
UpdateExpression SET signup_enabled = :on, schema_version = :next
ConditionExpression schema_version = :current
Se due writer corrono, il controllo schema_version = :current del secondo
fallisce ed esso riprova contro il valore fresco. Puoi impalcare i nomi, i valori
e questa esatta forma di espressione nell'Expression Builder per
DynamoDB prima di cablarla nel codice. Per
uno sguardo più approfondito agli operatori, vedi la guida sugli
idiomi delle update expression.
Attento alla hot key
Un singleton è, per costruzione, una hot key — ogni parte della tua app può leggere la stessa partizione. Va bene per le letture se metti in cache, ma è l'unico rischio reale del pattern.
- Metti in cache in modo aggressivo. Leggi i flag una volta per processo (o ogni N secondi), non a ogni richiesta. Il valore del singleton è la cosa più economica da memoizzare.
- Non renderlo un hot spot in scrittura. Un flag commutato da un admin qualche volta al giorno non è nulla. Un singleton che incrementi a ogni richiesta è un collo di bottiglia di throughput di partizione — quello è un problema da contatore, non da singleton.
- Mantienilo piccolo. Il costo di lettura scala con la dimensione dell'item a blocchi di 4 KB. Un blob di config gonfio rende ogni avvio più costoso del necessario.
Se davvero ti serve un contatore globale ad alta scrittura, il singleton è la forma sbagliata — fallo a shard su N item e somma in lettura. Quello è un pattern diverso.
Singleton vs item per-entità
La linea è semplicemente a cosa sono associati i dati.
| Item singleton | Item per-entità | |
|---|---|---|
| Chiave | Costante hardcoded (SETTINGS#APP) | Template con un id (USER#42) |
| Quanti | Esattamente uno | Uno per utente / ordine / tenant |
| Lettura tipica | GetItem sulla chiave nota | GetItem o Query per entità |
| Ambito | Intera applicazione | Una singola entità |
| Usalo per | Flag globali, config, versione di sistema | Profili, ordini, qualsiasi cosa per-id |
Se ti ritrovi a volere due singleton dello stesso tipo, non hai un singleton — hai un item per-entità e l'entità è la cosa che hai dimenticato di mettere in chiave (config per-tenant, ad esempio).
Insidie e prossimi passi
- Non fare
Scanper esso. Conosci la chiave; indirizzala direttamente. - Non fare read-modify-write. Usa update + condition expression.
- Non lasciarlo sparire silenziosamente. Imposta come default il valore sicuro in caso di cache miss.
- Non sovraccaricarlo con scritture ad alta frequenza. Quello è un lavoro da contatore a shard.
Il singleton vive comodamente dentro un single-table design — è solo un'altra item collection con una chiave fissa accanto alle tue righe di entità.
Prova DynoTable per sfogliare la tua tabella, trovare la riga singleton dalla sua chiave fissa e modificare i flag a mano mentre costruisci il percorso di scrittura.