Projection expression in DynamoDB
Una projection expression è il SELECT col1, col2 di DynamoDB: un elenco di
nomi di attributo separati da virgola che dice a GetItem, Query o Scan di
restituire solo quegli attributi invece dell'intero item.
Le projection expression di DynamoDB riducono il costo di lettura?
No. Una ProjectionExpression riduce il payload della risposta, non la capacità di lettura che ti viene fatturata. DynamoDB legge l'item completo dallo storage, calcola le sulla sua dimensione su disco, poi scarta in uscita gli attributi che non hai nominato. Per tagliare davvero il costo di lettura, usa invece un di copertura.
- Taglia il payload, non il costo di lettura. DynamoDB legge (e fattura)
l'item completo dallo storage, poi scarta in uscita gli attributi che non hai
nominato. La
ProjectionExpressionè un'ottimizzazione di rete, non di capacità. - È così che recuperi un sottoinsieme pubblico. Nomina i pochi attributi che un chiamante può vedere; il resto non lascia mai la tabella.
- Usa placeholder
#nameper qualsiasi cosa possa essere riservata. Nomi di attributo nudi nell'espressione si scontrano con le ~570 parole riservate di DynamoDB e fanno fallire la richiesta. - Per un vero risparmio di lettura, usa invece un covering index. Una GSI che proietta solo le colonne di cui hai bisogno è letta alla sua dimensione (più piccola).
Cosa risparmia davvero
Venendo da SQL, daresti per scontato che SELECT a, b scansioni meno di
SELECT *. In DynamoDB quell'intuizione è sbagliata. La
unità di capacità per una lettura è
calcolata dalla dimensione dell'item su disco, arrotondata ai 4 KB successivi
— prima che la proiezione sia applicata. AWS è esplicito: una
ProjectionExpression non cambia la capacità di lettura che una richiesta
consuma.1
Quindi una proiezione ti risparmia due cose, entrambe reali ma entrambe a valle della lettura:
- Byte sul filo. Un item da 6 KB restituito come due piccoli attributi è una
risposta minuscola. Su una
Queryche restituisce centinaia di item, ciò si accumula in fretta. - Lavoro lato client. Meno da deserializzare, meno da tenere in memoria, meno da far trapelare per sbaglio in un log o una risposta API.
Ciò che non risparmia è l'RCU. Quello è il footgun: la gente ricorre a una proiezione per tagliare la bolletta, non vede alcun cambiamento e conclude che DynamoDB è rotto. Non lo è — hai misurato la leva sbagliata.
Proietta un profilo utente pubblico
Diciamo che gestisci una directory di utenti. Ogni profilo è un item, con chiave così da poter recuperare una persona tramite handle:
PK = "PROFILE#ada" (partition key)
SK = "PROFILE#ada" (sort key — collection a singolo item)
L'item è grasso. Porta la faccia pubblica dell'account più un mucchio di attributi privati e operativi:
{
"PK": "PROFILE#ada",
"SK": "PROFILE#ada",
"displayName": "Ada L.",
"avatarUrl": "https://cdn.example.com/u/ada.png",
"bio": "Builds things.",
"emailAddress": "ada@example.com",
"passwordResetToken": "…",
"billingCustomerId": "cus_…",
"lastLoginIp": "…",
"internalRiskScore": 0.02
}Una scheda profilo pubblica ha bisogno di tre campi. Recuperare l'intero item
significa che emailAddress, lastLoginIp e internalRiskScore viaggiano verso
un contesto che non dovrebbe mai vederli. Nomina solo il sottoinsieme pubblico:
GetItem PK = "PROFILE#ada" SK = "PROFILE#ada"
ProjectionExpression: displayName, avatarUrl, bio
La risposta porta tre attributi. Quelli privati restano nella tabella — non filtrati dalla tua app dopo l'arrivo, ma mai serializzati nella risposta del tutto. È la vittoria di sicurezza, ed è quella difficile da annullare una volta che un segreto ha già attraversato un confine.
Puoi assemblare e copiare questa richiesta esatta — nomi, placeholder e la
chiamata SDK — nel DynamoDB Expression Builder,
che emette per te la ProjectionExpression e la mappa ExpressionAttributeNames.
Fai l'escape delle parole riservate con i placeholder #
Ecco dove una proiezione pulita esplode. DynamoDB riserva una lunga lista di
parole — name, status, comment, size, timestamp e centinaia
altre.2 Se un attributo che stai proiettando è una di esse, il nome
grezzo nell'espressione è rifiutato.
Supponi che il profilo abbia anche un attributo status ("active",
"suspended"). Questo fallisce:
ProjectionExpression displayName, status
status è riservata. La soluzione è un expression attribute name — un
placeholder prefissato con # mappato al nome reale:
ProjectionExpression displayName, #s
ExpressionAttributeNames { "#s": "status" }
Lo stesso meccanismo arriva agli attributi annidati. Per estrarre un singolo campo da una mappa, o un elemento di una lista, usa la sintassi document-path — e metti un placeholder su ogni segmento, dato che uno qualsiasi di essi potrebbe essere riservato:
ProjectionExpression #addr.#city, tags[0]
ExpressionAttributeNames { "#addr": "address", "#city": "city" }
Una regola pratica: metti placeholder su tutto. Non devi mai ricordare su quale delle ~570 parole riservate stai poggiando, e l'espressione si legge uguale in entrambi i modi.
Quando un covering index batte una proiezione
Se hai davvero bisogno di tagliare il costo di lettura — non solo il payload — la
leva è una Global Secondary Index che proietta solo gli attributi che leggi.
Una GSI è una copia separata dei dati; scegli KEYS_ONLY, INCLUDE o ALL per
la sua proiezione.3 Un indice KEYS_ONLY o con INCLUDE ristretto è
fisicamente più piccolo per item, quindi una Query contro di esso è
contabilizzata a quella dimensione minore.
Quello è un covering index: la query riceve risposta interamente dall'indice, nessun viaggio di ritorno alla tabella base. Usalo quando un pattern di lettura caldo ha bisogno solo di pochi attributi da item grandi.
ProjectionExpression | Covering GSI | |
|---|---|---|
| Taglia il payload | Sì | Sì |
| Taglia il costo di lettura | No | Sì — letto alla dimensione dell'indice |
| Storage extra | Nessuno | Una seconda copia dei campi proiettati |
| Costo di scrittura extra | Nessuno | Le scritture si propagano all'indice |
| Migliore per | Nascondere campi privati; piccole vittorie | Letture calde di pochi campi da item grandi |
Il compromesso è onesto: l'indice ti costa storage e capacità di scrittura per
risparmiare capacità di lettura. Ne vale la pena per una lettura frequente di una
fetta sottile da un item pesante; non per limare un GetItem una tantum. Vedi
GSI vs LSI per scegliere il tipo di indice, e
quando una lettura GSI può essere obsoleta
prima di metterne uno sul percorso caldo.
Trappole e prossimi passi
- Non aspettarti una bolletta più piccola. Una proiezione da sola non cambia mai gli RCU. Se il numero non si è mosso, è il comportamento documentato, non un bug.
- Metti placeholder sulle parole riservate. Un
nameostatusnudo nell'espressione fa fallire la richiesta — mappalo con#. - Proiettare gli attributi chiave è gratis e spesso utile — DynamoDB li restituisce a basso costo, e ti permettono di paginare o ri-recuperare.
- Ricorri a un covering index solo quando un pattern caldo legge pochi campi da item grandi; pesa prima il costo di scrittura/storage.
Costruisci la ProjectionExpression e la sua mappa di nomi attributo
nell'Expression Builder, e
prova DynoTable per eseguire queste proiezioni contro le tue tabelle
e guardare la risposta rimpicciolirsi.
- AWS DynamoDB Developer Guide, Working with Read and Write Operations — la capacità di lettura è basata sulla dimensione dell'item prima che venga applicata qualsiasi
ProjectionExpression. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ ↩ - AWS DynamoDB Developer Guide, Reserved Words in DynamoDB. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html ↩
- AWS DynamoDB Developer Guide, Attribute Projections (
KEYS_ONLY/INCLUDE/ALL). https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html ↩