Iniciante6 min de leitura

Projection Expressions no DynamoDB

Uma projection expression é o SELECT col1, col2 do DynamoDB: uma lista de nomes de atributo separada por vírgulas que diz a GetItem, Query ou Scan para retornar só esses atributos em vez do item inteiro.

As projection expressions do DynamoDB reduzem o custo de leitura?

Não. Uma ProjectionExpression apara o payload da resposta, não a capacidade de leitura cobrada. O DynamoDB lê o item completo do armazenamento, mede a com base no tamanho em disco, e só então descarta os atributos que você não nomeou na saída. Para de fato reduzir o custo de leitura, use um de cobertura.

  • Apara o payload, não o custo de leitura. O DynamoDB lê (e cobra) o item completo do armazenamento, depois descarta os atributos que você não nomeou na saída. ProjectionExpression é uma otimização de rede, não de capacidade.
  • É como você busca um subconjunto público. Nomeie os poucos atributos que um chamador tem permissão de ver; o resto nunca sai da tabela.
  • Use placeholders #name para qualquer coisa que possa ser reservada. Nomes de atributo crus na expressão colidem com as ~570 palavras reservadas do DynamoDB e fazem a requisição falhar.
  • Para economia real de leitura, use um índice de cobertura. Um GSI que projeta só as colunas de que você precisa é lido no seu próprio tamanho (menor).

O que ela de fato economiza

Vindo do SQL, você assumiria que SELECT a, b escaneia menos que SELECT *. No DynamoDB essa intuição está errada. A unidade de capacidade de uma leitura é calculada a partir do tamanho do item em disco, arredondado para o próximo 4 KB — antes da projeção ser aplicada. A AWS é explícita: uma ProjectionExpression não muda a capacidade de leitura que uma requisição consome.1

Então uma projeção te economiza duas coisas, ambas reais mas ambas a jusante da leitura:

  • Bytes pela rede. Um item de 6 KB retornado como dois atributos pequenos é uma resposta minúscula. Em um Query que retorna centenas de itens, isso soma rápido.
  • Trabalho no lado do cliente. Menos para desserializar, menos para manter em memória, menos para vazar em um log ou numa resposta de API por acidente.

O que ela não economiza é o RCU. Essa é a cilada: as pessoas recorrem a uma projeção para cortar a conta, não veem mudança, e concluem que o DynamoDB está quebrado. Não está — você mediu a alavanca errada.

Projete um perfil de usuário público

Digamos que você opere um diretório de usuários. Cada perfil é um item, chaveado para que você busque uma pessoa por handle:

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

O item é gordo. Ele carrega a face pública da conta mais uma pilha de atributos privados e operacionais:

{
  "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
}

Um cartão de perfil público precisa de três campos. Buscar o item inteiro significa que emailAddress, lastLoginIp e internalRiskScore viajam para um contexto que nunca deveria vê-los. Nomeie só o subconjunto público:

GetItem  PK = "PROFILE#ada"  SK = "PROFILE#ada"
ProjectionExpression: displayName, avatarUrl, bio

A resposta carrega três atributos. Os privados ficam na tabela — não filtrados pelo seu app depois da chegada, mas nunca serializados na resposta de jeito nenhum. Esse é o ganho de segurança, e é o que é difícil desfazer uma vez que um segredo já cruzou uma fronteira.

Você pode montar e copiar esta requisição exata — nomes, placeholders, e a chamada de SDK — no DynamoDB Expression Builder, que emite a ProjectionExpression e o mapa ExpressionAttributeNames para você.

Escape palavras reservadas com placeholders #

Aqui é onde uma projeção limpa explode. O DynamoDB reserva uma longa lista de palavras — name, status, comment, size, timestamp, e centenas mais.2 Se um atributo que você está projetando é uma delas, o nome cru na expressão é rejeitado.

Suponha que o perfil também tenha um atributo status ("active", "suspended"). Isto falha:

ProjectionExpression   displayName, status

status é reservado. A correção é um expression attribute name — um placeholder prefixado com # mapeado para o nome real:

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

O mesmo mecanismo alcança atributos aninhados. Para puxar um único campo de um map, ou um elemento de uma lista, use a sintaxe de document-path — e coloque placeholder em cada segmento, já que qualquer um deles pode ser reservado:

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

Uma regra prática: coloque placeholder em tudo. Você nunca tem que lembrar em qual das ~570 palavras reservadas está pisando, e a expressão se lê igual de qualquer jeito.

Quando um índice de cobertura bate uma projeção

Se você genuinamente precisa cortar o custo de leitura — não só o payload — a alavanca é um Índice Secundário Global que projeta só os atributos que você lê. Um GSI é uma cópia separada dos dados; você escolhe KEYS_ONLY, INCLUDE ou ALL para a sua projeção.3 Um índice KEYS_ONLY ou INCLUDE estreito é fisicamente menor por item, então um Query contra ele é medido nesse tamanho menor.

Esse é um índice de cobertura: a consulta é respondida inteiramente a partir do índice, sem ida de volta à tabela base. Use-o quando um padrão de leitura quente só precisa de alguns poucos atributos de itens grandes.

ProjectionExpressionGSI de cobertura
Corta payloadSimSim
Corta custo de leituraNãoSim — lido no tamanho do índice
Armazenamento extraNenhumUma segunda cópia dos campos projetados
Custo de escrita extraNenhumEscritas propagam para o índice
Melhor paraEsconder campos privados; ganhos pequenosLeituras quentes de poucos campos de itens grandes

O trade-off é honesto: o índice te custa armazenamento e capacidade de escrita para economizar capacidade de leitura. Vale a pena para uma leitura frequente de uma fatia fina de um item pesado; não vale a pena para economizar num GetItem avulso. Veja GSI vs LSI para escolher o tipo de índice, e quando uma leitura de GSI pode estar obsoleta antes de colocar um no caminho quente.

Armadilhas e próximos passos

  • Não espere uma conta menor. Uma projeção sozinha nunca muda o RCU. Se o número não mexeu, esse é o comportamento documentado, não um bug.
  • Coloque placeholder em palavras reservadas. Um name ou status cru na expressão faz a requisição falhar — mapeie com #.
  • Projetar atributos de chave é de graça e muitas vezes útil — o DynamoDB os retorna barato, e eles te deixam paginar ou re-buscar.
  • Recorra a um índice de cobertura só quando um padrão quente lê poucos campos de itens grandes; pese o custo de escrita/armazenamento primeiro.

Construa a ProjectionExpression e seu mapa de nome de atributo no Expression Builder, e experimente o DynoTable para rodar essas projeções contra suas próprias tabelas e ver a resposta encolher.


  1. AWS DynamoDB Developer Guide, Working with Read and Write Operations — a capacidade de leitura é baseada no tamanho do item antes de qualquer ProjectionExpression ser aplicada. 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

Atualizado