Crear sistemas en tiempo real sobre PostgreSQL: más allá del ORM

Equipo de Atlas Verified
·
13 de febrero de 2026
·
8 min de lectura
Crear sistemas en tiempo real sobre PostgreSQL: más allá del ORM

Cuando su aplicación necesita actualizaciones en tiempo real, la respuesta habitual en 2025 es un servicio gestionado: Firebase Realtime Database, Supabase Realtime, Pusher, Ably o una capa WebSocket alojada. Abstraen la complejidad de enviar actualizaciones a clientes conectados. Funcionan bien, hasta que dejan de hacerlo.

En Atlas Verified empezamos con una plataforma gestionada de bases que incluía suscripciones en tiempo real. Al crecer, alcanzamos los límites de la abstracción. Restricciones de grupos de conexiones, fiabilidad en tiempo real, consultas espaciales PostGIS y deseo de lógica personalizada de disparadores nos empujaron a controlar la capa de bases.

Migramos a PostgreSQL autogestionado y creamos el sistema en tiempo real con su mecanismo nativo LISTEN/NOTIFY junto con Server-Sent Events. Esta publicación explica la decisión, la arquitectura y lo aprendido.

Por qué dejamos la plataforma gestionada

Las plataformas gestionadas ofrecen una propuesta atractiva: autenticación, seguridad por filas, suscripciones en tiempo real, API autogeneradas y panel, listos para usar. Para productos iniciales es valioso. Puede crear una aplicación funcional sin escribir código de servidor.

Los problemas aparecen al madurar la aplicación y alejarse los requisitos de los supuestos de la plataforma.

Límites de conexiones. Las plataformas suelen dirigir conexiones a través de un agrupador con límites propios. Con varios servicios de servidor que necesitan sus grupos —API, trabajadores de fondo, escucha en tiempo real—, agota rápidamente conexiones. Terminamos diseñando soluciones a límites del agrupador en vez de resolver problemas del producto.

Fiabilidad en tiempo real. El sistema en tiempo real de la plataforma gestionada usaba captura de cambios en el registro de escritura anticipada de PostgreSQL. Es elegante, pero introducía fallos fuera de nuestro control. La replicación WAL tiene latencia inherente. A veces el servicio se retrasaba y entregaba actualizaciones obsoletas. Reconectar tras interrupciones era poco fiable. Depurar exigía acceso a infraestructura que no controlábamos.

Requisitos de extensiones. Nuestra aplicación procesa documentos con datos geográficos: puertos, rutas y posiciones de buques. PostGIS, extensión espacial de PostgreSQL, es esencial. La plataforma lo admitía, pero necesitábamos versiones y configuraciones específicas no siempre disponibles.

Lógica personalizada de disparadores. Necesitábamos disparadores que emitieran eventos específicos, además de avisos genéricos de «fila modificada». Cuando cambia el estado de procesamiento, debemos notificar al cliente conectado que cargó el documento. La plataforma operaba por tabla; necesitábamos por entidad.

Migrar fue una decisión pragmática, no de coste o principio: dedicábamos más tiempo a sortear restricciones que el necesario para crear las funciones.

LISTEN/NOTIFY: publicación/suscripción oculta de PostgreSQL

PostgreSQL incluye un mecanismo de publicación/suscripción desde la versión 9.0, lanzada en 2010. Pese a tener más de 15 años, LISTEN/NOTIFY se usa sorprendentemente poco. La mayoría de desarrolladores no sabe que existe.

El mecanismo es simple. Cualquier conexión puede ejecutar LISTEN en un canal con nombre para suscribirse. Cualquier conexión puede ejecutar NOTIFY y enviar un mensaje a todos los oyentes del canal. El contenido es una cadena limitada a 8,000 bytes.

Qué hace potente LISTEN/NOTIFY para aplicaciones en tiempo real:

Sin sobrecarga de infraestructura. No hay intermediario de mensajes separado que implementar, supervisar o escalar. Si tiene PostgreSQL, tiene publicación/suscripción. Sin Redis, RabbitMQ ni Kafka para este caso concreto.

Garantías transaccionales. Los mensajes NOTIFY solo se entregan al confirmarse la transacción contenedora. Si inserta una fila y notifica, pero revierte la transacción, no se entrega el aviso. Esta propiedad no se obtiene de intermediarios externos sin orquestación cuidadosa.

Integración con disparadores. Los disparadores PostgreSQL pueden llamar pg_notify al ejecutarse. Así la base notifica automáticamente cambios de datos específicos sin código de aplicación en la ruta de escritura.

Las limitaciones son reales y conviene comprenderlas:

Sin persistencia. Si nadie escucha al emitirse NOTIFY, se pierde el mensaje. Sin repetición, cola ni garantías para clientes desconectados. Los oyentes deben gestionar reconexión y recuperación de estado.

Tamaño del contenido. El límite de 8,000 bytes impide enviar objetos grandes. Enviamos contenido mínimo —tabla, operación y clave primaria— y dejamos al cliente obtener datos completos si necesita.

Ámbito de una sola base. LISTEN/NOTIFY funciona en una instancia PostgreSQL. No abarca réplicas ni clústeres. Para arquitecturas con todas las escrituras en un único primario, no es una limitación.

Nuestra arquitectura: disparadores, SSE y multiplexación de canales

Así encajan las piezas.

Disparadores de base de datos se instalan en tablas donde importan actualizaciones en tiempo real: documentos, mensajes, conversaciones, notificaciones, verificaciones de envíos y más. Al insertar, actualizar o eliminar una fila, llaman pg_notify con JSON que contiene tabla, operación, clave primaria y columnas de filtro.

Una conexión de escucha dedicada en nuestro servidor API ejecuta LISTEN en el canal y recibe eventos de disparadores. Es una conexión PostgreSQL dedicada y duradera, separada del grupo de consultas, que solo escucha notificaciones.

Un bus de eventos recibe avisos del oyente y los dirige a clientes según sus suscripciones. Un cliente suscrito a documentos de un ID de envío concreto solo recibe avisos de ese envío.

Server-Sent Events entrega notificaciones al navegador. Cada cliente abre una conexión SSE al servidor API indicando canales y filtros. El bus coteja avisos entrantes con suscripciones y envía eventos coincidentes por las conexiones correspondientes.

Por qué SSE frente a WebSockets

Es una decisión con la que seguimos satisfechos. Server-Sent Events es más simple en toda dimensión relevante para nuestro uso.

Unidireccional por diseño. Necesitamos servidor a cliente: el servidor avisa cambios de datos. El cliente no necesita mensajes en tiempo real al servidor; usa HTTP estándar. El modelo unidireccional SSE coincide con el requisito real.

HTTP nativo. Las conexiones SSE son solicitudes HTTP estándar con un tipo de contenido concreto. Funcionan con todos los proxies, balanceadores y CDN HTTP sin configuración especial. WebSockets requiere HTTP Upgrade, que algunos proxies gestionan mal y ciertos cortafuegos empresariales bloquean.

Reconexión automática. La API EventSource del navegador gestiona reconexión nativamente. Si cae, reconecta automáticamente con espera exponencial. Con WebSockets debe implementar esa lógica y equivocarse al menos una vez.

Simplemente no necesitamos el área donde gana WebSockets: comunicación bidireccional. Para mensajes de cliente a servidor en tiempo real, WebSockets es adecuado. Para notificaciones de servidor a cliente, SSE lo es.

Crear una capa de consultas sin ORM

Al dejar la plataforma, dejamos también su API autogenerada. Necesitábamos una capa de acceso a bases para nuestro servidor API.

Las opciones habituales son ORM como Prisma, TypeORM o Drizzle, o SQL directo con controlador. Elegimos una tercera: un constructor ligero de consultas con API encadenable que genera y ejecuta SQL directo.

API familiar. El código existente usaba la biblioteca cliente de la plataforma. Una API compatible minimizó cambios al migrar. Las consultas anteriores podían usar nuestro constructor con sintaxis idéntica.

Sin magia. Los ORM generan SQL que no ve y a veces no controla. Si una consulta es lenta, debe leer SQL generado que suele parecerse poco a la llamada original. Nuestro constructor genera SQL predecible: cada método corresponde a una cláusula y siempre puede inspeccionarlo.

Uniones integradas. Los datos comerciales son relacionales. Un envío tiene documentos, con nodos y comprobaciones. Expresar selecciones anidadas y recibir resultados anidados sin escribir JOIN complejos cada vez mejora significativamente la experiencia de desarrollo.

El resultado es una capa de unos cientos de líneas que gestiona la gran mayoría de interacciones y produce SQL siempre legible y optimizable.

Presupuesto de grupos de conexiones

Al autogestionar PostgreSQL, controla el presupuesto de conexiones. max_connections define el límite absoluto y cada conexión consume memoria.

Descubrimos que considerar conexiones como presupuesto, un recurso finito compartido, previene problemas que afectan a equipos sin planificación.

Cada servicio declara su máximo de conexiones. La suma debe quedar muy por debajo del límite PostgreSQL, dejando margen para sesiones psql, migraciones y supervisión. Parece obvio, pero la mayoría espera a agotar conexiones en producción. Presupuestarlas desde el principio nos evitó esa alerta de las 3 AM.

Migraciones seguras a escala

Migrar de plataforma gestionada a PostgreSQL propio implicó cientos de tablas, funciones, disparadores e índices. Pero el mayor reto fue crear un sistema de migraciones que siguiera funcionando al evolucionar la aplicación.

Nuestro sistema automático funciona así: archivos con marcas de tiempo en un directorio dedicado. El ejecutor lo examina, compara con una tabla que registra migraciones aplicadas y ejecuta las pendientes en orden. Cada una va en transacción: si falla, revierte limpiamente y bloquea el despliegue hasta corregirla.

El ejecutor funciona automáticamente en cada despliegue antes de iniciar la aplicación. Los cambios de esquema son parte del despliegue, sin pasos manuales. Un desarrollador crea un archivo SQL con marca de tiempo, lo envía al repositorio y el siguiente despliegue lo aplica.

Decisiones clave de seguridad: protecciones idempotentes para no fallar tras aplicación parcial. Cada migración debe ser un script SQL autónomo sin intervención humana. Si falla, revierte toda la transacción: la aplicación no arranca con cambios parciales.

Lo que ganamos

Pasar a PostgreSQL propio con tiempo real personalizado requirió gran inversión de ingeniería. Pero los beneficios fueron sustanciales:

Fiabilidad. Los avisos llegan en milisegundos tras el cambio. Sin retraso de replicación WAL ni caídas externas en la ruta crítica.

Facilidad de depuración. Podemos rastrear todo: disparador, notificación, bus y entrega SSE. Cada paso está en nuestro código, registros e infraestructura.

Flexibilidad. Añadir tiempo real a una tabla nueva es una migración SQL que instala un disparador. Sin configurar plataforma, paneles externos ni solicitudes de soporte.

Rendimiento. Las conexiones directas PostgreSQL con un grupo optimizado superan a las enrutadas por infraestructura gestionada. La latencia bajó significativamente tras migrar.

La contrapartida es responsabilidad operativa. Gestionamos la base, copias de seguridad, replicación y capacidad. Para un equipo cómodo con PostgreSQL es manejable. Para otros, las abstracciones gestionadas pueden compensar sus límites.


Atlas Verified funciona sobre PostgreSQL autogestionado con infraestructura propia en tiempo real y procesa miles de verificaciones diarias de cumplimiento en la cadena mundial.

¿Le gustó este artículo?

Compártalo con su red