Existe um gênero de post circulando que diz que dá pra substituir sua stack inteira por Postgres. Mata o Redis, mata o Kafka, mata o Elasticsearch, mata seu banco vetorial, tudo. Basta usar Postgres.
O gênero está certo na lista de features e errado na realidade operacional. Cada afirmação desses posts é tecnicamente verdadeira. JSONB com índices GIN realmente te dá flexibilidade estilo MongoDB. FOR UPDATE SKIP LOCKED realmente constrói uma queue. pg_trgm realmente faz fuzzy search. pgvector realmente faz busca ANN. O que esses posts não te contam é onde cada feature quebra, o que custa operacionalmente, e quando a ferramenta especializada que você ia eliminar volta a parecer barata.
Este post é a versão que eu queria que alguém tivesse escrito pra mim: uma escada de adoção concreta com números, não um tour de features. Quatro níveis, de 'adotar sem pensar' até 'nem tente'. Cada item tem um threshold aproximado que te diz quando parar.
Nível 1: Adotar Sem Hesitar
Peças load-bearing do Postgres que um engenheiro senior deveria usar desde o dia um. Estáveis, baratas, sem dívida operacional.
JSONB com índices GIN
Dados 'estruturados-mais-flexíveis' vivem aqui. Um perfil de usuário com schema previsível e um blob de preferências? Uma tabela, duas colunas, um índice GIN no blob. Você mantém joins, ACID e schema-on-read no mesmo lugar. A reclamação padrão é overhead de storage (JSONB guarda os nomes de campos em cada linha), mas isso é um problema a centenas de milhões de documentos, não na sua escala pré-Série-B.
pg_trgm para fuzzy text matching
Índices de trigrama resolvem o problema da 'barra de busca que tolera typos' em umas cinco linhas de SQL. É o caso de 90%. Se você está invocando Elasticsearch para alimentar um typeahead sobre 200k produtos, está superengenhando.
Materialized views com REFRESH CONCURRENTLY
Se sua query de dashboard agrega sobre um milhão de linhas e demora seis segundos, a resposta não é contrato com o Snowflake. É uma materialized view refrescada a cada cinco minutos. Com um índice único, dá pra usar REFRESH CONCURRENTLY, que calcula em background e troca as linhas sem bloquear leitores.
pgvector abaixo de 5M de vetores
O próprio benchmark de 2024 da Timescale (rodado pelo fornecedor, 50M de embeddings com 99% de recall) colocou o pgvectorscale (uma extensão separada da Tiger Data construída em cima do pgvector, não o pgvector vanilla) à frente do índice s1 pod storage-optimized do Pinecone: 28ms p95 contra 784ms p95. Abaixo de 5M de vetores com índices HNSW, pgvector não é um compromisso, é a escolha certa. Especialmente verdade quando você precisa de filtros híbridos ('similaridade semântica, mas só documentos desse tenant na última semana'). Filtros híbridos são justamente onde DBs vetoriais especializadas ainda doem.
Índices BRIN mais partitioning declarativo para time-series
Se seus dados são aproximadamente monotônicos (logs, eventos, métricas) e você particiona por dia ou semana, BRIN te dá um índice de uma a três ordens de magnitude menor que um B-tree nos mesmos dados. Onde você cai dentro dessa faixa depende inteiramente de quão de perto a ordem física das linhas acompanha a coluna indexada: correlação quase perfeita te coloca no topo da faixa, uma tabela embaralhada te coloca no fundo e faz do BRIN o índice errado. Range scans continuam rápidos. Disco continua barato.
PostGIS para qualquer coisa geoespacial
PostGIS não é uma alternativa a sistemas GIS comerciais, é o padrão contra o qual esses sistemas frequentemente perdem. Se você vai entregar qualquer coisa que toque em coordenadas, comece aqui e pare de pensar no assunto.
Nível 2: Adotar com Disciplina Operacional Desde o Dia Um
Essas funcionam, mas você tem que prestar atenção. Subir sem a disciplina de suporte, e você vai aprender do jeito difícil.
Queues com FOR UPDATE SKIP LOCKED
A feature SQL mais limpa do Postgres. Transforma uma tabela comum em uma queue de trabalho concorrente. A pegadinha mora em outra camada: churn. Uma queue de alto throughput significa INSERT, UPDATE, DELETE constantes na mesma tabela, o que significa dead tuples, o que significa pressão de VACUUM. Se o autovacuum não der conta, o planner começa a escolher planos ruins, seu 'grab de job instantâneo' vira um seq scan de 400ms, e seus workers parecem quebrados.
Ajuste autovacuum por tabela (derrubar autovacuum_vacuum_scale_factor do padrão 0.2 para 0.02 em tabelas de queue é o padrão da indústria, ou seja, vacuum dispara a 2% de dead tuples em vez de 20%), mantenha transações curtas, monitore a taxa de dead tuples. Não é opcional, é parte de adotar o padrão.
Full-text search com tsvector
Funciona lindamente abaixo de um milhão de documentos em um único idioma. Acima disso, você começa a sentir o que o Elasticsearch realmente te compra: BM25 scoring tunável, analisadores multilíngues reais, busca facetada, fuzzy matching além de trigramas. O threshold de graduação é aproximadamente 1M a 5M de registros, ou a primeira vez que um product manager pede 'relevância igual ao Algolia'.
PgBouncer na frente de tudo
Postgres é processo-por-conexão. Cada conexão é um processo OS de 5MB. max_connections default é 100, e performance degrada acima de 200 a 500. Em produção você vai passar disso mais rápido do que imagina, especialmente com frameworks modernos que abrem conexões lazy em todo canto. PgBouncer não é uma ferramenta 'para depois', é parte da stack base.
PgBouncer tem três modos de pooling: session (uma conexão de servidor por cliente, o mais seguro mas o pool enche rápido), transaction (a conexão de servidor volta pro pool quando cada transação dá commit, a escolha usual pra maioria dos workloads), e statement (volta depois de cada statement, quebra transações, raramente é a escolha certa). Use transaction pooling, pool size na faixa de 20 a 30 por instância de app, monitore a fila de espera. Cuidado: algumas features do Postgres param de funcionar corretamente em transaction pooling: LISTEN/NOTIFY, advisory locks no escopo de sessão, PREPARE no nível SQL. (Prepared statements no nível de protocolo são suportados desde o PgBouncer 1.21 e vêm ligados por padrão desde o 1.24.) Saiba o que você perde antes de adotar esse modo.
Nível 3: Pensar Duas Vezes
Features que parecem ótimas em blog posts e se tornam load-bearing de formas que você vai lamentar.
LISTEN/NOTIFY para pub/sub de alto throughput
Soa de graça porque vem embutido. Aqui está a parte que o tour de features ignora: NOTIFY adquire um lock exclusivo pesado logo antes do commit e o segura até a transação terminar, então transações que emitem um NOTIFY commitam uma de cada vez. Um UPDATE comum que não notifica ninguém nunca encosta nesse lock. Isso parece restrito até o notify cair no seu caminho de escrita mais quente, e aí seu throughput de escrita fica limitado por uma única fila serializada. Mensagens não são persistidas, o limite de payload é 8KB, e o conjunto não funciona com transaction pooling. Use para dashboards internos, admin panels, sinais de invalidação de baixa frequência. Não use como sistema nervoso de um produto de alto tráfego.
Row-Level Security como única camada de autorização
RLS é poderoso, e para apps multi-tenant é genuinamente uma boa ferramenta. A armadilha é performance: cada query que o planner vê tem sua policy RLS inlinada, e se sua policy toca uma coluna não indexada ou uma subquery, um SELECT simples pode virar algo que se lê como join de 14 tabelas. Trate RLS como a rede de segurança e mantenha mesmo assim checks no nível da aplicação nos caminhos críticos. Adote RLS com o mesmo respeito que daria a um trigger de banco: útil, invisível, e capaz de te surpreender.
PostgREST ou pg_graphql como backend inteiro
APIs autogeradas sobre seu schema são uma ferramenta de prototipagem maravilhosa. Cobrem CRUD de forma limpa. No momento em que entra lógica de negócio real (workflows de múltiplos passos, chamadas a APIs externas, regras que vivem em code review), você está escrevendo funções PL/pgSQL. PL/pgSQL não tem stack traces modernos, tooling fraco, disciplina git pobre por padrão, e nenhum language server que valha a pena. O discurso de 'elimine o backend' vira 'toda sua lógica de negócio mora em arquivos .sql aleatórios que ninguém lembra ter escrito'.
Nível 4: Não Faça
Alguns workloads não são o trabalho de uma única instância Postgres, não importa quantas extensões você carregue.
100M+ vetores
Acima desse range, DBs vetoriais dedicadas (Qdrant, Milvus, Weaviate) ganham significativamente em recall por latência e em memory footprint. Se seu sistema RAG passa desse threshold, a especialização é merecida.
Milhões de eventos websocket por segundo
Redis Streams, Kafka e NATS existem pra isso. Postgres não é construído para fan-out de alto throughput nesse ritmo, independentemente do backing store ser memória (Redis) ou log durável (Kafka).
Pub/sub com milhares de listeners concorrentes
LISTEN/NOTIFY topa na prática em torno de 1000 listeners, e degrada bem antes disso sob carga de escrita. Em torno de 100 listeners é onde ele continua sem graça, e esse é o número para o qual projetar. Os mil são a parede, não o alvo.
Writes ativo-ativo multi-região
Postgres tem um primary. Replicação lógica e soluções ativo-ativo existem, mas movem os problemas duros (resolução de conflitos, amplificação de escrita) para sua aplicação. Se você genuinamente precisa escrever em três regiões, o banco não é a camada pra manter simples.
O Modelo Mental
O ponto da escada não é 'Postgres não é suficiente'. O ponto é que Postgres quase sempre é suficiente, por mais tempo do que você imagina, se você adotar com a disciplina operacional que ele espera. A maioria das empresas que adotam um Redis, um Kafka, um Elasticsearch e um vector DB no primeiro ano está pagando por complexidade que ainda não conquistou.
A regra que uso: começar no Nível 1 sem discussão, promover para o Nível 2 só quando tiver maturidade ops pra bancar, tratar o Nível 3 como uma decisão deliberada com plano de graduação, e reconhecer o Nível 4 como 'ferramenta diferente' em vez de 'Postgres maior'.
A resposta pouco sexy para 'o que eu deveria usar?' normalmente é 'Postgres, com cuidado'. Os posts que dizem 'just use Postgres' acertam na direção e erram na facilidade.
Referências
- JSONB Data Type (docs PostgreSQL): https://www.postgresql.org/docs/current/datatype-json.html
- GIN Indexes (docs PostgreSQL): https://www.postgresql.org/docs/current/gin.html
- Full-Text Search com tsvector e tsquery (docs PostgreSQL): https://www.postgresql.org/docs/current/textsearch.html
- REFRESH MATERIALIZED VIEW CONCURRENTLY (docs PostgreSQL): https://www.postgresql.org/docs/current/sql-refreshmaterializedview.html
- SELECT ... FOR UPDATE SKIP LOCKED (docs PostgreSQL): https://www.postgresql.org/docs/current/sql-select.html
- Partitioning de tabelas (docs PostgreSQL): https://www.postgresql.org/docs/current/ddl-partitioning.html
- Índices BRIN (docs PostgreSQL): https://www.postgresql.org/docs/current/brin.html
- Row Security Policies / RLS (docs PostgreSQL): https://www.postgresql.org/docs/current/ddl-rowsecurity.html
- LISTEN e NOTIFY (docs PostgreSQL): https://www.postgresql.org/docs/current/sql-notify.html
- Fila do NOTIFY e o lock de serialização no commit (código-fonte do PostgreSQL, src/backend/commands/async.c): https://github.com/postgres/postgres/blob/master/src/backend/commands/async.c
- Routine Vacuuming e tuning de autovacuum (docs PostgreSQL): https://www.postgresql.org/docs/current/routine-vacuuming.html
- Repositório oficial do pgvector (HNSW desde 0.5.0, agosto de 2023): https://github.com/pgvector/pgvector
- Extensão pg_trgm (trigram matching): https://www.postgresql.org/docs/current/pgtrgm.html
- Site oficial do PostGIS: https://postgis.net/
- Docs do PostgREST: https://postgrest.org/
- pg_graphql (Supabase): https://github.com/supabase/pg_graphql
- Benchmark pgvector vs Pinecone (Timescale, hoje Tiger Data, junho de 2024): https://www.tigerdata.com/blog/pgvector-vs-pinecone
- Postgres Job Queues & Failure By MVCC (Brandur Leach): https://brandur.org/postgres-queues
- Postgres Full-Text Search vs Elasticsearch (ParadeDB): https://www.paradedb.com/blog/elasticsearch-vs-postgres
- PgBouncer: useful, important, and fraught with peril (JP Camara): https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html