¿Hace falta Redis? PostgreSQL ofrece colas, locks y Pub/Sub (2021)
(spin.atomicobject.com)- La combinación común PostgreSQL+Redis en servicios web es práctica, pero algunas funciones como colas de tareas en segundo plano, locks distribuidos y Pub/Sub también pueden resolverse solo con PostgreSQL
FOR UPDATE SKIP LOCKEDde PostgreSQL 9.5 permite omitir filas bloqueadas sin esperarlas, y se usa para implementar colas que evitan que varios workers tomen la misma tarea- Los locks distribuidos a nivel de aplicación pueden crearse con los advisory locks de PostgreSQL, reutilizando su motor interno de locks para fines definidos por la aplicación
LISTEN/NOTIFYde PostgreSQL 9 permite suscribirse a canales de cadenas arbitrarias y enviar notificaciones, por lo que puede usarse como capa Pub/Sub; Rails ActionCable también soporta PostgreSQL por defecto- Redis sigue teniendo ventajas para caching con TTL y manipulación de datos temporales, pero algunos sistemas pueden reducir su dependencia de Redis para bajar costos operativos y complejidad de desarrollo
Roles de Redis que PostgreSQL puede absorber
- Un servicio web típico usa PostgreSQL como almacén de datos y Redis para coordinar colas de tareas en segundo plano o realizar operaciones atómicas limitadas
- Redis en sí es útil, pero algunos roles que cumple en esta combinación pueden reemplazarse solo con funcionalidades de PostgreSQL
Colas de tareas en segundo plano
- Redis se usa con frecuencia para coordinar colas de tareas que entregan trabajo a un pool de workers en segundo plano en servicios web
- Registra las tareas en segundo plano que deben ejecutarse y sus datos de entrada
- Debe garantizar que solo uno de varios workers tome una tarea determinada
- Redis encaja bien en este uso porque ofrece muchas operaciones atómicas sobre estructuras de datos
- Desde PostgreSQL 9.5 se puede usar la opción
SKIP LOCKEDen sentenciasSELECT ... FOR ...- Al especificar esta opción, PostgreSQL ignora las filas que tendrían que esperar la liberación de un lock
- Con
FOR UPDATE SKIP LOCKED, se adquiere implícitamente un lock a nivel de fila sobre las filas devueltas - Gracias a
SKIP LOCKED, no hay posibilidad de quedar bloqueado por locks de otras transacciones - Si hay otra tarea para procesar, esa tarea se devuelve
- Aunque varios workers ejecuten el mismo comando, no reciben la misma fila debido al lock a nivel de fila
- El flujo básico consiste en seleccionar dentro de una transacción una tarea en estado
pending, cambiarla arunningy devolverlaBEGINSELECT ... FOR UPDATE SKIP LOCKEDUPDATE jobs SET status = 'running' ... RETURNING jobs.*COMMIT
- Un punto a tener en cuenta es que, cuando hay muchos workers y muchas tareas, el costo de recorrer la cola intentando adquirir locks puede crecer
- En la mayoría de las apps tratadas en la práctica había menos de 12 workers en segundo plano, por lo que era probable que ese costo no fuera grande
Locks de aplicación
- Hay casos, como una rutina que sincroniza con un servicio de terceros, en los que se necesita que solo una instancia se ejecute para un usuario específico en todos los procesos del servidor
- Estos locks distribuidos son otro caso de uso frecuente de Redis
- PostgreSQL puede lograr el mismo objetivo con
advisory locks- Los advisory locks permiten usar el motor de locks que PostgreSQL utiliza internamente para fines definidos por la aplicación
Pub/Sub
- Redis también se usa a menudo para enviar eventos a clientes activos
- Notificar a un usuario que puede leer un mensaje nuevo
- Hacer streaming al cliente apenas los datos estén listos
- Por lo general, WebSocket es la capa de entrega de eventos y Redis actúa como motor Pub/Sub
- Desde PostgreSQL 9, las sentencias
LISTENyNOTIFYofrecen funcionalidad Pub/Sub- Un cliente de PostgreSQL puede suscribirse con
LISTENa un canal de mensajes específico, que es una cadena arbitraria - Cuando otro cliente envía un
NOTIFYa ese canal, todos los clientes suscritos reciben la notificación - Opcionalmente, se puede adjuntar un mensaje pequeño
- Un cliente de PostgreSQL puede suscribirse con
- Si estás usando Rails y ActionCable, el uso de PostgreSQL está soportado por defecto
Áreas donde vale la pena conservar Redis
- Redis cubre áreas distintas a PostgreSQL y es fuerte en tareas que PostgreSQL no apunta a resolver
- Caching de datos con TTL
- Almacenamiento y manipulación de datos temporales
- PostgreSQL tiene un rango de funcionalidades demasiado amplio como para verlo simplemente como una base de datos SQL o como un componente opaco detrás de un ORM
- Es posible que algunas tareas delegadas a Redis también sean adecuadas para PostgreSQL
- Omitir Redis puede ser una opción para reducir los costos operativos y la complejidad de desarrollo derivados de depender de varios servicios de datos
1 comentarios
Opiniones de Hacker News
Como todos insisten en arquitecturas excesivamente distribuidas, muchas veces no ven la verdadera ventaja de Redis. Si lo ejecutas en la misma máquina que la aplicación, puede responder en mucho menos de 1 milisegundo, y eso permite hacer en la aplicación cosas que serían difíciles con Postgres.
Es cierto que Postgres es excelente, pero no corre en memoria en la misma máquina que la aplicación. Si solo necesitas algo como una cola, quizá no necesites un almacén clave-valor en memoria. El punto central de un almacén clave-valor en memoria es hacer cosas que requieren las características de rendimiento de la RAM, y esas características no se obtienen a través de una conexión de red.
Postgres no tiene ninguna magia especial; es simplemente un programa que corre en otro proceso, igual que Redis. En conexiones locales usa pipes rápidos para reducir la latencia, y también puede usar métodos más rápidos de transferencia masiva de datos. Lo he usado así varias veces.
Pero en casos como herramientas internas de negocio, se puede mantener una sola instancia durante mucho tiempo, y esa caché en memoria las vuelve muy rápidas. django-cachalot es una biblioteca que maneja automáticamente la invalidación de caché cada vez que hay una escritura en una tabla. Es un enfoque algo tosco, pero ofrece mejoras de rendimiento casi sin esfuerzo, y las apps internas con pocas actualizaciones prácticamente corren desde RAM y solo vuelven a consultas normales a la base de datos cuando algo no está en caché.
https://github.com/noripyt/django-cachalot
Aquí hay muchas respuestas defensivas desde la perspectiva de Redis, pero por supuesto hay áreas específicas en las que Redis es mejor.
Aun así, creo que ese no es el punto central del artículo. La frase clave podría resumirse así: “PostgreSQL tiene muchas más funciones de las que esperarías si lo abordas como una simple base de datos SQL o como una entidad misteriosa escondida detrás de un ORM”. Si estás usando cualquier base de datos solo detrás de un ORM, es muy probable que estés desaprovechando funciones. Si necesitas agregar otro servicio como Redis, tal vez convenga usar la base de datos que ya tienes configurada antes que sumar una nueva dependencia.
Está bien entender lo que Postgres puede hacer. Es una base de datos potente.
El contraargumento es que la barrera para usar Redis es muy baja y, a cambio, ofrece alto rendimiento, amplio soporte de bibliotecas y reduce la carga sobre la base de datos principal. Por ejemplo, se puede crear una caché de respuestas de API con Postgres. Para el TTL, basta con un job de cron que limpie los valores de caché antiguos. O simplemente puedes usar Redis.
Los bloqueos consultivos son geniales y útiles, pero si usas algo como PgBouncer, pueden surgir problemas entre los bloqueos consultivos de sesión y el intercalado de transacciones. Un sistema aparte tiene desventajas como llamadas de red, disponibilidad y conocimiento de dominio, pero el compromiso que implica Redis es bastante bajo.
Es un artículo bastante antiguo, pero ahora ya se volvió un patrón muy común. El 90% de los proyectos que solo necesitan una cola de trabajos para enviar correos o generar reportes no procesa millones de mensajes por segundo, así que vale la pena considerar una forma de simplificar el stack.
Usé este patrón con frecuencia para esquivar problemas que tuve con Celery y terminé separándolo en un framework propio: https://github.com/TkTech/chancy Se agradece feedback.
Hay muchas herramientas de este tipo, y algunas son servicios comerciales, así que claramente hay demanda.
https://worker.graphile.org/ (Node.js)
https://riverqueue.com/ (Go)
https://github.com/acaloiaro/neoq (Go)
https://github.com/contribsys/faktory (Go)
https://github.com/sorentwo/oban (Elixir)
https://github.com/procrastinate-org/procrastinate (Python)
PGQueuer usa FOR UPDATE SKIP LOCKED y LISTEN/NOTIFY de PostgreSQL para ofrecer colas de trabajo, bloqueos y notificaciones en tiempo real.
Si ya usas PostgreSQL, es una alternativa minimalista que no requiere Redis.
https://github.com/janbjorge/PGQueuer
Como referencia, lo hice yo.
Me gusta Postgres, pero tiene algunas limitaciones
Si necesitas un almacén clave-valor, hay que evaluar si entiendes autovacuum, si conoces los límites del pool de conexiones y qué prefieres entre throughput y seguridad. Si necesitas una cola, hay que ver si es procesamiento secuencial, si tiene limitación de velocidad, si requiere fan-out o si debe separarse por tema. Si necesitas publicación/suscripción, hay que evaluar si te importan las recepciones duplicadas, si te importa la pérdida de mensajes y si necesitas reproducción. Si necesitas bloqueos, debes conocer los límites del pool de conexiones y statement_timeout. La mayoría de los problemas anteriores se pueden resolver, pero no es tan simple
Un gran obstáculo en la publicación/suscripción de Postgres es que el tamaño máximo del mensaje es de 8000 bytes
La solución alternativa recomendada es poner los datos en una tabla y enviar solo el ID, pero si no quieres conservarlos para siempre, tienes que hacer garbage collection de esos datos, y eso agrega trabajo por cada mensaje. Claro que en algunos casos está bien, pero en muchos casos de uso de Redis esta limitación dificulta considerarlo equivalente
Veamos si pgsql maneja 15 000 conexiones de clientes
Alguien de Planetscale dijo en un podcast que cada instancia de MySQL de GitHub maneja más de 50 000 conexiones. En cambio, si en Postgres necesitas más de 100 conexiones, ya necesitas PgBouncer
Colas, bloqueos y publicación/suscripción son posibles. Pero falta el uso más importante de Redis: el caché
Las actualizaciones en Postgres son notoriamente más caras que las inserciones, generan basura y requieren vacuum. Para caché, las garantías de durabilidad que no son importantes también vuelven las escrituras bastante lentas. La expiración automática es muy cómoda y reduce errores
También se pueden desactivar commits síncronos, autovacuum, etc. Claro que Redis seguirá siendo más rápido, pero puede que la diferencia no sea algo que le importe a una empresa común
Dicho de forma más clara, la idea central del texto es: empieza con Postgres y, si surge la necesidad, muévete a Redis
Conviene mantener la menor cantidad posible de piezas móviles