Projection expressions en DynamoDB
Una projection expression es el SELECT col1, col2 de DynamoDB: una lista
separada por comas de nombres de que le dice a GetItem,
Query o Scan que devuelva solo esos atributos en lugar del item entero.
¿Reducen las projection expressions de DynamoDB el coste de lectura?
No. Un ProjectionExpression recorta el payload de la respuesta, no la capacidad de lectura que te facturan. DynamoDB lee el item completo del storage, mide el sobre su tamaño en disco y luego tira los atributos que no nombraste de camino a la salida. Para cortar de verdad el coste de lectura, usa un covering en su lugar.
- Recorta el payload, no el coste de lectura. DynamoDB lee (y factura) el
item completo del storage, y luego tira los atributos que no nombraste de
camino a la salida.
ProjectionExpressiones una optimización de red, no de capacidad. - Es cómo traes un subconjunto público. Nombra los pocos atributos que un caller puede ver; el resto nunca sale de la tabla.
- Usa placeholders
#namepara cualquier cosa que pueda ser reservada. Los nombres planos de atributo en la expresión chocan con las ~570 palabras reservadas de DynamoDB y fallan la petición. - Para ahorros reales de lectura, usa un covering index en su lugar. Un que proyecta solo las columnas que necesitas se lee a su propio tamaño (más pequeño).
Lo que ahorra de verdad
Si vienes de SQL, asumirías que SELECT a, b escanea menos que SELECT *. En
DynamoDB esa intuición está mal. La
unidad de capacidad de una lectura se
calcula a partir del tamaño del item en disco, redondeado al siguiente 4 KB
— antes de que se aplique la proyección. AWS es explícito: un
ProjectionExpression no cambia la capacidad de lectura que consume una
petición.1
Así que una proyección te ahorra dos cosas, ambas reales pero ambas aguas abajo de la lectura:
- Bytes por el cable. Un item de 6 KB devuelto como dos atributos pequeños
es una respuesta diminuta. En un
Queryque devuelve cientos de items, eso suma rápido. - Trabajo en el cliente. Menos que deserializar, menos que guardar en memoria, menos que filtrar a un log o a una respuesta de API por accidente.
Lo que no ahorra es el RCU. Ese es el pie: la gente llega a una proyección para cortar la factura, no ve cambio y concluye que DynamoDB está roto. No lo está — mediste la palanca equivocada.
Proyecta un perfil de usuario público
Digamos que llevas un directorio de usuarios. Cada perfil es un item, claveado para que puedas traer a una persona por handle:
PK = "PROFILE#ada" (partition key)
SK = "PROFILE#ada" (sort key — single-item collection)
El item está gordo. Lleva la cara pública de la cuenta más un montón de atributos privados y operacionales:
{
"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 tarjeta de perfil público necesita tres campos. Traer el item entero
significa que emailAddress, lastLoginIp e internalRiskScore viajan a un
contexto que nunca debería verlos. Nombra solo el subconjunto público:
GetItem PK = "PROFILE#ada" SK = "PROFILE#ada"
ProjectionExpression: displayName, avatarUrl, bio
La respuesta lleva tres atributos. Los privados se quedan en la tabla — no filtrados por tu app después de llegar, sino nunca serializados en la respuesta. Esa es la victoria de seguridad, y es la que es difícil de deshacer una vez que un secreto ya ha cruzado una frontera.
Puedes montar y copiar esta petición exacta — names, placeholders y la llamada
al SDK — en el
DynamoDB Expression Builder, que emite el
ProjectionExpression y el map de ExpressionAttributeNames por ti.
Añade o quita campos en el preset de abajo para ver cómo cambia el
ProjectionExpression — solo vuelven los atributos listados:
Escapa palabras reservadas con placeholders #
Una proyección limpia explota con palabras reservadas. DynamoDB reserva una
lista larga — name, status, comment, size, timestamp y cientos más.2
Si un atributo que estás proyectando es uno de ellos, el nombre crudo en la
expresión se rechaza.
Supongamos que el perfil también tiene un atributo status ("active",
"suspended"). Esto falla:
ProjectionExpression displayName, status
status está reservado. El arreglo es un expression attribute name — un
placeholder con prefijo # mapeado al nombre real:
ProjectionExpression displayName, #s
ExpressionAttributeNames { "#s": "status" }
El mismo mecanismo llega a atributos anidados. Para sacar un solo campo de un map, o un elemento de una list, usa sintaxis de document-path — y pon placeholder a cada segmento, porque cualquiera puede estar reservado:
ProjectionExpression #addr.#city, tags[0]
ExpressionAttributeNames { "#addr": "address", "#city": "city" }
Una regla práctica: pon placeholder a todo. Nunca tienes que recordar sobre
cuál de las ~570 palabras reservadas estás, y la expresión se lee igual de
cualquier forma. Y si prefieres saber qué nombres son de verdad el problema,
pégalos en el
checker de reserved-words — marca las
colisiones y emite el map de alias de ExpressionAttributeNames.
Cuándo un covering index gana a una proyección
Si de verdad necesitas cortar el coste de lectura — no solo el payload — la
palanca es un Global Secondary Index que proyecta solo los atributos que
lees. Un GSI es una copia aparte de los datos; eliges KEYS_ONLY, INCLUDE o
ALL para su proyección.3 Un índice KEYS_ONLY o un INCLUDE
estrecho es físicamente más pequeño por item, así que un Query contra él se
mide a ese tamaño más pequeño.
Eso es un covering index: la query se responde del todo desde el índice, sin viaje de vuelta a la tabla base. Úsalo cuando un access pattern de lectura caliente solo necesita unos pocos atributos de items grandes.
ProjectionExpression | Covering GSI | |
|---|---|---|
| Corta payload | Sí | Sí |
| Corta coste de lectura | No | Sí — lee al tamaño del índice |
| Storage extra | Ninguno | Una segunda copia de los campos proyectados |
| Coste de escritura extra | Ninguno | Las escrituras se propagan al índice |
| Mejor para | Ocultar campos privados; wins pequeños | Lecturas calientes de unos pocos campos de items grandes |
El índice te cuesta storage y capacidad de escritura para ahorrar capacidad de
lectura. Merece la pena para una lectura frecuente de un trozo fino de un item
pesado; no merece la pena para afeitar un GetItem one-off. Ver
GSI vs LSI para elegir el tipo de índice, y
cuándo una lectura de GSI puede estar stale
antes de poner uno en el hot path.
Escollos y próximos pasos
- No esperes una factura más pequeña. Una proyección sola nunca cambia el RCU. Si el número no se movió, ese es el comportamiento documentado, no un bug.
- Pon placeholder a las palabras reservadas. Un
nameostatuspelado en la expresión falla la petición — mapealo con#. - Incluye siempre los atributos de clave — añaden payload negligible y te dejan paginar o re-fetch el item.
- Llega a un covering index solo cuando un patrón caliente lee unos pocos campos de items grandes; pesa primero el coste de escritura/storage.
Construye el ProjectionExpression y su map de attribute-name en el
Expression Builder, y
prueba DynoTable para lanzar estas proyecciones contra tus
propias tablas y ver cómo se encoge la respuesta.
- Guía para desarrolladores de AWS DynamoDB, Using projection expressions in DynamoDB: la capacidad de lectura se basa en el tamaño del elemento antes de aplicar cualquier
ProjectionExpression. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html ↩ - Guía para desarrolladores de AWS DynamoDB, Reserved Words in DynamoDB. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html ↩
- Guía para desarrolladores de AWS DynamoDB, Attribute Projections (
KEYS_ONLY/INCLUDE/ALL). https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html ↩