Database Sharding e Partitioning: estratégias modernas de escalabilidade para 2026
Sharding e partitioning evoluíram de técnica de emergência para disciplina estratégica de arquitetura de dados, permitindo escalar sistemas de bilhões de registros com latência determinística e custo controlado.
Resumo executivo
Sharding e partitioning evoluíram de técnica de emergência para disciplina estratégica de arquitetura de dados, permitindo escalar sistemas de bilhões de registros com latência determinística e custo controlado.
Ultima atualizacao: 09/03/2026
Resumo executivo
Database sharding e partitioning consolidaram-se em 2026 como disciplina estratégica de arquitetura de dados, evoluindo de técnica de emergência ("nossa base de dados explodiu, vamos dividir") para framework proativo de escalabilidade. A diferença fundamental não está em "quanto data podemos armazenar", mas em "como podemos acessar qualquer dado eficientemente independentemente do tamanho total do dataset."
Para arquitetos de dados e engineering leads, a mudança de paradigma é clara: scale vertical (mais hardware na mesma máquina) tem limites físicos e custos exponenciais, enquanto scale horizontal (distribuir dados em múltiplas máquinas) permite escalar linearmente se implementado corretamente. O desafio em 2026 não é mais "como dividir dados?", mas "como dividir dados de forma que queries permanecem eficientes, resiliência é mantida, e schema evolution seja gerenciável?"
A realidade do mercado em 2026 é brutal: sistemas sem sharding bem-planejado atingem limits de scale onde queries de simples selects levam segundos, backups tornam-se operações de dias, e adding capacity requer downtime ou complexas re-sharding operations. A diferença não está em "escolher database mais potente", mas em disciplina de arquitetura: shard key design apropriado, partitioning strategy consistente, e cross-shard queries bem-planejadas.
Fundamentos: o que sharding e partitioning resolvem
Limitações de scale vertical
Scale vertical (vertical scaling) adiciona mais CPU, memory e storage à mesma máquina. Em 2026, limits físicos e econômicos tornam essa abordagem insustentável:
Limits físicos:
- Maximum RAM por instance (~2TB em cloud high-end instances)
- Maximum storage por instance (~100TB em cloud)
- Maximum I/O throughput por instance
- Network bandwidth constraints
Limits econômicos:
- Custo exponencial: 2x performance não custa 2x (custa 4-8x)
- Vendor lock-in: migrar de instance massive-level é complexo
- Single point of failure: toda application depende de uma máquina
Sharding vs. Partitioning: distinção importante
Partitioning (Table Partitioning):
- Divide uma tabela em múltiplos arquivos/partitions físicos
- Single database instance gerencia todas partitions
- Transparente para application (queries não mudam)
- Limitada por capacidade de single instance
- Tipicamente baseado em range (data) ou hash
Sharding (Horizontal Scaling):
- Divide dados em múltiplos database instances físicos
- Cada shard é independent e autônomo
- Application precisa saber qual shard contém dados (shard key)
- Escala linearmente: mais shards = mais capacity
- Exige cross-shard queries e transactions distribuídas
Estratégias de Shard Key Design
Shard key é o determinante mais importante para sucesso de sharding. Shard key ruim causa:
- Hotspots: um shard recebe 90% de traffic
- Cross-shard queries: queries precisam acessar múltiplos shards
- Imbalance: shards têm tamanhos e loads muito diferentes
Pattern 1: Hash-based Sharding
Conceito: Aplica função de hash em shard key para determinar shard destination. Garante distribuição uniforme.
Implementação:
sql-- Shard routing table
CREATE TABLE shard_routing (
shard_id INT PRIMARY KEY,
host VARCHAR(255),
port INT
);
-- Hash function para determinar shard
CREATE FUNCTION get_shard_id(key_value VARCHAR(255))
RETURNS INT AS $$
BEGIN
RETURN MOD(
MD5(key_value)::INT,
(SELECT COUNT(*) FROM shard_routing)
);
END;
$$ LANGUAGE plpgsql;
-- Query routing: application determina shard antes de executar query
SELECT host, port FROM shard_routing WHERE shard_id = get_shard_id('user-123');
-- Result: shard-3, host: db-shard-3.internal:5432Vantagens:
- Distribuição uniforme de dados
- Simplifica routing (hash determinístico)
- Escalabilidade previsível (adicionar novo shard com rebalancing planejado)
Desvantagens:
- Queries por range (e.g., "users created between Jan-Mar") acessam todos shards
- Relacionamentos entre tables podem ser cross-shard
- Re-sharding (mudar número de shards) é operação complexa
Quando usar:
- High-throughput workloads onde uniformidade de distribuição é crítica
- Dados sem padrões de access sequencial
- Workloads onde queries são predominantemente por chave primária
Pattern 2: Range-based Sharding
Conceito: Divide dados em ranges baseados em shard key (ex: data, ID, region).
Implementação:
sql-- Shard routing por range
CREATE TABLE shard_routing_range (
shard_id INT PRIMARY KEY,
min_value BIGINT,
max_value BIGINT,
host VARCHAR(255),
port INT
);
INSERT INTO shard_routing_range VALUES
(1, 0, 1000000, 'db-shard-1.internal', 5432),
(2, 1000001, 2000000, 'db-shard-2.internal', 5432),
(3, 2000001, 3000000, 'db-shard-3.internal', 5432);
-- Query routing por ID range
SELECT host, port FROM shard_routing_range
WHERE 1234567 BETWEEN min_value AND max_value;
-- Result: shard-2, host: db-shard-2.internal:5432Vantagens:
- Queries por range são efficient (single shard)
- Rebalancing é mais simples (mudar ranges, não dados)
- Facilita data lifecycle management (e.g., archival de dados antigos)
Desvantagens:
- Hotspots comuns (shard com range mais recente receives mais traffic)
- Imbalance se distribuição de dados não é uniforme
- Adicionar/remover shards requer ajustar ranges complexo
Quando usar:
- Time-series data onde queries são predominantemente por range de tempo
- Dados com padrões de access sequencial
- Workloads onde data lifecycle management é importante
Pattern 3: Geographic Sharding
Conceito: Divide dados por geografia (region, country, data center proximity).
Implementação:
sql-- Shard routing por geografia
CREATE TABLE shard_routing_geo (
shard_id INT PRIMARY KEY,
region VARCHAR(50),
country_code VARCHAR(2),
host VARCHAR(255),
port INT
);
INSERT INTO shard_routing_geo VALUES
(1, 'US-EAST', 'US', 'db-shard-useast.internal', 5432),
(2, 'US-WEST', 'US', 'db-shard-uswest.internal', 5432),
(3, 'EU-WEST', 'EU', 'db-shard-euwest.internal', 5432),
(4, 'AP-SOUTHEAST', 'AP', 'db-shard-apse.internal', 5432);
-- Query routing por país de usuário
SELECT host, port FROM shard_routing_geo
WHERE country_code = 'BR';
-- Result: shard-4, host: db-shard-apse.internal:5432Vantagens:
- Latência reduzida (dados estão próximos de users)
- Compliance de residência de dados (GDPR, LGPD)
- Reduz cross-region network costs
Desvantagens:
- Imbalance se distribuição de users não é uniforme por geografia
- Cross-shard queries se workloads são global
- Complexidade operacional (múltiplas regions, compliance)
Quando usar:
- Aplicações globais com requirements de latência regional
- Compliance de residência de dados é requirement
- Workloads onde access patterns são predominantemente regionais
Padrões de Cross-Shard Queries
Sharding cria desafio fundamental: queries que precisam acessar dados em múltiplos shards.
Pattern 1: Application-side Aggregation
Conceito: Application acessa múltiplos shards e agrega resultados.
Implementação:
typescriptclass ShardedQueryService {
async getUserOrders(userId: string): Promise<Order[]> {
// Determinar shard de usuário
const userShard = await this.getShardForUser(userId);
// Buscar usuário no shard apropriado
const user = await this.queryShard(userShard, `
SELECT * FROM users WHERE id = $1
`, [userId]);
// Buscar orders em todos shards (orders podem estar em qualquer shard)
const orderShards = await this.getAllShards();
const ordersPromises = orderShards.map(shard =>
this.queryShard(shard, `
SELECT * FROM orders WHERE user_id = $1
`, [userId])
);
const ordersByShard = await Promise.all(ordersPromises);
const allOrders = ordersByShard.flat();
// Agregar e ordenar results
return allOrders.sort((a, b) =>
b.createdAt.getTime() - a.createdAt.getTime()
);
}
}Vantagens:
- Simplicidade de implementação
- Flexibilidade para queries complexas
- Não requer database layer modificado
Desvantagens:
- Latência aumentada (múltiplas chamadas de network)
- Memory overhead na application
- Sem atomicidade entre shards
Pattern 2: Database-side Distributed Query
Conceito: Database com suporte nativo para queries distribuídas (ex: PostgreSQL FDW, MySQL Federated).
Implementação:
sql-- PostgreSQL Foreign Data Wrapper para cross-shard queries
CREATE EXTENSION postgres_fdw;
-- Criar foreign server para shard remoto
CREATE SERVER shard2 FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (
host 'db-shard-2.internal',
port '5432',
dbname 'appdb'
);
-- Mapear foreign tables
CREATE FOREIGN TABLE orders_shard2 (
id BIGINT,
user_id BIGINT,
created_at TIMESTAMP,
total DECIMAL(10,2)
)
SERVER shard2
OPTIONS (schema_name 'public', table_name 'orders');
-- Query distribuída transparente
SELECT * FROM orders o
LEFT JOIN orders_shard2 o2 ON o.user_id = o2.user_id
WHERE o.user_id = $1;Vantagens:
- Queries SQL normais (application não precisa saber sobre shards)
- Database gerencia join optimization
- Atomicidade parcial (transactions podem ser distribuídas)
Desvantagens:
- Complexidade de configuration e maintenance
- Performance overhead de queries distribuídas
- Database vendor lock-in
Pattern 3: Materialized Views de Aggregation
Conceito: Materializa dados agregados periodicamente em shard dedicado.
Implementação:
sql-- Tabela de agregação em shard dedicado (analytics shard)
CREATE TABLE user_order_summary (
user_id BIGINT PRIMARY KEY,
total_orders INT,
total_spent DECIMAL(15,2),
last_order_date TIMESTAMP,
updated_at TIMESTAMP DEFAULT NOW()
);
-- Job de agregação (executa periodicamente)
INSERT INTO user_order_summary
SELECT
user_id,
COUNT(*) as total_orders,
SUM(total) as total_spent,
MAX(created_at) as last_order_date,
NOW() as updated_at
FROM (
SELECT * FROM orders
UNION ALL
SELECT * FROM orders_shard2
UNION ALL
SELECT * FROM orders_shard3
) all_orders
GROUP BY user_id
ON CONFLICT (user_id) DO UPDATE SET
total_orders = EXCLUDED.total_orders,
total_spent = EXCLUDED.total_spent,
last_order_date = EXCLUDED.last_order_date,
updated_at = EXCLUDED.updated_at;Vantagens:
- Queries agregadas são rápidas (single table scan)
- Reduz load em shards operacionais
- Facilita analytics e reporting
Desvantagens:
- Data eventual consistency (summary é atualizado periodicamente)
- Duplicação de dados
- Complexidade de jobs de manutenção
Estratégias de Partitioning
Partitioning complementa sharding dividindo tables grandes em partitions gerenciáveis.
Time-series Partitioning
Conceito: Divide table por intervalos de tempo (diário, semanal, mensal).
Implementação:
sql-- Table particionada por data
CREATE TABLE orders (
id BIGINT,
user_id BIGINT,
created_at TIMESTAMP NOT NULL,
total DECIMAL(10,2),
status VARCHAR(50)
) PARTITION BY RANGE (created_at);
-- Partitions mensais
CREATE TABLE orders_2026_01 PARTITION OF orders
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE orders_2026_02 PARTITION OF orders
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
CREATE TABLE orders_2026_03 PARTITION OF orders
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
-- Partition default para dados futuros
CREATE TABLE orders_default PARTITION OF orders DEFAULT;
-- Query automaticamente roteada para partition correta
SELECT * FROM orders WHERE created_at >= '2026-03-01' AND created_at < '2026-03-02';
-- Scanner apenas partition orders_2026_03, não table inteiraVantagens:
- Queries por range de tempo são extremamente rápidas (pruning de partitions)
- Facilita data lifecycle management (drop partitions antigas)
- Reduz lock contention (queries acessam partitions diferentes)
Desvantagens:
- Queries cross-partitions (range spanning múltiplos meses) perdem benefício
- Schema evolution mais complexo (todas partitions precisam ser alteradas)
- Overhead de gestão de partitions
Hash Partitioning
Conceito: Divide table aplicando hash em partition key.
Implementação:
sql-- Table particionada por hash
CREATE TABLE events (
id BIGINT,
event_type VARCHAR(50),
payload JSONB,
created_at TIMESTAMP
) PARTITION BY HASH (id);
-- Partitions
CREATE TABLE events_0 PARTITION OF events FOR VALUES WITH (MODULUS 8, REMAINDER 0);
CREATE TABLE events_1 PARTITION OF events FOR VALUES WITH (MODULUS 8, REMAINDER 1);
CREATE TABLE events_2 PARTITION OF events FOR VALUES WITH (MODULUS 8, REMAINDER 2);
-- ... até events_7
-- Query automaticamente roteada para partition correta
SELECT * FROM events WHERE id = 12345;
-- Scanner apenas partition events_5 (12345 % 8 = 5), não table inteiraVantagens:
- Distribuição uniforme de dados
- Queries por ID são extremamente rápidas (single partition)
- Simplicidade de gestão (número fixo de partitions)
Desvantagens:
- Queries por range perdem benefício (acessam todas partitions)
- Não facilita data lifecycle management
- Re-sharding é complexo (mudar número de partitions)
Schema Evolution e Migration
Sharding adiciona complexidade significativa para schema changes e migrations.
Pattern 1: Blue-Green Migration
Conceito: Cria nova versão de schema em paralelo e migra gradualmente.
Implementação:
typescriptclass SchemaMigrationService {
async migrateToNewSchema() {
// 1. Criar nova tabela com novo schema em todos shards
for (const shard of await this.getAllShards()) {
await this.queryShard(shard, `
CREATE TABLE users_v2 (
id BIGINT PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255) UNIQUE,
preferences JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
`);
}
// 2. Configurar dual-write (write em ambas tabelas)
await this.enableDualWrite('users', 'users_v2');
// 3. Backfill dados existentes
await this.backfillData('users', 'users_v2');
// 4. Gradual migration de reads
await this.migrateReads('users', 'users_v2', 0.1); // 10% reads para v2
await this.migrateReads('users', 'users_v2', 0.5); // 50% reads para v2
await this.migrateReads('users', 'users_v2', 1.0); // 100% reads para v2
// 5. Desabilitar dual-write e limpar tabela antiga
await this.disableDualWrite('users', 'users_v2');
await this.dropOldTable('users');
}
}Vantagens:
- Zero downtime migration
- Fácil rollback (desabilitar migration e voltar para schema antigo)
- Permite monitoramento e validação gradual
Desvantagens:
- Complexidade de implementação
- Dual-write aumenta load e custo
- Período de eventual consistency durante migration
Pattern 2: Online Schema Change (OSC)
Conceito: Ferramentas como pt-online-schema-change (Percona) ou pg_repack (PostgreSQL) executam schema changes sem blocking locks.
Implementação:
bash# Percona Toolkit para online schema change
pt-online-schema-change \
--host=db-shard-1.internal \
--port=5432 \
--user=admin \
--password=*** \
D=appdb,t=users \
--alter "ADD COLUMN phone VARCHAR(20)" \
--execute
# PostgreSQL pg_repack para reorganizar table sem locks
pg_repack -h db-shard-1.internal -p 5432 -d appdb -t usersVantagens:
- Schema changes sem blocking operations
- Permite reads e writes durante migration
- Simplifica operação para DBAs
Desvantagens:
- Requer ferramentas específicas de database
- Alguns tipos de changes não são suportados
- Overhead temporário de performance durante migration
Frameworks e ferramentas recomendadas
Distributed SQL Databases
CockroachDB:
- Geo-partitioning nativo
- Automatic rebalancing
- Distributed transactions ACID
- SQL compatibility (PostgreSQL-like)
TiDB:
- Horizontal scaling automático
- HTAP (Hybrid Transactional/Analytical Processing)
- MySQL protocol compatibility
- Online schema changes nativos
Vitess:
- MySQL sharding framework
- VSchema para shard routing
- VTGate para query routing
- Online schema changes
Middleware de Sharding
ProxySQL:
- Transparent sharding para MySQL
- Query routing inteligente
- Connection pooling
- Monitoring e metrics
Vitess:
- Sharding layer para MySQL
- Query planning e optimization
- VReplication para data migration
- VDiff para comparação de datasets
ShardingSphere:
- Framework de sharding para Java
- Suporte para múltiplos databases
- Distributed transactions
- Read-write splitting
Checklist de implementação de 90 dias
Mês 1: Planejamento e Fundação
Semana 1-2: Análise de workload
- [ ] Identificar bottlenecks de performance atuais
- [ ] Analisar padrões de query e access
- [ ] Determinar candidatos a shard key
- [ ] Estimar volume de dados e growth rate
Semana 3-4: Design de sharding
- [ ] Selecionar estratégia de shard key (hash/range/geographic)
- [ ] Definir schema de shard routing
- [ ] Projetar partitioning para tables críticas
- [ ] Documentar cross-shard query patterns
Mês 2: Implementação Piloto
Semana 5-6: Setup de infraestrutura
- [ ] Provisionar primeiros shards
- [ ] Configurar replication e backup para cada shard
- [ ] Implementar shard routing service
- [ ] Estabelecer monitoring cross-shard
Semana 7-8: Migration Piloto
- [ ] Selecionar workload de baixo risco para piloto
- [ ] Implementar dual-write para tables piloto
- [ ] Executar data backfill incremental
- [ ] Migrar reads gradualmente (10% → 100%)
Mês 3: Expansão e Otimização
Semana 9-10: Expansão Controlada
- [ ] Migrar workloads adicionais para sharded architecture
- [ ] Monitorar performance e latência cross-shard
- [ ] Otimizar shard routing queries
- [ ] Refinar schema de agregação para cross-shard queries
Semana 11-12: Production-Ready
- [ ] Configurar automatic failover para shards
- [ ] Implementar alerting para shard health
- [ ] Documentar operações de manutenção e troubleshooting
- [ ] Estabelecer processo de re-sharding planejado
Riscos e anti-padrões
Anti-padrão: Shard Key baseado em auto-increment
Problema: IDs sequenciais criam hotspots no último shard.
sql-- ❌ WRONG: Auto-increment como shard key
CREATE TABLE users (
id BIGINT AUTO_INCREMENT, -- Sequential IDs go to same shard
name VARCHAR(255),
email VARCHAR(255)
);
-- ✅ CORRECT: UUID como shard key
CREATE TABLE users (
id UUID DEFAULT gen_random_uuid(), -- Random IDs distribute evenly
name VARCHAR(255),
email VARCHAR(255)
);Anti-padrão: Monolito de Cross-Shard Queries
Problema: Todas queries acessam todos shards, eliminando benefícios de sharding.
typescript// ❌ WRONG: Query não especifica shard key
async function getAllUsers(): Promise<User[]> {
// Acessa todos shards
const allUsers = await this.queryAllShards(`
SELECT * FROM users
`);
return allUsers;
}
// ✅ CORRECT: Query especifica shard key quando possível
async function getUserById(userId: string): Promise<User> {
// Acessa apenas shard correto
const shard = await this.getShardForUser(userId);
const user = await this.queryShard(shard, `
SELECT * FROM users WHERE id = $1
`, [userId]);
return user;
}Conclusão
Database sharding e partitioning em 2026 são disciplinas de arquitetura de dados que permitem escalar sistemas além dos limits de scale vertical. Implementação bem-sucedida requer shard key design apropriado, partitioning strategy consistente, e planejamento cuidadoso de cross-shard queries e schema evolution.
A pergunta estratégica para 2026 não é "devemos shardar nosso database?", mas "como shardar de forma que queries permanecem eficientes, resiliência é mantida, e schema evolution é gerenciável?"
Empresas maduras tratam sharding e partitioning como parte fundamental de arquitetura de dados, planejando proativamente em vez de reagir emergencialmente quando limits são atingidos. A escolha correta de shard key, estratégia de partitioning, e framework de cross-shard queries determina sucesso de escalabilidade a longo prazo.
Sua base de dados está atingindo limits de scale vertical e precisa estratégia para escalar horizontalmente? Falar sobre arquitetura de dados com a Imperialis para desenhar estratégia de sharding e partitioning que permita escalar com latência determinística.
Fontes
- PostgreSQL Partitioning Documentation — PostgreSQL partitioning guide
- MySQL Partitioning — MySQL partitioning documentation
- CockroachDB Architecture — Distributed SQL architecture
- TiDB Architecture — Distributed database architecture