Principiante6 min de lectura

Projection expressions de DynamoDB

Una projection expression es el SELECT col1, col2 de DynamoDB: una lista de nombres de atributo separada por comas que le dice a GetItem, Query o Scan que devuelva solo esos atributos en lugar del elemento completo.

¿Las projection expressions de DynamoDB reducen el coste de lectura?

No. Una ProjectionExpression recorta el payload de la respuesta, no la capacidad de lectura que se te factura. DynamoDB lee el elemento completo desde el almacenamiento, mide la sobre su tamaño en disco, y luego descarta los atributos que no nombraste a la salida. Para reducir realmente el coste de lectura, usa un de cobertura en su lugar.

  • Recorta el payload, no el coste de lectura. DynamoDB lee (y factura) el elemento completo desde el almacenamiento, luego descarta los atributos que no nombraste a la salida. ProjectionExpression es una optimización de red, no de capacidad.
  • Es como traes un subconjunto público. Nombra los pocos atributos que un llamante tiene permitido ver; el resto nunca sale de la tabla.
  • Usa marcadores #name para cualquier cosa que pudiera estar reservada. Los nombres de atributo planos en la expresión chocan con las ~570 palabras reservadas de DynamoDB y hacen fallar la petición.
  • Para un ahorro de lectura real, usa un índice de cobertura en su lugar. Un GSI que proyecta solo las columnas que necesitas se lee a su propio tamaño (más pequeño).

Qué ahorra realmente

Viniendo 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 elemento en disco, redondeado hacia arriba al siguiente bloque de 4 KB — antes de aplicar la proyección. AWS es explícito: una 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 posteriores a la lectura:

  • Bytes por la red. Un elemento de 6 KB devuelto como dos atributos pequeños es una respuesta diminuta. En una Query que devuelve cientos de elementos, eso suma rápido.
  • Trabajo del lado del cliente. Menos que deserializar, menos que mantener en memoria, menos que filtrar por accidente hacia un log o una respuesta de API.

Lo que no ahorra es la RCU. Ese es el tiro al pie: la gente recurre a una proyección para recortar su factura, no ve ningún cambio y concluye que DynamoDB está roto. No lo está — mediste la palanca equivocada.

Proyecta un perfil de usuario público

Digamos que gestionas un directorio de usuarios. Cada perfil es un elemento, indexado para que puedas traer a una persona por su nombre:

PK = "PROFILE#ada"      (partition key)
SK = "PROFILE#ada"      (sort key — single-item collection)

El elemento es gordo. Lleva la cara pública de la cuenta más un montón de atributos privados y operativos:

{
  "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ública necesita tres campos. Traer el elemento 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 tras la llegada, sino nunca serializados en la respuesta en absoluto. Esa es la victoria de seguridad, y es la que cuesta deshacer una vez que un secreto ya ha cruzado un límite.

Puedes ensamblar y copiar esta petición exacta — nombres, marcadores y la llamada al SDK — en el Constructor de expresiones de DynamoDB, que emite por ti la ProjectionExpression y el mapa ExpressionAttributeNames.

Escapa las palabras reservadas con marcadores #

Aquí es donde una proyección limpia estalla. DynamoDB reserva una larga lista de palabras — name, status, comment, size, timestamp y cientos más.2 Si un atributo que estás proyectando es una de ellas, el nombre en bruto en la expresión se rechaza.

Supón 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 marcador con prefijo # mapeado al nombre real:

ProjectionExpression       displayName, #s
ExpressionAttributeNames   { "#s": "status" }

El mismo mecanismo alcanza atributos anidados. Para sacar un único campo de un map, o un elemento de una list, usa la sintaxis de ruta de documento — y pon un marcador en cada segmento, ya que cualquiera de ellos podría estar reservado:

ProjectionExpression       #addr.#city, tags[0]
ExpressionAttributeNames   { "#addr": "address", "#city": "city" }

Una regla práctica: pon marcadores en todo. Nunca tienes que recordar cuál de las ~570 palabras reservadas estás pisando, y la expresión se lee igual de cualquier manera.

Cuándo un índice de cobertura gana a una proyección

Si genuinamente necesitas recortar el coste de lectura — no solo el payload — la palanca es un índice secundario global que proyecta solo los atributos que lees. Un GSI es una copia separada 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 elemento, así que una Query contra él se mide a ese tamaño más pequeño.

Eso es un índice de cobertura: la consulta se responde enteramente desde el índice, sin viaje de vuelta a la tabla base. Úsalo cuando un patrón de lectura caliente solo necesita unos pocos atributos de elementos grandes.

ProjectionExpressionGSI de cobertura
Recorta payload
Recorta coste de lecturaNo — leído al tamaño del índice
Almacenamiento extraNingunoUna segunda copia de los campos proyectados
Coste de escritura extraNingunoLas escrituras se propagan al índice
Mejor paraOcultar campos privados; pequeñas victoriasLecturas calientes de unos pocos campos de elementos grandes

El compromiso es honesto: el índice te cuesta almacenamiento y capacidad de escritura para ahorrar capacidad de lectura. Vale la pena para una lectura frecuente de una porción fina de un elemento pesado; no vale la pena para recortar un GetItem puntual. Consulta GSI vs LSI para elegir el tipo de índice, y cuándo una lectura de GSI puede estar obsoleta antes de ponerlo en la ruta caliente.

Trampas y próximos pasos

  • No esperes una factura más pequeña. Una proyección por sí sola nunca cambia la RCU. Si el número no se movió, ese es el comportamiento documentado, no un bug.
  • Pon marcadores en las palabras reservadas. Un name o status pelado en la expresión hace fallar la petición — mapéalo con #.
  • Proyectar atributos de clave es gratis y a menudo útil — DynamoDB los devuelve barato, y te permiten paginar o volver a traer.
  • Recurre a un índice de cobertura solo cuando un patrón caliente lee unos pocos campos de elementos grandes; sopesa primero el coste de escritura/almacenamiento.

Construye la ProjectionExpression y su mapa de nombres-de-atributo en el Constructor de expresiones, y prueba DynoTable para ejecutar estas proyecciones contra tus propias tablas y ver encogerse la respuesta.


  1. AWS DynamoDB Developer Guide, Working with Read and Write Operations — 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/
  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

Actualizado