LSI vs GSI no DynamoDB: Quando Usar Cada Tipo de Índice Secundário

Você modelou sua tabela DynamoDB com uma partition key e sort key perfeitas para o acesso principal — e então o produto pediu uma nova tela que precisa filtrar por um atributo completamente diferente. É nesse momento que a escolha entre LSI e GSI deixa de ser teórica e vira um problema real de design.

TL;DR — LSI vs GSI no DynamoDB

Característica LSI (Local Secondary Index) GSI (Global Secondary Index)
Partition Key Mesma da tabela base Qualquer atributo
Sort Key Atributo diferente da tabela base Qualquer atributo (opcional)
Quando criar Somente na criação da tabela A qualquer momento
Consistência de leitura Strongly consistent ou eventually consistent Somente eventually consistent
Throughput Compartilha com a tabela base Capacidade provisionada própria
Limite por tabela Até 5 Até 20 (padrão, ajustável via suporte)
Projeção de atributos KEYS_ONLY, INCLUDE, ALL KEYS_ONLY, INCLUDE, ALL

Como os Índices Secundários Funcionam no DynamoDB

O DynamoDB organiza dados em partições físicas baseadas na partition key. Quando você faz uma query, o serviço roteia a requisição diretamente para a partição correta — operação O(1). O problema começa quando você precisa filtrar por um atributo que não é a partition key: sem índice, a única opção é um Scan completo da tabela, que lê todos os itens independente do tamanho.

Índices secundários resolvem isso criando uma estrutura de dados separada, mantida automaticamente pelo DynamoDB, com uma chave primária diferente. Cada operação de escrita na tabela base propaga automaticamente para todos os índices associados — isso tem custo de WCU e latência adicional, e é importante considerar antes de criar índices desnecessários.

graph TD TB["Tabela Base
PK: customerId | SK: orderId"] W["Operação de Escrita"] LSI["LSI: OrdersByDate
PK: customerId | SK: orderDate"] GSI["GSI: OrdersByStatus
PK: status | SK: orderDate"] W --> TB TB -->|"Propagação síncrona"| LSI TB -->|"Propagação assíncrona"| GSI Q1["Query por cliente + data"] Q2["Query por status + data"] Q1 --> LSI Q2 --> GSI style LSI fill:#4a90d9,color:#fff style GSI fill:#e8a838,color:#fff style TB fill:#2d6a4f,color:#fff
  1. Tabela Base: Dados primários organizados pela partition key e sort key originais.
  2. LSI: Estrutura alternativa dentro da mesma partição lógica — mesma partition key, sort key diferente.
  3. GSI: Estrutura completamente independente com partition key própria — pode acessar itens de qualquer partição da tabela base.
  4. Propagação de escrita: Toda escrita na tabela base é propagada assincronamente para os GSIs e sincronamente para os LSIs.

LSI — Local Secondary Index: Flexibilidade Dentro da Partição

O LSI mantém a mesma partition key da tabela base e permite uma sort key alternativa. 'Local' significa que o índice é local a uma partição — você só consegue consultar itens que compartilham a mesma partition key.

A restrição mais crítica: LSIs só podem ser criados no momento da criação da tabela. Se a tabela já existe em produção, não há como adicionar um LSI sem recriar a tabela. Isso já pegou muita gente de surpresa depois do deploy.

Quando o LSI faz sentido

Use LSI quando suas queries sempre filtram pela mesma partition key da tabela, mas precisam ordenar ou filtrar por atributos diferentes. Exemplo clássico: uma tabela de pedidos com partition key customerId e sort key orderId. Se você precisa consultar pedidos de um cliente ordenados por orderDate ou filtrados por status, um LSI com sort key orderDate resolve sem precisar de uma nova partition key.

LSI também suporta leituras strongly consistent — diferente do GSI. Para casos onde consistência imediata é obrigatória dentro de uma partição, o LSI é a única opção via índice secundário.

Criando um LSI

aws dynamodb create-table \
  --table-name Orders \
  --attribute-definitions \
    AttributeName=customerId,AttributeType=S \
    AttributeName=orderId,AttributeType=S \
    AttributeName=orderDate,AttributeType=S \
  --key-schema \
    AttributeName=customerId,KeyType=HASH \
    AttributeName=orderId,KeyType=RANGE \
  --local-secondary-indexes '[{
    "IndexName": "OrdersByDate",
    "KeySchema": [
      {"AttributeName": "customerId", "KeyType": "HASH"},
      {"AttributeName": "orderDate", "KeyType": "RANGE"}
    ],
    "Projection": {
      "ProjectionType": "INCLUDE",
      "NonKeyAttributes": ["status", "totalAmount"]
    }
  }]' \
  --billing-mode PAY_PER_REQUEST \
  --region us-east-1

Consultando via LSI

aws dynamodb query \
  --table-name Orders \
  --index-name OrdersByDate \
  --key-condition-expression 'customerId = :cid AND orderDate BETWEEN :start AND :end' \
  --expression-attribute-values '{
    ":cid": {"S": "customer-123"},
    ":start": {"S": "2024-01-01"},
    ":end": {"S": "2024-12-31"}
  }' \
  --region us-east-1

GSI — Global Secondary Index: Acesso por Qualquer Atributo

O GSI permite definir uma partition key completamente diferente da tabela base. 'Global' porque uma query no GSI pode retornar itens de qualquer partição da tabela original. Isso abre padrões de acesso que seriam impossíveis com LSI.

GSIs podem ser criados e removidos a qualquer momento, mesmo em tabelas com dados em produção. O DynamoDB popula o índice em background — durante esse processo, o índice fica em estado CREATING e não está disponível para queries.

Throughput independente e o risco de throttling

GSIs têm capacidade de leitura e escrita provisionada separada da tabela base (ou seguem o modo on-demand independentemente). O ponto que causa problemas em produção: se o GSI não tiver WCU suficiente para absorver as escritas propagadas da tabela base, o DynamoDB pode throttle as escritas na tabela base — não apenas no índice. Esse comportamento assimétrico pega engenheiros desprevenidos.

Pense no GSI como uma tabela espelho com chave diferente. Ela precisa de capacidade própria para receber as cópias dos dados. Se o espelho não consegue acompanhar o ritmo, o original também para.

Criando um GSI em tabela existente

aws dynamodb update-table \
  --table-name Orders \
  --attribute-definitions \
    AttributeName=status,AttributeType=S \
    AttributeName=orderDate,AttributeType=S \
  --global-secondary-index-updates '[{
    "Create": {
      "IndexName": "OrdersByStatus",
      "KeySchema": [
        {"AttributeName": "status", "KeyType": "HASH"},
        {"AttributeName": "orderDate", "KeyType": "RANGE"}
      ],
      "Projection": {
        "ProjectionType": "INCLUDE",
        "NonKeyAttributes": ["customerId", "totalAmount"]
      },
      "ProvisionedThroughput": {
        "ReadCapacityUnits": 5,
        "WriteCapacityUnits": 5
      }
    }
  }]' \
  --region us-east-1

Nota: se a tabela usa billing mode PAY_PER_REQUEST, omita o campo ProvisionedThroughput no GSI.

Verificando o status de criação do GSI

aws dynamodb describe-table \
  --table-name Orders \
  --query 'Table.GlobalSecondaryIndexes[*].{Nome:IndexName,Status:IndexStatus}' \
  --region us-east-1

Consultando via GSI

aws dynamodb query \
  --table-name Orders \
  --index-name OrdersByStatus \
  --key-condition-expression '#s = :status AND orderDate > :cutoff' \
  --expression-attribute-names '{"#s": "status"}' \
  --expression-attribute-values '{
    ":status": {"S": "PENDING"},
    ":cutoff": {"S": "2024-06-01"}
  }' \
  --region us-east-1

Fluxo de Decisão: LSI ou GSI no DynamoDB?

graph TD START(["Preciso de um novo padrão de acesso"]) Q1{"A tabela já existe em produção?"} Q2{"O padrão filtra pela mesma partition key?"} Q3{"Precisa de leitura strongly consistent?"} Q4{"A nova chave cruza partições diferentes?"} LSI["Use LSI
(criar nova tabela)"] GSI_ONLY["Use GSI"] GSI_REC["Use GSI
(recomendado)"] BOTH["LSI + GSI
(tabela nova)"] START --> Q1 Q1 -->|"Sim"| GSI_ONLY Q1 -->|"Não"| Q2 Q2 -->|"Não"| Q4 Q2 -->|"Sim"| Q3 Q3 -->|"Sim"| LSI Q3 -->|"Não"| Q4 Q4 -->|"Sim"| GSI_REC Q4 -->|"Não, mesma partição"| BOTH style LSI fill:#4a90d9,color:#fff style GSI_ONLY fill:#e8a838,color:#fff style GSI_REC fill:#e8a838,color:#fff style BOTH fill:#6a4c93,color:#fff
  1. Se a tabela já existe em produção, LSI não é uma opção — vá direto para GSI.
  2. Se o padrão de acesso sempre filtra pela mesma partition key da tabela, LSI é candidato natural.
  3. Se você precisa de leitura strongly consistent no índice, apenas o LSI oferece essa garantia.
  4. Se o padrão de acesso cruza partições ou usa um atributo completamente diferente como chave, GSI é a única solução.
  5. Para tabelas novas com ambos os requisitos, é possível criar LSI e GSI simultaneamente.

Diagnóstico Real: O Caso do GSI Throttling a Tabela Base

Um sistema de e-commerce tinha uma tabela Orders com GSI em status. Em promoções, o volume de pedidos disparava e começavam a aparecer erros ProvisionedThroughputExceededException nas escritas — mas o CloudWatch mostrava a tabela base com capacidade sobrando.

A hipótese inicial foi hot partition na tabela base. Errada. O atributo status tem cardinalidade baixíssima — em promoção, 80% dos pedidos ficam com status PROCESSING, criando uma hot partition no GSI. Com o GSI throttled, o DynamoDB começou a rejeitar escritas na tabela base para proteger a consistência do índice.

O diagnóstico correto veio ao verificar as métricas separadas do GSI:

aws cloudwatch get-metric-statistics \
  --namespace AWS/DynamoDB \
  --metric-name WriteThrottleEvents \
  --dimensions \
    Name=TableName,Value=Orders \
    Name=GlobalSecondaryIndexName,Value=OrdersByStatus \
  --start-time 2024-01-15T18:00:00Z \
  --end-time 2024-01-15T20:00:00Z \
  --period 300 \
  --statistics Sum \
  --region us-east-1

A correção envolveu duas frentes: aumentar a WCU do GSI para absorver os picos, e revisar o design — status com baixa cardinalidade como partition key de GSI é um anti-pattern clássico. A solução de longo prazo foi adicionar um sufixo de data (status#date) para distribuir melhor as escritas.

Projeção de Atributos: O Detalhe que Afeta Custo e Performance

Tanto LSI quanto GSI suportam três tipos de projeção:

  • KEYS_ONLY: Apenas as chaves do índice e da tabela base são projetadas. Menor custo de armazenamento, mas queries que precisam de outros atributos disparam fetch adicional na tabela base.
  • INCLUDE: Chaves mais atributos específicos que você define. Equilíbrio entre custo e completude.
  • ALL: Todos os atributos da tabela base são copiados para o índice. Elimina fetches adicionais, mas duplica o armazenamento e aumenta o custo de escrita.

Para LSI, se a query precisar de atributos não projetados, o DynamoDB automaticamente faz um fetch consistente na tabela base — isso consome RCU da tabela, não do índice. Para GSI, atributos não projetados simplesmente não estão disponíveis na query; não há fetch automático.

IAM: Controlando Acesso a Índices Secundários

Permissões para query em índices secundários são controladas separadamente da tabela base. Para restringir acesso a um GSI específico:

🔽 Clique para expandir — Política IAM para acesso a GSI
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:Query"
      ],
      "Resource": [
        "arn:aws:dynamodb:us-east-1:123456789012:table/Orders/index/OrdersByStatus"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:PutItem",
        "dynamodb:UpdateItem",
        "dynamodb:DeleteItem",
        "dynamodb:GetItem"
      ],
      "Resource": [
        "arn:aws:dynamodb:us-east-1:123456789012:table/Orders"
      ]
    }
  ]
}

O ARN de um índice segue o padrão arn:aws:dynamodb:<region>:<account-id>:table/<TableName>/index/<IndexName>. Escritas no índice não são controladas diretamente — elas acontecem via escrita na tabela base.

LSI vs GSI no DynamoDB: Próximos Passos e Conclusão

A escolha entre LSI e GSI raramente é difícil quando você conhece as restrições: LSI é criado uma única vez junto com a tabela e vive dentro da partição; GSI é flexível, independente, mas exige atenção ao throughput e à cardinalidade da partition key escolhida.

Para aprofundar o design de tabelas DynamoDB com múltiplos padrões de acesso, consulte o guia de boas práticas de índices na documentação oficial da AWS e o guia de design NoSQL para DynamoDB.

Glossário

Termo Definição
Partition Key Atributo usado pelo DynamoDB para determinar em qual partição física o item é armazenado. Deve ter alta cardinalidade para distribuição uniforme.
Sort Key Atributo secundário da chave primária composta. Permite ordenação e range queries dentro de uma partição.
RCU / WCU Read Capacity Unit / Write Capacity Unit. Unidades de throughput provisionado no DynamoDB. 1 RCU = 1 leitura strongly consistent de até 4KB.
Projeção Conjunto de atributos copiados da tabela base para o índice. Define o que está disponível em queries no índice sem fetch adicional.
Hot Partition Partição que recebe volume desproporcional de requisições, causando throttling mesmo quando a capacidade total da tabela está subutilizada.

Related Posts

Comentários

Postagens mais visitadas deste blog

Variáveis de Ambiente no Lambda: Configuração, Acesso e Criptografia com KMS

Monitoramento de Memória RAM no EC2: Por que o CloudWatch Agent é Obrigatório

S3 Access Denied: Por que 'Bloquear Acesso Público' impede seu objeto mesmo após torná-lo público