AWS Admin

Como Usar DynamoDB: Data Modeling, GSIs, LSIs e Capacity Modes em Produção Já leu

Data Modeling no DynamoDB O DynamoDB é um banco de dados NoSQL totalmente gerenciado da AWS que exige uma abordagem de modelagem fundamentalmente diferente de bancos relacionais. A chave está em pensar em como você vai consultar os dados, não apenas em como armazená-los. Diferentemente do SQL, você define uma chave primária (Partition Key + Sort Key opcional) que determina como os dados são distribuídos e recuperados. Uma boa modelagem começa com identificar seus padrões de acesso. Se você precisa buscar usuários por ID e depois por email, não crie duas tabelas — use uma Partition Key ( ) e crie um GSI (Global Secondary Index) para email. Evite o máximo possível queries que varrem toda a tabela. O exemplo abaixo mostra uma tabela de e-commerce onde o padrão de acesso é: buscar pedidos por usuário, e secundariamente por status. Índices Secundários: GSI vs LSI Global Secondary Indexes (GSI) GSIs permitem consultar dados usando uma chave primária diferente, com throughput

Data Modeling no DynamoDB

O DynamoDB é um banco de dados NoSQL totalmente gerenciado da AWS que exige uma abordagem de modelagem fundamentalmente diferente de bancos relacionais. A chave está em pensar em como você vai consultar os dados, não apenas em como armazená-los. Diferentemente do SQL, você define uma chave primária (Partition Key + Sort Key opcional) que determina como os dados são distribuídos e recuperados.

Uma boa modelagem começa com identificar seus padrões de acesso. Se você precisa buscar usuários por ID e depois por email, não crie duas tabelas — use uma Partition Key (userId) e crie um GSI (Global Secondary Index) para email. Evite o máximo possível queries que varrem toda a tabela. O exemplo abaixo mostra uma tabela de e-commerce onde o padrão de acesso é: buscar pedidos por usuário, e secundariamente por status.

import boto3
from decimal import Decimal

dynamodb = boto3.resource('dynamodb')

# Criar tabela com modelagem adequada
table = dynamodb.create_table(
    TableName='Orders',
    KeySchema=[
        {'AttributeName': 'userId', 'KeyType': 'HASH'},     # Partition Key
        {'AttributeName': 'orderId', 'KeyType': 'RANGE'}    # Sort Key
    ],
    AttributeDefinitions=[
        {'AttributeName': 'userId', 'AttributeType': 'S'},
        {'AttributeName': 'orderId', 'AttributeType': 'S'},
        {'AttributeName': 'orderStatus', 'AttributeType': 'S'}
    ],
    BillingMode='PAY_PER_REQUEST'
)

# Inserir item com dados desnormalizados (padrão NoSQL)
table.put_item(Item={
    'userId': 'user#123',
    'orderId': 'order#456',
    'orderStatus': 'DELIVERED',
    'totalAmount': Decimal('199.99'),
    'items': [
        {'sku': 'PROD#001', 'quantity': 2},
        {'sku': 'PROD#002', 'quantity': 1}
    ],
    'createdAt': '2024-01-15T10:30:00Z'
})

Índices Secundários: GSI vs LSI

Global Secondary Indexes (GSI)

GSIs permitem consultar dados usando uma chave primária diferente, com throughput independente. Você pode criar, atualizar e deletar GSIs sem parar a tabela. São úteis quando você precisa de padrões de acesso totalmente novos. Um GSI consome throughput separado da tabela base — você paga por leitura/escrita no GSI.

# Adicionar GSI para buscar pedidos por status
table.update_table(
    AttributeDefinitions=[
        {'AttributeName': 'orderStatus', 'AttributeType': 'S'},
        {'AttributeName': 'createdAt', 'AttributeType': 'S'}
    ],
    GlobalSecondaryIndexUpdates=[
        {
            'Create': {
                'IndexName': 'OrderStatusIndex',
                'KeySchema': [
                    {'AttributeName': 'orderStatus', 'KeyType': 'HASH'},
                    {'AttributeName': 'createdAt', 'KeyType': 'RANGE'}
                ],
                'Projection': {'ProjectionType': 'ALL'},
                'BillingMode': 'PAY_PER_REQUEST'
            }
        }
    ]
)

# Consultar usando GSI
response = table.query(
    IndexName='OrderStatusIndex',
    KeyConditionExpression='orderStatus = :status',
    ExpressionAttributeValues={':status': 'PENDING'}
)

Local Secondary Indexes (LSI)

LSIs compartilham throughput com a tabela base e devem ser criados no momento da criação da tabela. São limitados a 10 GB por partition key. Use LSIs quando você quer ordenar resultados de forma diferente mantendo a mesma partition key. Para a maioria dos casos de produção, GSIs são preferíveis.

# Criar tabela com LSI (apenas na criação)
table = dynamodb.create_table(
    TableName='UserActivity',
    KeySchema=[
        {'AttributeName': 'userId', 'KeyType': 'HASH'},
        {'AttributeName': 'timestamp', 'KeyType': 'RANGE'}
    ],
    AttributeDefinitions=[
        {'AttributeName': 'userId', 'AttributeType': 'S'},
        {'AttributeName': 'timestamp', 'AttributeType': 'N'},
        {'AttributeName': 'activityType', 'AttributeType': 'S'}
    ],
    LocalSecondaryIndexes=[
        {
            'IndexName': 'UserActivityTypeIndex',
            'KeySchema': [
                {'AttributeName': 'userId', 'KeyType': 'HASH'},
                {'AttributeName': 'activityType', 'KeyType': 'RANGE'}
            ],
            'Projection': {'ProjectionType': 'ALL'}
        }
    ],
    BillingMode='PAY_PER_REQUEST'
)

Capacity Modes em Produção

Provisioned vs On-Demand

O modo Provisioned exige que você defina RCUs (Read Capacity Units) e WCUs (Write Capacity Units) antecipadamente. Cada RCU permite uma leitura fortemente consistente de até 4 KB por segundo; cada WCU permite uma escrita de até 1 KB por segundo. Use quando sua carga é previsível e consistente — é mais barato em geral.

O modo On-Demand (PAY_PER_REQUEST) cobra por requisição: aproximadamente $0.25 por milhão de RCUs e $1.25 por milhão de WCUs. Ideal para cargas impredizíveis, desenvolvimento e spikes de tráfego. Não há limite hard — a AWS escala automaticamente.

# Exemplo: Provisioned Mode para aplicação com carga conhecida
table = dynamodb.create_table(
    TableName='ProductCatalog',
    KeySchema=[{'AttributeName': 'productId', 'KeyType': 'HASH'}],
    AttributeDefinitions=[{'AttributeName': 'productId', 'AttributeType': 'S'}],
    BillingMode='PROVISIONED',
    ProvisionedThroughputCapacity={
        'ReadCapacityUnits': 100,
        'WriteCapacityUnits': 50
    }
)

# Exemplo: On-Demand para novos serviços ou tráfego variável
table = dynamodb.create_table(
    TableName='UserSessions',
    KeySchema=[{'AttributeName': 'sessionId', 'KeyType': 'HASH'}],
    AttributeDefinitions=[{'AttributeName': 'sessionId', 'AttributeType': 'S'}],
    BillingMode='PAY_PER_REQUEST'
)

Prática em Produção

Para produção, comece com On-Demand se não conhecer a carga. Monitore via CloudWatch — se seu padrão se torna previsível, migre para Provisioned com Auto Scaling. Configure alarmes para throttling (quando você esgota sua capacidade) e use o CloudWatch Insights para analisar hot partitions (uma partition key recebendo tráfego desproporcional).

# Configurar Auto Scaling para Provisioned Mode
autoscaling = boto3.client('application-autoscaling')

autoscaling.register_scalable_target(
    ServiceNamespace='dynamodb',
    ResourceId='table/Orders',
    ScalableDimension='dynamodb:table:WriteCapacityUnits',
    MinCapacity=5,
    MaxCapacity=1000
)

autoscaling.put_scaling_policy(
    PolicyName='OrdersWriteScaling',
    ServiceNamespace='dynamodb',
    ResourceId='table/Orders',
    ScalableDimension='dynamodb:table:WriteCapacityUnits',
    PolicyType='TargetTrackingScaling',
    TargetTrackingScalingPolicyConfiguration={
        'TargetValue': 70.0,
        'PredefinedMetricSpecification': {
            'PredefinedMetricType': 'DynamoDBWriteCapacityUtilization'
        }
    }
)

Otimizações e Boas Práticas

Sempre use batch operations (BatchGetItem, BatchWriteItem) em vez de requisições individuais — reduz latência e custo. Implemente exponential backoff para throttling automático. Use sparse indexes — só projete atributos que você realmente consultará no GSI, economizando throughput. Evite scans; eles leem tudo e são caros. Se precisar migrar dados, use o DMS (Data Migration Service) ou Lambda com streams.

Considere o DynamoDB Streams para capturar mudanças em tempo real — perfeito para sincronizar com Elasticsearch ou disparar Lambdas. Na modelagem, use partition keys com boa distribuição — não use booleanos ou enums com poucos valores (criarão hot partitions). Para casos raros, use Query com FilterExpression, mas lembre que o filtro é aplicado após a leitura, portanto ainda consome RCU.

Conclusão

DynamoDB exige pensar primeiro em padrões de acesso, não em estrutura de dados. Domine GSIs (criação flexível, throughput independente), use On-Demand em produção inicial e migre para Provisioned com Auto Scaling conforme a carga se estabiliza. Monitore hot partitions e throttling no CloudWatch — a escalabilidade é automática, mas uma boa modelagem e escolha de chaves determinam seu custo e performance final.

Referências


Artigos relacionados