Estratégias de escalonamento de banco de dados: leitura de réplicas, fragmentação e muito mais
O desempenho do banco de dados é o gargalo em 78% dos problemas de dimensionamento de aplicativos Web. Os aplicativos podem ser dimensionados horizontalmente com esforço mínimo, mas os bancos de dados resistem ao dimensionamento horizontal. As estratégias escolhidas para o dimensionamento do banco de dados determinam se seu aplicativo atende 100 usuários ou 100.000 usuários com desempenho aceitável.
Este guia cobre o espectro completo de estratégias de escalonamento de banco de dados, desde otimizações simples que atrasam a necessidade de escalonamento até técnicas avançadas, como fragmentação horizontal.
Principais conclusões
- Otimize consultas e adicione índices antes de adicionar infraestrutura --- isso resolve 60% dos problemas de desempenho do banco de dados
- As réplicas de leitura são a estratégia de escalonamento de menor risco e lidam com 80% das cargas de trabalho com uso intenso de leitura
- O pool de conexões é obrigatório quando seu aplicativo executa mais de 10 instâncias
- A fragmentação horizontal é um último recurso que introduz uma complexidade significativa de aplicação
A escada de escala
Dimensione nesta ordem. Cada etapa é mais barata e menos arriscada que a seguinte:
Etapa 1: Otimização de consulta (gratuita)
Antes de adicionar infraestrutura, certifique-se de que seu banco de dados existente esteja funcionando de maneira ideal.
-- Find slow queries in PostgreSQL
SELECT
calls,
mean_exec_time::numeric(10,2) AS avg_ms,
total_exec_time::numeric(10,2) AS total_ms,
query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;
Otimizações comuns:
- Adicione índices ausentes para colunas filtradas com frequência
- Substitua
SELECT *por listas de colunas específicas - Use
EXPLAIN ANALYZEpara identificar varreduras sequenciais em tabelas grandes - Adicionar índices compostos para cláusulas WHERE de várias colunas
- Implementar paginação com paginação de conjunto de chaves em vez de
OFFSET
-- Bad: OFFSET pagination (scans all skipped rows)
SELECT * FROM orders ORDER BY created_at DESC LIMIT 20 OFFSET 10000;
-- Good: Keyset pagination (index-only scan)
SELECT * FROM orders
WHERE created_at < '2026-03-15T10:00:00Z'
ORDER BY created_at DESC
LIMIT 20;
Etapa 2: Escala vertical ($)
Aumente a CPU, a RAM e o armazenamento no seu servidor de banco de dados existente. Isso ganha tempo e não requer nenhuma alteração no aplicativo.
| Tamanho da instância | vCPU | memória RAM | Conexões | Custo Mensal (RDS) |
|---|---|---|---|---|
| db.t3.médio | 2 | 4 GB | 100 | US$ 65 |
| db.r6g.large | 2 | 16 GB | 200 | US$ 175 |
| db.r6g.xlarge | 4 | 32 GB | 400 | US$ 350 |
| db.r6g.2xlarge | 8 | 64 GB | 800 | US$ 700 |
A maioria dos aplicativos atinge seu limite de 64 GB de RAM e 8 vCPUs. Além disso, a escala vertical torna-se proibitiva em termos de custos.
Etapa 3: Pool de conexões ($)
Application (50 pods x 20 connections = 1,000 connections)
|
v
PgBouncer (25 database connections, transaction pooling)
|
v
PostgreSQL (25 active connections, manageable)
Configuração do PgBouncer:
[databases]
app = host=db.example.com port=5432 dbname=production
[pgbouncer]
listen_port = 6432
listen_addr = 0.0.0.0
auth_type = md5
pool_mode = transaction
default_pool_size = 25
max_client_conn = 1000
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3
Etapa 4: Ler réplicas ($$)
As réplicas de leitura lidam com consultas SELECT, descarregando de 60 a 90% da carga do banco de dados do primário.
Arquitetura:
Write queries --> Primary database
|
Replication (async)
|
+----+----+
| |
Read queries --> Replica 1 Replica 2
Roteamento em nível de aplicativo (exemplo Drizzle ORM):
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
const primaryPool = new Pool({ connectionString: process.env.DATABASE_URL });
const replicaPool = new Pool({ connectionString: process.env.DATABASE_REPLICA_URL });
export const primaryDb = drizzle(primaryPool);
export const replicaDb = drizzle(replicaPool);
// In service code:
// Write operations use primaryDb
async createOrder(data: OrderInput) {
return primaryDb.insert(orders).values(data).returning();
}
// Read operations use replicaDb
async getOrders(organizationId: string) {
return replicaDb.select().from(orders)
.where(eq(orders.organizationId, organizationId))
.orderBy(desc(orders.createdAt));
}
Considerações sobre atraso de replicação: a replicação assíncrona introduz um atraso (normalmente de 10 a 100 ms). Imediatamente após uma gravação, a leitura da réplica pode retornar dados obsoletos. Use o primário para leituras que seguem gravações no mesmo fluxo de usuário.
Etapa 5: Cache ($$)
O cache do Redis elimina totalmente as consultas repetidas ao banco de dados.
async getProduct(id: string): Promise<Product> {
const cacheKey = `product:${id}`;
// Check cache first
const cached = await this.redis.get(cacheKey);
if (cached) return JSON.parse(cached);
// Cache miss: query database
const product = await this.db.select().from(products)
.where(eq(products.id, id))
.limit(1);
// Cache for 5 minutes
await this.redis.setex(cacheKey, 300, JSON.stringify(product[0]));
return product[0];
}
Estratégia de invalidação de cache: invalidar na gravação. Quando um produto for atualizado, exclua a chave de cache. Use um padrão de cache-aside (o aplicativo gerencia o cache) em vez de write-through (o banco de dados gerencia o cache).
Etapa 6: Fragmentação horizontal ($$$)
A fragmentação distribui dados entre várias instâncias de banco de dados com base em uma chave de fragmento.
| Estratégia de fragmentação | Descrição | Melhor para |
|---|---|---|
| Baseado em hash | Hash a chave de fragmento, distribua uniformemente | Distribuição uniforme de dados |
| Baseado em intervalo | Atribuir intervalos aos fragmentos (por exemplo, AM, NZ) | Séries temporais, dados geográficos |
| Baseado em inquilino | Um fragmento por locatário/organização | SaaS multilocatário |
Quando fragmentar:
- Banco de dados único excede 1 TB e está crescendo
- A taxa de transferência de gravação excede o que um único primário pode suportar
- Os custos de escala vertical excedem US$ 2.000/mês sem margem de manobra
Quando NÃO fragmentar:
- Você não esgotou as etapas 1 a 5
- Seus dados cabem em um único banco de dados de 500 GB
- Consultas entre fragmentos são comuns em seu aplicativo
Otimizações específicas do PostgreSQL
Particionamento (antes da fragmentação)
O particionamento de tabelas do PostgreSQL divide tabelas grandes em tabelas físicas menores, mantendo uma única tabela lógica:
-- Partition orders by month
CREATE TABLE orders (
id UUID PRIMARY KEY,
organization_id UUID NOT NULL,
created_at TIMESTAMP NOT NULL,
total DECIMAL(10,2)
) PARTITION BY RANGE (created_at);
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');
O particionamento melhora o desempenho de consultas de intervalo de tempo em 10 a 100x em tabelas grandes porque o PostgreSQL verifica apenas partições relevantes.
Aspiração e Manutenção
-- Check table bloat
SELECT
schemaname,
relname,
n_live_tup,
n_dead_tup,
round(n_dead_tup::numeric / greatest(n_live_tup, 1) * 100, 2) AS dead_pct
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;
Configure o autovacuum agressivamente para tabelas com alta gravação:
ALTER TABLE orders SET (
autovacuum_vacuum_threshold = 100,
autovacuum_vacuum_scale_factor = 0.05,
autovacuum_analyze_threshold = 50,
autovacuum_analyze_scale_factor = 0.02
);
Monitorando o desempenho do banco de dados
Acompanhe essas métricas para entender quando e como escalar:
| Métrica | Ferramenta | Limite de alerta |
|---|---|---|
| Latência de consulta (P95) | pg_stat_statements | >500ms |
| Conexões ativas | pg_stat_atividade | >80% do máximo |
| Proporção de acertos de cache | pg_stat_database | <95% |
| Atraso na replicação | pg_stat_replicação | >1 segundo |
| Inchaço de mesa | pg_stat_user_tables | >20% de tuplas mortas |
| Espera de E/S de disco | iostat/CloudWatch | >20ms |
Uma taxa de acertos de cache abaixo de 95% é o indicador mais forte de que você precisa de mais memória. Aumentar shared_buffers e effective_cache_size costuma ser mais barato e rápido do que adicionar réplicas de leitura.
Acompanhamento de desempenho de consulta
-- Enable pg_stat_statements (postgresql.conf)
-- shared_preload_libraries = 'pg_stat_statements'
-- Find the top 10 most time-consuming queries
SELECT
queryid,
calls,
mean_exec_time::numeric(10,2) AS avg_ms,
total_exec_time::numeric(10,2) AS total_ms,
rows,
query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
Revise as 10 principais consultas semanalmente. Otimizar até mesmo uma única consulta executada com frequência pode reduzir a carga geral do banco de dados em 10 a 30%.
Perguntas frequentes
Monitore três métricas: latência de consulta P95 (alerta em 500ms), utilização de conexão (alerta em 80%) e utilização de CPU (alerta em 70% sustentado). Se você atinge esses limites regularmente, passe para o próximo degrau na escada de escala. Não pré-otimize --- dimensione quando os dados solicitarem.
Comece com o cache. O cache Redis é mais simples de implementar, elimina mais carga (as ocorrências de cache ignoram totalmente o banco de dados) e custa menos. Adicione réplicas de leitura quando a taxa de acertos do cache já estiver acima de 80%, mas o banco de dados primário ainda estiver sob pressão por falhas de cache e operações de gravação.
Odoo usa exclusivamente PostgreSQL. Comece com a otimização de consultas (Odoo gera consultas complexas para relatórios). Adicione PgBouncer para pool de conexões quando você exceder 50 usuários simultâneos. Use réplicas de leitura para consultas de relatórios (configure a opção --db-replica do Odoo). ECOSIRE fornece otimização de desempenho Odoo incluindo ajuste de banco de dados.
Sim, para a maioria das empresas. Os bancos de dados gerenciados lidam com backups, patches, failover e monitoramento automatizados. O custo adicional de 30-40% em relação ao PostgreSQL autogerenciado é compensado pelo tempo de engenharia que você economiza. A exceção são as implantações em larga escala, nas quais o custo adicional de uma instância grande excede o custo de um DBA de meio período.
O que vem a seguir
O dimensionamento de banco de dados é um componente de uma estratégia mais ampla de dimensionamento de infraestrutura. Combine-o com otimização de CDN para ativos estáticos, escalonamento automático do Kubernetes para pods de aplicativos e teste de carga para validar suas decisões de escalonamento em condições realistas.
Entre em contato com a ECOSIRE para obter consultoria de otimização de banco de dados ou consulte nosso guia DevOps para obter o roteiro completo de infraestrutura.
Publicado pela ECOSIRE – ajudando as empresas a dimensionar a infraestrutura de dados com confiança.
Escrito por
ECOSIRE Research and Development Team
Construindo produtos digitais de nível empresarial na ECOSIRE. Compartilhando insights sobre integrações Odoo, automação de e-commerce e soluções de negócios com IA.
Artigos Relacionados
Padrões de gateway de API e práticas recomendadas para aplicativos modernos
Implemente padrões de gateway de API, incluindo limitação de taxa, autenticação, roteamento de solicitações, disjuntores e controle de versão de API para arquiteturas web escaláveis.
Otimização de desempenho de CDN: o guia completo para entrega global mais rápida
Otimize o desempenho da CDN com estratégias de cache, computação de ponta, otimização de imagens e arquiteturas multi-CDN para entrega mais rápida de conteúdo global.
Práticas recomendadas para pipeline de CI/CD: automatize seu caminho para implantações confiáveis
Crie pipelines de CI/CD confiáveis com práticas recomendadas para testes, preparação, automação de implantação, estratégias de reversão e verificação de segurança em fluxos de trabalho de produção.