2 puntos por GN⁺ 2024-11-04 | 1 comentarios | Compartir por WhatsApp
  • 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 LOCKED de 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/NOTIFY de 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 LOCKED en sentencias SELECT ... 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 a running y devolverla
    • BEGIN
    • SELECT ... FOR UPDATE SKIP LOCKED
    • UPDATE 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 LISTEN y NOTIFY ofrecen funcionalidad Pub/Sub
    • Un cliente de PostgreSQL puede suscribirse con LISTEN a un canal de mensajes específico, que es una cadena arbitraria
    • Cuando otro cliente envía un NOTIFY a ese canal, todos los clientes suscritos reciben la notificación
    • Opcionalmente, se puede adjuntar un mensaje pequeño
  • 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

 
GN⁺ 2024-11-04
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.

    • Si solo un proceso local va a usar Redis dentro de la máquina como caché en memoria, es mejor usar simplemente las estructuras de datos del lenguaje de programación que estés usando.
    • Me da curiosidad qué tan distinto es el overhead de Postgres y Redis cuando se ejecutan localmente. Tampoco entiendo por qué se asume que Postgres no corre en local.
      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.
    • Django trae incorporada una caché con soporte para Redis, y también tiene una opción de caché en memoria, pero está marcada como “no apta para producción”. Esto se debe a que, si hay varias instancias de Django, cada caché en memoria será distinta.
      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
    • Lo que es más rápido que Redis en esta configuración es un hashmap. Tampoco hay razón para que Postgres no pueda correr en el mismo servidor que la app; de hecho, es una configuración bastante común.
    • La sobrediseño y la distribución prematura son problemas reales, pero Redis significa “Remote Dictionary Server”. Su propósito original no es en absoluto correr localmente. Claro que ejecutarlo en local también puede ser una decisión de diseño válida, por ejemplo si el diccionario básico del lenguaje no soporta consultas por rango.
  • 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.

    • Tener una cosa menos que administrar en producción es una ventaja. Empieza con PostgreSQL y agrega sistemas especializados cuando lo necesites por rendimiento, escalado o costo.
  • 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)

    • Estaba buscando una cola de trabajos simple. Huey también está bien, pero ya uso Postgres, y como el trabajo que pongo en la cola ocurre más o menos una vez por hora, algo basado en Redis siempre me pareció excesivo.
    • En el lado de Node también está PG-Boss: https://github.com/timgit/pg-boss
  • 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

    • También hay que ver si la implementación no se rompe cuando las transacciones de larga duración impiden que vacuum haga la eliminación de tuplas
  • 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

    • La diferencia entre Postgres y MySQL aquí es realmente sorprendente
      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
    • Se puede usar pooling de conexiones. Con algo como AWS RDS Proxy se puede usar con unos pocos clics
  • 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

    • La mayoría de las funciones que no necesitas se pueden desactivar. Por ejemplo, puedes crear una tabla como unlogged: https://www.postgresql.org/docs/current/sql-createtable.html...
      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

    • Redis no es particularmente difícil de desplegar. En vez de planear una migración que podría traer efectos secundarios inesperados más adelante, también está bien usar Redis desde el principio