Blog Developer Experience

Muito prazer, Backstage.io — uma nova forma de criar portais para times de desenvolvimento

Gabriel Dantas

Platform Engineer / SRE

Jun 7, 2022 6 min leitura
Muito prazer, Backstage.io — uma nova forma de criar portais para times de desenvolvimento

Introdução

Em 2023, o time de plataforma da empresa onde trabalhava enfrentou o que toda equipe de backend teme: nosso PostgreSQL principal havia crescido para 10TB de dados e as queries começaram a degradar. Não linearmente — exponencialmente.

Neste artigo, vou documentar o que funcionou, o que não funcionou, e o que aprendi operando Postgres em escala real. Não é teoria — é um post-mortem com lições concretas.

"Você não tem problema de banco de dados. Você tem problema de design de schema que se manifestou no banco de dados." — conselho que recebi tarde demais.

O Pesadelo do Particionamento

Nossa tabela events tinha 4.2 bilhões de linhas. Queries simples por range de data levavam 45 segundos. A solução óbvia parecia ser particionamento — mas a implementação foi tudo menos óbvia.

O Postgres 14 introduziu melhorias significativas no Declarative Partitioning. A sintaxe é elegante:

CREATE TABLE events (
  id        BIGSERIAL,
  user_id   BIGINT NOT NULL,
  event_at  TIMESTAMPTZ NOT NULL,
  payload   JSONB
) PARTITION BY HASH (user_id);

CREATE TABLE events_p0 PARTITION OF events
  FOR VALUES WITH (MODULUS 8, REMAINDER 0);
-- ... repeat for p1 through p7

O problema é que migrar uma tabela existente com dados para um modelo particionado exige zero downtime com logical replication. Fizemos isso em 4 dias com uma janela de manutenção de apenas 30 segundos.

Indexação em Larga Escala

Com o particionamento resolvido, o próximo gargalo foram os índices. Índices B-Tree em colunas de timestamp em tabelas de 500GB são caros de manter e lentos para queries de range.

A solução foi trocar para índices BRIN (Block Range INdexes) para colunas timestamp naturalmente ordenadas:

-- B-Tree: 12GB por partição
CREATE INDEX idx_events_at ON events (event_at);

-- BRIN: 150KB por partição, igualmente efetivo para range queries
CREATE INDEX idx_events_at_brin ON events
  USING BRIN (event_at) WITH (pages_per_range = 128);

Demo: EXPLAIN ANALYZE comparando B-Tree vs BRIN

8:23 min

Lições Aprendidas

  • Configure autovacuum_vacuum_scale_factor = 0.01 para tabelas grandes — o default de 0.2 é mortal
  • Use PgBouncer em modo transaction pooling antes de qualquer otimização de query
  • Monitore pg_stat_user_tables diariamente — dead tuples acumulam silenciosamente
  • Índices parciais salvam vidas: WHERE deleted_at IS NULL reduz 60% do tamanho
  • EXPLAIN (ANALYZE, BUFFERS) é seu melhor amigo — leia os hit/miss ratios de buffers
Gabriel Dantas

Platform Engineer / SRE

Platform Engineer & SRE com 6+ anos em Developer Experience e Platform Engineering. Palestrante no DevopsDays SP, Platform Days, DevPR Config e DevOps Summit. github.com/gabriel-dantas98

Artigos Relacionados