Principiante6 min di lettura

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 #name per 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 Query che 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.

ProjectionExpressionCovering GSI
Taglia il payload
Taglia il costo di letturaNo — letto alla dimensione dell'indice
Storage extraNessunoUna seconda copia dei campi proiettati
Costo di scrittura extraNessunoLe scritture si propagano all'indice
Migliore perNascondere campi privati; piccole vittorieLetture 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 name o status nudo 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.


  1. 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/
  2. AWS DynamoDB Developer Guide, Reserved Words in DynamoDB. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html
  3. AWS DynamoDB Developer Guide, Attribute Projections (KEYS_ONLY / INCLUDE / ALL). https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html

Aggiornato