Últimamente circula un género de artículo que te promete reemplazar toda tu stack con Postgres. Cargarte Redis, cargarte Kafka, cargarte Elasticsearch, cargarte tu base vectorial, todo. Just use Postgres.
Aciertan con la lista de features y fallan con lo que cuesta operarlo. Técnicamente, cada afirmación de esos posts es cierta. JSONB con índices GIN sí te da una flexibilidad tipo MongoDB. FOR UPDATE SKIP LOCKED sí te arma una cola. pg_trgm sí te resuelve búsquedas difusas. pgvector sí hace búsqueda ANN. Lo que no te cuentan es dónde revienta cada feature, cuánto te sale en términos operativos, y cuándo la herramienta especializada que ibas a eliminar vuelve a parecer barata.
Esta es la versión que me hubiera gustado que alguien escribiera para mí: una escalera concreta con números, no un tour de features. Cuatro niveles, desde 'adoptar sin pensarlo' hasta 'no lo hagas'. Cada item trae un umbral aproximado que te indica cuándo parar.
Nivel 1: Adoptar sin dudar
Piezas de Postgres que cargan peso y que cualquier senior debería elegir desde el día uno. Estables, baratas, sin deuda operativa.
JSONB con índices GIN
Aquí viven los datos que son estructurados pero a la vez flexibles. ¿Un perfil de usuario con un schema predecible y un blob de preferencias encima? Una tabla, dos columnas, un índice GIN sobre el blob. Te quedas con joins, ACID y schema-on-read en el mismo sitio. La queja de siempre es el overhead de almacenamiento (JSONB guarda los nombres de campos en cada fila), pero eso empieza a doler a cientos de millones de documentos, no a la escala en la que estás antes de una Serie B.
pg_trgm para búsqueda difusa de texto
Los índices de trigramas resuelven en unas cinco líneas de SQL el clásico 'barra de búsqueda que tolera errores de tipeo'. Cubre el 90% de los casos. Si estás levantando Elasticsearch solo para alimentar un typeahead sobre 200k productos, te estás sobreingenierizando.
Vistas materializadas con REFRESH CONCURRENTLY
Si el query de tu dashboard agrega sobre un millón de filas y tarda seis segundos, la respuesta no es firmar un contrato con Snowflake. Es una vista materializada que se refresca cada cinco minutos. Con un índice único le haces REFRESH CONCURRENTLY, que calcula en segundo plano y cambia las filas sin bloquear a los lectores.
pgvector por debajo de 5M de vectores
El propio benchmark de 2024 de Timescale (hecho por el vendor, 50M de embeddings con 99% de recall) mostró a pgvectorscale (una extensión aparte de Tiger Data construida encima de pgvector, no es pgvector vanilla) superando al índice s1 pod storage-optimized de Pinecone: 28ms p95 contra 784ms p95. Por debajo de 5M de vectores, con índices HNSW, pgvector no es un compromiso, es la decisión correcta. Y lo es todavía más cuando necesitas filtros híbridos ('similitud semántica pero solo documentos de este tenant en la última semana'). Justamente ahí, las bases vectoriales especializadas siguen sufriendo.
Índices BRIN más particionamiento declarativo para series temporales
Si tus datos son aproximadamente monotónicos (logs, eventos, métricas) y particionas por día o semana, BRIN te da un índice de uno a tres órdenes de magnitud más pequeño que un B-tree sobre los mismos datos. Dónde caes dentro de ese rango depende por completo de qué tan de cerca el orden físico de las filas sigue a la columna indexada: una correlación casi perfecta te pone en el tope del rango, una tabla desordenada te pone en el piso y convierte a BRIN en el índice equivocado. Los range scans siguen siendo rápidos. El disco sigue siendo barato.
PostGIS para cualquier cosa geoespacial
PostGIS no es la alternativa a los sistemas GIS comerciales, es el estándar contra el que suelen perder. Si vas a enviar algo que toque coordenadas, arranca aquí y deja de darle vueltas.
Nivel 2: Adoptar con disciplina operativa desde el día uno
Funcionan, pero no puedes dormirte. Si las despliegas sin la disciplina que traen atrás, lo vas a aprender por las malas.
Colas con FOR UPDATE SKIP LOCKED
Es la feature SQL más limpia de Postgres. Convierte una tabla normal en una cola de trabajo concurrente. La pega vive en otra capa: el churn. Una cola con throughput alto implica INSERT, UPDATE y DELETE constantes sobre la misma tabla, lo que implica tuples muertos, lo que implica presión sobre VACUUM. Si autovacuum no da abasto, el planner empieza a elegir malos planes, tu 'job grab instantáneo' pasa a ser un seq scan de 400ms, y tus workers parecen rotos.
Ajusta autovacuum tabla por tabla (bajar autovacuum_vacuum_scale_factor del 0.2 por defecto a 0.02 en tablas de cola es el estándar de la industria, lo que dispara vacuum al 2% de tuples muertos en lugar del 20%), mantén las transacciones cortas, monitorea el ratio de tuples muertos. No es opcional, es parte de adoptar el patrón.
Full-text search con tsvector
Funciona de maravilla por debajo del millón de documentos y en un solo idioma. Por encima empiezas a sentir para qué sirve realmente Elasticsearch: scoring BM25 que puedes ajustar, analizadores multilingües de verdad, búsqueda facetada, fuzzy matching más allá de trigramas. El umbral de graduación ronda el rango de 1M a 5M de registros, o la primera vez que un product manager te pide 'relevancia como la de Algolia'.
PgBouncer delante de todo
Postgres es un proceso por conexión. Cada conexión son 5MB de proceso del sistema operativo. El max_connections por defecto es 100, y el rendimiento se degrada a partir de las 200 a 500. En producción vas a pasarte de ese número más rápido de lo que crees, sobre todo con frameworks modernos que abren conexiones perezosas en todos lados. PgBouncer no es una herramienta 'para después', es parte de la stack base.
PgBouncer tiene tres modos de pooling: session (una conexión de servidor por cliente, el más seguro pero el pool se llena rápido), transaction (la conexión de servidor vuelve al pool cuando cada transacción hace commit, la opción habitual para la mayoría de las cargas) y statement (se libera después de cada sentencia, rompe transacciones, casi nunca es la decisión correcta). Usa transaction pooling, tamaño de pool de 20 a 30 por instancia de app, y vigila la cola de espera. Un detalle importante: algunas features de Postgres dejan de funcionar bien en transaction pooling: LISTEN/NOTIFY, advisory locks con scope de sesión, PREPARE a nivel SQL. (Los prepared statements a nivel de protocolo están soportados desde PgBouncer 1.21 y vienen activados por defecto desde la 1.24.) Ten claro qué pierdes antes de elegir el modo.
Nivel 3: Pensarlo dos veces
Features que se ven geniales en los blog posts y que terminan sosteniendo peso de formas que vas a lamentar.
LISTEN/NOTIFY para pub/sub de alto throughput
Suena gratis porque viene integrado. Esta es la parte que el tour de features se salta: NOTIFY toma un lock exclusivo pesado justo antes del commit y lo mantiene hasta que termina la transacción, así que las transacciones que emiten un NOTIFY hacen commit de a una. Un UPDATE común que no notifica a nadie nunca toca ese lock. Suena acotado hasta que el notify queda en tu camino de escritura más caliente, y tu throughput de escritura queda limitado por una única cola serializada. Los mensajes no se persisten, el payload tiene un tope de 8KB, y todo esto no funciona con transaction pooling. Úsalo para dashboards internos, paneles de admin, señales de invalidación de baja frecuencia. No lo uses como sistema nervioso de un producto de alto tráfico.
Row-Level Security como única capa de autorización
RLS es potente, y para apps multi-tenant es genuinamente una buena herramienta. La trampa es de rendimiento: cada query que ve el planner lleva tu policy de RLS inlineada, y si esa policy toca una columna sin índice o una subquery, un simple SELECT puede terminar leyéndose como un join de 14 tablas. Usa RLS como la red de seguridad y mantén de todos modos checks a nivel de aplicación en los caminos críticos. Trata a RLS con el mismo respeto que le darías a un trigger de base de datos: útil, invisible y capaz de sorprenderte.
PostgREST o pg_graphql como backend entero
Las APIs autogeneradas sobre tu schema son una maravilla para prototipar. Cubren CRUD de forma limpia. En el momento en que entra lógica de negocio de verdad (workflows de varios pasos, llamadas a APIs externas, reglas que pasan por code review), ya estás escribiendo funciones en PL/pgSQL. PL/pgSQL no tiene stack traces modernas, su tooling es débil, su disciplina git por defecto es mala, y no hay language server que valga la pena. El discurso de 'elimina el backend' termina siendo 'toda tu lógica de negocio vive en archivos .sql sueltos que nadie recuerda haber escrito'.
Nivel 4: No lo hagas
Hay cargas que no son trabajo para una sola instancia de Postgres, por más extensiones que le metas encima.
100M+ vectores
Por encima de ese rango, las bases vectoriales dedicadas (Qdrant, Milvus, Weaviate) ganan de manera significativa en recall a una latencia dada y en uso de memoria. Si tu sistema de RAG pasa ese umbral, la especialización ya se ganó su lugar.
Millones de eventos websocket por segundo
Para eso existen Redis Streams, Kafka y NATS. Postgres no está hecho para fan-out de alto throughput a esa tasa, da igual que el backing store sea memoria (Redis) o un log durable (Kafka).
Pub/sub con miles de listeners concurrentes
En la práctica, LISTEN/NOTIFY topa cerca de los 1000 listeners, y se degrada bastante antes de llegar ahí bajo carga de escritura. Cerca de 100 listeners es donde se mantiene aburrido, y ese es el número para el que hay que diseñar. Los mil son el muro, no el objetivo.
Escrituras activo-activo multi-región
Postgres tiene un solo primario. Existe la replicación lógica y existen soluciones activo-activo, pero todas mueven los problemas difíciles (resolución de conflictos, amplificación de escrituras) a tu aplicación. Si de verdad necesitas escribir en tres regiones, la base de datos no es la capa que conviene mantener simple.
El modelo mental
La escalera no intenta decirte que 'Postgres no alcanza'. Lo que dice es que Postgres casi siempre alcanza, y por más tiempo del que piensas, si lo adoptas con la disciplina operativa que espera. La mayoría de las empresas que meten un Redis, un Kafka, un Elasticsearch y una base vectorial en su primer año están pagando por complejidad que todavía no se han ganado.
Como guía personal: empiezo siempre en Nivel 1 sin discutir, promuevo al Nivel 2 solo cuando tengo la madurez de ops para sostenerlo, trato al Nivel 3 como una decisión deliberada con plan de graduación, y reconozco al Nivel 4 como 'otra herramienta', no como 'Postgres más grande'.
La respuesta poco sexy a '¿qué uso?' casi siempre es 'Postgres, con cuidado'. Los posts que dicen 'just use Postgres' aciertan con la dirección y se equivocan con la facilidad.
Referencias
- 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 con tsvector y 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
- Particionamiento de tablas (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 y NOTIFY (docs PostgreSQL): https://www.postgresql.org/docs/current/sql-notify.html
- Cola de NOTIFY y el lock de serialización en el commit (fuente PostgreSQL, src/backend/commands/async.c): https://github.com/postgres/postgres/blob/master/src/backend/commands/async.c
- Routine Vacuuming y tuning de autovacuum (docs PostgreSQL): https://www.postgresql.org/docs/current/routine-vacuuming.html
- Repositorio oficial de pgvector (HNSW desde 0.5.0, agosto 2023): https://github.com/pgvector/pgvector
- Extensión pg_trgm (matching por trigramas): https://www.postgresql.org/docs/current/pgtrgm.html
- Sitio oficial de PostGIS: https://postgis.net/
- Docs de PostgREST: https://postgrest.org/
- pg_graphql (Supabase): https://github.com/supabase/pg_graphql
- Benchmark pgvector vs Pinecone (Timescale, ahora Tiger Data, junio 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