01 - Particionamento Horizontal vs Vertical
video: https://youtu.be/... no frontmatter da nota.Particionamento Horizontal vs Vertical
Particionamento é a divisão de uma tabela grande em pedaços menores, gerenciados de forma independente mas consultáveis como uma tabela única; o particionamento horizontal divide por linhas, e o vertical divide por colunas. A distinção decisiva para produção é a fronteira física: quando os pedaços permanecem dentro do mesmo servidor de banco, fala-se em partitioning; quando atravessam servidores distintos, fala-se em sharding, tema detalhado nos arquivos seguintes deste módulo. O particionamento resolve problemas de administração, manutenção e desempenho de consultas em tabelas volumosas, enquanto o sharding resolve problemas de capacidade bruta de CPU, memória e disco de um único nó. Em produção, a decisão entre um e outro determina o modelo operacional do banco por anos, e a escolha equivocada, na direção do sharding prematuro ou do particionamento tardio, está entre as causas mais comuns de projetos de escala atrasados.
Conceito: as quatro combinações possíveis
A matriz conceitual cruza a dimensão de corte com a fronteira física. O corte horizontal fragmenta linhas: pedidos de 2024 em um grupo, de 2025 em outro, ou usuários de ID par e ímpar. O corte vertical fragmenta colunas: as colunas quentes do perfil em uma tabela, os blobs e textos longos em outra. Em cada uma dessas duas dimensões, os pedaços podem ficar no mesmo servidor ou em servidores diferentes, produzindo quatro arranjos com propósitos distintos.
O particionamento horizontal no servidor é o arranjo mais comum no dia a dia do DBA: uma tabela de eventos dividida por mês, com consultas roteadas automaticamente para as partições relevantes. O particionamento vertical é menos visível, mas frequente em sistemas legados que separaram dados frios em tabelas de histórico. O sharding horizontal e o sharding vertical entre servidores são os mesmos cortes, elevados à escala distribuída, com todos os custos de complexidade que a distribuição traz.
O vocabulário entre motores varia e gera confusão em discussões de arquitetura. O MySQL chama as partições horizontais de partitions e não tem particionamento vertical formal; o MongoDB embaralha os termos, pois sua operação de sharding cria chunks internos que funcionam como partições distribuídas; ferramentas como o Citus chamam de shards justamente as partições distribuídas por nós. O referencial adotado neste módulo separa pela fronteira física: mesma máquina, partição; máquinas distintas, shard.
O problema que resolve
Tabelas grandes degradam a operação de forma não linear. Índices deixam de caber na memória, consultas varrem mais blocos, o vacuum do PostgreSQL leva horas em tabelas de bilhões de linhas, e operações de manutenção como REINDEX ou alterações de schema viram janelas de risco. O ponto crítico é que esses problemas aparecem muito antes do esgotamento de capacidade: uma tabela de 500 GB pode ainda caber confortavelmente em disco e já ser um pesadelo de manutenção.
O particionamento horizontal ataca exatamente essa faixa de problema. Cada partição tem seus próprios índices menores, o planejador descarta partições irrelevantes para consultas com filtro na chave, e operações de ciclo de vida se tornam DDL barato: remover o mês antigo é descartar uma partição, operação instantânea, em vez de executar um DELETE que gera milhões de tuplas mortas e horas de vacuum. O particionamento vertical, por sua vez, combate linhas largas: colunas raramente lidas, de tamanho grande, espremem o cache de páginas e penalizam varreduras de colunas realmente quentes.
O diagnóstico correto usa métricas simples. Tempo de vacuum e de manutenção crescente, índices que não cabem em memória e planos que varrem tabelas inteiras são sinais de particionamento; saturação de CPU e de I/O com planos eficientes, fila de conexões e escrita que não acompanha são sinais de limite de nó, que nenhuma partição resolve. Medir antes de escolher evita aplicar a solução errada ao problema certo.
Particionamento horizontal no servidor
O PostgreSQL implementa particionamento declarativo nativo desde a versão 10, com três métodos. RANGE divide por intervalos da chave, tipicamente datas ou identificadores sequenciais; HASH distribui pelo resto da divisão do valor, homogeneizando a carga quando não há ordenação natural; LIST agrupa por valores discretos, como região ou unidade de negócio. A tabela pai define o método e a chave, e cada partição é uma tabela filha real, com índices próprios criados e mantidos em cascata.
-- Tabela de eventos particionada por mês (RANGE)
CREATE TABLE eventos (
id bigserial,
ocorrido_em timestamptz NOT NULL,
tipo text NOT NULL,
payload jsonb,
PRIMARY KEY (id, ocorrido_em) -- a chave da partição deve estar na PK
) PARTITION BY RANGE (ocorrido_em);
-- Partição de janeiro de 2025
CREATE TABLE eventos_2025_01 PARTITION OF eventos
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
-- Distribuição uniforme por hash quando não há eixo temporal
CREATE TABLE usuarios_part (
id bigint PRIMARY KEY,
nome text NOT NULL
) PARTITION BY HASH (id);
CREATE TABLE usuarios_p0 PARTITION OF usuarios_part
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
O ganho de desempenho vem do partition pruning: consultas com filtro na chave da partição acessam apenas as partições relevantes, reduzindo varredura e uso de cache. O ganho operacional vem do ciclo de vida por DDL: ATTACH e DETACH adicionam e removem partições em produção sem bloqueio prolongado, e DROP descarta dados antigos de forma instantânea, substituindo rotinas de arquivamento inteiras.
Os detalhes de implantação merecem atenção desde o início. Cada partição materializa seus próprios índices, portanto o custo total de memória e planejamento escala com a contagem de partições; restrições de unicidade precisam incluir a chave de partição, o que às vezes exige redesenhar a chave primária; e a criação de partições futuras deve ser automatizada, pois inserção sem partição de destino é erro imediato. Extensões como a pg_partman formalizam a automação de criação e retenção em produção.
Particionamento vertical
O corte vertical separa colunas de uma mesma entidade em tabelas distintas ligadas pela mesma chave primária. O caso de uso clássico divide o quente do frio: as colunas lidas a cada requisição ficam juntas e densas, enquanto textos longos, JSONs históricos e binários migram para uma tabela acessada raramente. Em bancos como o PostgreSQL, o efeito aparece também no mecanismo de TOAST, que já move valores muito grandes para armazenamento separado; o particionamento vertical manual antecipa e controla essa separação.
Os ganhos concretos são três. Páginas de disco ficam mais densas nas colunas quentes, aumentando a proporção de dados úteis em cada leitura; caches de buffer passam a reter mais entidades por página; e permissões e acessos podem ser segregados, como deixar a tabela de documentos sensíveis com política de acesso distinta da tabela de perfil público. O custo é a reconstituição da linha por JOIN quando o consumo precisa do conjunto completo, o que exige atenção em consultas que hoje varrem a tabela inteira sem filtro.
Um exemplo concreto ancora a técnica: em um sistema de RH, a tabela de colaboradores carrega nome, e-mail, matrícula e situação, lidos a cada listagem, junto com histórico salarial, documentos digitalizados e avaliações textuais, lidos raramente. Separar as colunas frias em tabela irmã com a mesma chave primária reduz a tabela quente para uma fração do tamanho original, acelera listagens e relatórios gerais, e ainda simplifica a aplicação de políticas de confidencialidade sobre o conjunto sensível.
Sharding horizontal entre servidores
Quando as partições horizontais atravessam a fronteira do servidor, o arranjo passa a ser sharding, e o problema muda de natureza. O particionamento no servidor mantém transações ACID locais, JOINs nativos e uma única fonte de verdade; o sharding distribui esses mesmos cortes por nós independentes, ganhando capacidade bruta de escrita e armazenamento, e perdendo simplicidade transacional e consistência forte entre fragmentos.
A progressão natural em produção é escalonar verticalmente, particionar no servidor, adicionar réplicas de leitura e, só então, considerar sharding. Os arquivos seguintes deste módulo detalham a chave de sharding, as estratégias de distribuição e os desafios de re-sharding; o ponto a registrar aqui é que o particionamento bem feito é preparação para o sharding, pois tabela já dividida por intervalos ou por hash migra para nós distribuídos com muito menos retrabalho.
Comparação dos arranjos
| Aspecto | Partição horizontal | Partição vertical | Shard horizontal | Shard vertical |
|---|---|---|---|---|
| O que se divide | Linhas, por intervalo ou hash | Colunas da mesma entidade | Linhas entre servidores | Tabelas entre servidores |
| Fronteira física | Mesmo servidor | Mesmo servidor | Múltiplos servidores | Múltiplos servidores |
| Problema atacado | Manutenção e desempenho de tabelas grandes | Linhas largas e colunas frias | Capacidade bruta de escrita e dados | Escala por domínio de negócio |
| Transações ACID | Locais e completas | Locais e completas | Limitadas ao fragmento | Por banco de domínio |
| JOIN entre pedaços | Nativo, mesmo nó | Nativo, mesmo nó | Caro, distribuído | Passa a ser integração entre serviços |
| Complexidade operacional | Baixa a média | Baixa | Alta | Alta, com governança de dados |
| Caso típico | Eventos, logs, faturamento por mês | Perfil com dados frios, documentos | Usuários, pedidos em escala massiva | Catálogo, pagamentos, identidade separados |
A leitura da tabela mostra que os dois particionamentos no servidor são técnicas de engenharia de banco aplicáveis cedo e reversíveis; os dois shardings são decisões arquiteturais caras, com custo permanente de operação, tomadas quando o nó único comprovadamente não alcança.
Particionamento e escala: o que muda e o que não muda
O particionamento melhora três vetores sem mudar a capacidade bruta: manutenção, com ciclo de vida por DDL; desempenho de consulta, com pruning; e contensão de dano, com incidentes confinados a uma partição. Ele não aumenta o poder de CPU, nem a memória, nem o disco do servidor; uma carga de escrita que satura o nó continuará saturando, mesmo com a tabela dividida em duzentas partições.
Essa limitação define o critério de escolha. Particione quando o problema é tamanho e operação de tabela; faça sharding quando o problema é capacidade do nó. Monitorar qual dos dois problemas está ativo é parte do trabalho do time de plataforma: métricas de tempo de vacuum, de tamanho de índice e de manutenção apontam para particionamento; métricas de saturação de CPU, de I/O e de fila de conexões apontam para escala horizontal do próprio banco.
Sinais práticos de que o particionamento basta e o sharding é exagero:
- Consultas principais filtram naturalmente por um eixo, como data, tenant ou região, permitindo poda eficiente.
- O volume total cabe no nó atual com folga de crescimento para o horizonte de planejamento do negócio.
- O problema real é retenção de dados antigos, resolvida por descarte de partição.
- A escrita satura por falta de indexação ou por transações longas, não por limite físico do hardware.
- O time de operação é pequeno, sem capacidade de absorver a complexidade permanente de um cluster distribuído.
Aplicação prática: retenção e consultas em produção
O padrão de maior retorno imediato é a retenção de eventos por partição mensal, com rotina de criação antecipada de partições futuras e descarte das antigas. Ferramentas como a extensão pg_partman automatizam esse ciclo, mas a versão manual é pequena e ensina o mecanismo:
-- Consulta com poda de partição: só a partição de janeiro é lida
EXPLAIN SELECT count(*) FROM eventos
WHERE ocorrido_em >= '2025-01-10' AND ocorrido_em < '2025-01-20';
-- Descarte instantâneo do mês antigo, sem DELETE e sem vacuum
ALTER TABLE eventos DETACH PARTITION eventos_2024_12;
-- Verificação do plano: procure a linha "Append" ou "Seq Scan on eventos_2025_01"
-- confirmando que partições fora do intervalo foram descartadas do plano
Boas práticas complementares incluem: criar partições futuras com antecedência para evitar erro de inserção sem destino; incluir sempre a chave de partição nos filtros e nas chaves primárias; dimensionar partições para caberem inteiras em memória quando possível; e documentar o contrato de retenção, pois partição descartada é dado perdido para sempre. Em cargas analíticas, a combinação de particionamento com zonas de brin ou índices por partição amplia o ganho de varredura, e em cargas transacionais o acompanhamento do planejador nas versões do banco é obrigatório, pois a eficiência do pruning melhora a cada release.
Erros comuns e boas práticas
- Erro: particionar por coluna ausente na maioria das consultas, anulando o pruning e somando overhead de planejamento; prática: escolher chave que esteja nos predicados dominantes do workload.
- Erro: usar particionamento como resposta para saturação de escrita; a capacidade do nó não cresce; prática: particionar para manutenção, escalar escrita com réplicas e, no limite, sharding.
- Erro: criar milhares de partições de dia ou de hora em versões antigas do PostgreSQL, degradando o planejador; prática: dimensionar a granularidade pelo volume real e pela versão em uso.
- Erro: esquecer a antecedência na criação de partições e receber erro de insert sem partição destino; prática: automação de criação futura com alarme de partição ausente.
- Erro: particionar verticalmente e negligenciar o JOIN de reconstituição, degradando o caminho de leitura completo; prática: medir os planos dos fluxos principais após o corte.
- Erro: usar o particionamento como substituto de indexação e ajuste de consultas; partição mal indexada continua lenta; prática: particionar e indexar são trabalhos complementares, não alternativos.
- Erro: migrar a tabela grande para partições em um único evento sem ensaio; prática: conversão gradual com tabela nova, gatilho ou lógica de gravação dupla e backfill controlado.
- Erro: presumir que o particionamento elimina a necessidade de monitoramento por tabela; prática: métricas por partição e por índice no observabilidade padrão do banco.
O desenho correto começa por diagnosticar qual problema dói: se o sofrimento é de operação e consulta sobre tabela enorme, particionamento horizontal ou vertical no servidor é a resposta certa e barata; se o sofrimento é de capacidade do nó, particionamento ainda ajuda como preparação, mas a solução está no sharding, com todo o seu custo. Particionar cedo e shardar tarde é a máxima que resume a experiência consolidada de times que escalaram bancos relacionais a sério.