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
#namepara 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
Queryque 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.
ProjectionExpression | GSI de cobertura | |
|---|---|---|
| Corta payload | Sim | Sim |
| Corta custo de leitura | Não | Sim — lido no tamanho do índice |
| Armazenamento extra | Nenhum | Uma segunda cópia dos campos projetados |
| Custo de escrita extra | Nenhum | Escritas propagam para o índice |
| Melhor para | Esconder campos privados; ganhos pequenos | Leituras 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
nameoustatuscru 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.
- AWS DynamoDB Developer Guide, Working with Read and Write Operations — a capacidade de leitura é baseada no tamanho do item antes de qualquer
ProjectionExpressionser aplicada. 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 ↩