Escala e NoSQL
Tudo o que você viu até aqui — transações, JOINs, integridade — assume um banco numa máquina só. Quando os dados não cabem mais nela, cada uma dessas garantias vira uma negociação. Esta lição é sobre o que exatamente se perde ao distribuir, o que se ganha em troca, e por que a resposta certa quase sempre é "ainda não".
Distribuir é trocar garantias por capacidade
Numa máquina só, "o saldo é 1000" é um fato simples: existe um lugar onde esse número mora. Em cinco máquinas, a frase precisa de qualificação. Saldo segundo qual servidor? E se dois receberem escritas diferentes ao mesmo tempo? E se um deles não estiver falando com os outros neste instante?
Nada disso é falha de engenharia — é consequência de a informação levar tempo para viajar. Um sistema distribuído não tem um "agora" compartilhado. Toda tecnologia distribuída é, no fundo, uma escolha específica sobre como conviver com essa ausência.
Replicação, sharding e a escolha do CAP
Na primeira aba, escreva no primário e veja o lag de replicação criar leituras desatualizadas. Na segunda, compare sharding por hash e por faixa — marque a caixa de registros recentes para ver o ponto quente aparecer. Na terceira, particione a rede e escolha entre consistência e disponibilidade.
Replicação é ter cópias dos mesmos dados em várias máquinas — para sobreviver à queda de uma delas e para dividir a carga de leitura. A pergunta difícil é: o commit deve esperar as cópias?
A ordem certa de escalar
# A ordem certa de escalar um banco — e quase ninguém precisa # passar do passo 4. # # 1. ÍNDICES E CONSULTAS ganho de 10x a 1000x, custo ~zero # A maioria dos "problemas de escala" é uma consulta ruim. # # 2. CACHE Redis na frente das leituras quentes # Barato, mas traz o problema clássico: invalidação. # # 3. ESCALA VERTICAL mais RAM e CPU numa máquina só # Subestimado. Uma máquina moderna com 1 TB de RAM aguenta # muito mais do que a maioria dos sistemas jamais vai exigir, # e mantém tudo simples e transacional. # # 4. RÉPLICAS DE LEITURA cópias que atendem SELECTs # Resolve leitura pesada. Não resolve escrita, e traz o lag. # # 5. SHARDING particionar os dados em N servidores # A última opção, e a mais cara. JOINs entre shards deixam de # existir, transações entre shards viram um problema em aberto, # e reparticionar depois é uma migração dolorosa. # # Regra: só suba um degrau depois de MEDIR que o anterior acabou.
Sharding: a decisão irreversível
# Escolher a chave de partição é a decisão mais difícil de reverter # em todo o sistema. Ela define o que fica junto — e o que nunca mais # poderá ser consultado numa operação só. # POR HASH — distribuição uniforme, sem ponto quente shard = hash(usuario_id) % N # ✓ carga equilibrada mesmo com ids sequenciais # ✗ chaves vizinhas ficam separadas: consulta por intervalo # precisa perguntar a TODOS os shards (scatter-gather) # ✗ com "% N" puro, mudar N remaneja quase tudo # CONSISTENT HASHING — resolve o problema do "% N" # Servidores e chaves são posicionados num anel de 0 a 2³²; # cada chave pertence ao primeiro servidor em sentido horário. # Adicionar um servidor move só ~1/N das chaves, não quase todas. # Base do Cassandra, do DynamoDB e do roteamento de CDNs. # POR FAIXA — vizinhos ficam juntos shard = 0 if data < "2024-01" else 1 # ✓ consulta por intervalo toca um shard só # ✗ PONTO QUENTE: com ids ou timestamps crescentes, todo o # tráfego novo vai para o último shard e os outros ficam ociosos # O que se PERDE ao shardear, em qualquer estratégia: # . JOIN entre shards (ou é feito na aplicação, ou não existe) # . transações ACID atravessando shards # . UNIQUE global (o banco só garante unicidade dentro do shard) # . AUTO_INCREMENT global (daí UUIDs, ULIDs, Snowflake IDs)
As famílias NoSQL
-- "NoSQL" não é uma tecnologia: é um guarda-chuva para bancos que -- abriram mão de algo do modelo relacional para ganhar outra coisa. -- CHAVE-VALOR — Redis, DynamoDB, Memcached -- get(chave) / put(chave, valor). Só isso. -- Ganha: latência sub-milissegundo, escala trivial -- Perde: só se busca pela chave. Sem consulta por conteúdo. -- Use para: cache, sessões, rate limiting, filas, contadores -- DOCUMENTOS — MongoDB, CouchDB, Firestore -- Guarda JSON aninhado; o documento inteiro é lido de uma vez. -- Ganha: esquema flexível, sem JOIN para dados aninhados -- Perde: consistência entre documentos, e o esquema não some — -- ele só migra do banco para o seu código de leitura -- Use para: catálogos heterogêneos, documentos naturalmente -- autocontidos, prototipagem -- COLUNAR LARGO — Cassandra, HBase, ScyllaDB -- Você modela a partir das CONSULTAS, não das entidades, e -- duplica dados de propósito (uma tabela por padrão de acesso). -- Ganha: escrita massiva, escala linear, sem ponto único de falha -- Perde: consultas ad-hoc — se não estava no modelo, não dá -- Use para: séries temporais, telemetria, feeds, logs em escala -- GRAFOS — Neo4j, Neptune -- Relacionamentos são cidadãos de primeira classe. -- Ganha: percorrer conexões profundas sem N JOINs -- Use para: redes sociais, detecção de fraude, recomendação -- ANALÍTICO / COLUNAR — ClickHouse, BigQuery, Snowflake, DuckDB -- Guarda por COLUNA, não por linha: ler uma coluna de 1 bilhão -- de linhas não toca nas outras 40, e comprime muito melhor. -- Perde: escrita e atualização linha a linha -- Use para: BI, agregações sobre volumes enormes
O pêndulo de volta
-- O movimento dos últimos anos: o pêndulo voltou. -- -- 2010: "bancos relacionais não escalam, precisamos de NoSQL" -- 2025: os relacionais aprenderam quase tudo que faltava — -- -- . JSONB no PostgreSQL, com índices GIN: documentos COM -- transações e possibilidade de JOIN quando precisar -- . particionamento nativo de tabelas -- . replicação lógica e réplicas de leitura simples de operar -- . NewSQL — CockroachDB, TiDB, Spanner, Yugabyte: SQL e ACID -- distribuídos de verdade, em vários datacenters -- . SQLite e DuckDB embutidos, cobrindo dois extremos CREATE TABLE eventos ( id BIGSERIAL PRIMARY KEY, tipo TEXT NOT NULL, dados JSONB -- flexível onde precisa ser ); CREATE INDEX ON eventos USING GIN (dados); SELECT * FROM eventos WHERE dados @> '{"user": 42}'; -- O conselho que envelheceu bem: -- comece com PostgreSQL. Ele resolve o problema de 95% dos -- sistemas, e você só vai saber qual trade-off precisa aceitar -- depois de ter carga real. Escolher um banco distribuído antes -- de ter o problema é pagar toda a complexidade sem o benefício.
Distribuindo de verdade
Mini projeto: implemente consistent hashing em Python. Um anel com N servidores
virtuais, uma função que mapeia chave → servidor, e uma medição: distribua 100 mil chaves entre 4
servidores, adicione o quinto e conte quantas mudaram de lugar. Compare com hash(k) % N
puro. A diferença — algo como 20% contra 80% — é a razão de existir da técnica. Depois adicione nós
virtuais (cada servidor ocupando 150 pontos do anel) e veja a distribuição ficar visivelmente mais
uniforme.
Projeto principal: suba um PostgreSQL com uma réplica de leitura (dois containers Docker com replicação em streaming, ou o serviço gerenciado da sua nuvem). Escreva um script que insere no primário e imediatamente lê da réplica, medindo com que frequência lê dado velho. Depois aumente a carga de escrita e observe o lag crescer. Por fim, implemente read-your-writes: direcione ao primário as leituras de um usuário nos segundos seguintes a uma escrita dele.
Desafio extra: modele o mesmo domínio — digamos, um feed de rede social — em três bancos: PostgreSQL normalizado, MongoDB com documentos e Cassandra com tabelas por padrão de consulta. Para cada um, escreva as cinco consultas mais comuns do produto e anote o que ficou fácil e o que ficou impossível. O exercício não tem vencedor: o objetivo é sentir na prática que a escolha do banco é a escolha de quais perguntas serão baratas.
Teste sua intuição
Onde você encontra isso
O carrinho da Amazon e o Dynamo
O artigo do Dynamo (2007) é a origem prática da consistência eventual. A decisão de projeto foi explícita: para um carrinho de compras, dizer "estamos fora do ar" custa uma venda, enquanto aceitar duas versões do carrinho e mesclá-las depois custa, no pior caso, um item reaparecendo. Escolheram AP conscientemente — e essa escolha só fazia sentido para aquele caso de uso. O checkout e o pagamento continuaram consistentes.
Spanner e o relógio como infraestrutura
O Spanner, do Google, oferece transações ACID serializáveis globalmente distribuídas — algo tido como impraticável. O truque é a TrueTime: relógios atômicos e receptores de GPS em cada datacenter que limitam a incerteza temporal a poucos milissegundos. Transações esperam esse intervalo antes de confirmar, garantindo ordem global. É o caso raro em que a solução para um problema de software foi comprar hardware.
CRDTs e a colaboração em tempo real
Google Docs, Figma e Notion precisam aceitar edições simultâneas de várias pessoas, inclusive offline, e convergir sem conflito. A base são os CRDTs (conflict-free replicated data types): estruturas cujas operações são projetadas para que a ordem de aplicação não importe — o resultado converge sozinho. É a resposta de "AP feito direito": em vez de last-write-wins descartando trabalho, mesclagem que preserva a intenção de todo mundo.