Redis est-il nécessaire ? PostgreSQL fournit la mise en file d’attente, les verrous et le Pub/Sub (2021)
(spin.atomicobject.com)- Dans les services web, la combinaison courante PostgreSQL+Redis est pratique, mais certaines fonctions parmi la file de tâches en arrière-plan, les verrous distribués et le Pub/Sub peuvent être gérées avec PostgreSQL seul
- Le
FOR UPDATE SKIP LOCKEDde PostgreSQL 9.5 ignore les lignes verrouillées au lieu d’attendre leur déverrouillage, ce qui permet d’implémenter une file empêchant plusieurs workers de récupérer la même tâche - Les verrous distribués au niveau applicatif peuvent être construits avec les advisory locks de PostgreSQL, en réutilisant le moteur de verrouillage interne pour des besoins définis par l’application
- Les
LISTEN/NOTIFYde PostgreSQL 9 prennent en charge l’abonnement à des canaux de chaînes arbitraires et l’envoi de notifications, ce qui permet de les utiliser comme couche Pub/Sub, et Rails ActionCable prend aussi PostgreSQL en charge par défaut - Redis reste très solide pour le cache avec TTL et la manipulation de données temporaires, mais certains systèmes peuvent réduire leur dépendance à Redis afin de diminuer les coûts d’exploitation et la complexité de développement
Rôles de Redis pouvant être absorbés par PostgreSQL
- Un service web classique utilise PostgreSQL comme stockage de données et Redis pour coordonner les files de tâches en arrière-plan ou pour certaines opérations atomiques limitées
- Redis est utile en soi, mais dans cette combinaison, certains rôles qu’il assume peuvent être remplacés uniquement par des fonctionnalités de PostgreSQL
File de tâches en arrière-plan
- Redis est souvent utilisé dans les services web pour la coordination de files de tâches vers un pool de workers en arrière-plan
- enregistrer les tâches à exécuter en arrière-plan ainsi que leurs données d’entrée
- garantir qu’une seule tâche est récupérée par un seul worker parmi plusieurs
- Redis s’y prête bien grâce à ses nombreuses opérations atomiques sur les structures de données
- Depuis PostgreSQL 9.5, il est possible d’utiliser l’option
SKIP LOCKEDdans les instructionsSELECT ... FOR ...- avec cette option, PostgreSQL ignore les lignes pour lesquelles il faudrait attendre la libération du verrou
- avec
FOR UPDATE SKIP LOCKED, un verrou au niveau ligne est implicitement acquis sur les lignes retournées - grâce à
SKIP LOCKED, il n’y a pas de risque de blocage sur les verrous détenus par d’autres transactions - s’il existe d’autres tâches à traiter, c’est l’une d’elles qui sera renvoyée
- même si plusieurs workers exécutent la même commande, ils ne recevront pas la même ligne grâce au verrouillage au niveau ligne
- Le flux de base consiste à sélectionner, dans une transaction, une tâche au statut
pending, à la passer enrunning, puis à la renvoyerBEGINSELECT ... FOR UPDATE SKIP LOCKEDUPDATE jobs SET status = 'running' ... RETURNING jobs.*COMMIT
- Point d’attention : lorsque le nombre de workers et de tâches est élevé, le coût du parcours de la file en tentant d’acquérir des verrous peut devenir important
- dans la plupart des applications réellement rencontrées, il y avait moins de 12 workers en arrière-plan, donc ce coût a de fortes chances de rester limité
Verrous applicatifs
- Dans des routines comme la synchronisation avec un service tiers, il peut être nécessaire de garantir qu’une seule instance s’exécute pour un utilisateur donné sur l’ensemble des processus serveur
- C’est un autre cas d’usage fréquent de Redis pour les verrous distribués
- PostgreSQL permet d’atteindre le même objectif via les
advisory locks- les advisory locks permettent de réutiliser, pour des besoins définis par l’application, le moteur de verrouillage que PostgreSQL utilise en interne
Pub/Sub
- Redis est aussi souvent utilisé pour pousser des événements vers des clients actifs
- notifier l’utilisateur qu’il peut lire un nouveau message
- diffuser des données vers le client au fil de leur disponibilité
- en général, les WebSockets servent de couche de transport des événements et Redis joue le rôle de moteur Pub/Sub
- Depuis PostgreSQL 9, les instructions
LISTENetNOTIFYfournissent des fonctions Pub/Sub- un client PostgreSQL peut s’abonner avec
LISTENà un canal de messages identifié par une chaîne arbitraire - lorsqu’un autre client envoie un
NOTIFYsur ce canal, tous les clients abonnés reçoivent la notification - il est aussi possible d’y joindre un petit message
- un client PostgreSQL peut s’abonner avec
- Si vous utilisez Rails et ActionCable, la prise en charge de PostgreSQL est fournie par défaut
Domaines où Redis garde sa place
- Redis couvre des besoins différents de PostgreSQL et reste fort sur des tâches que PostgreSQL ne vise pas
- le cache de données avec TTL
- le stockage et la manipulation de données temporaires
- PostgreSQL offre un éventail de fonctionnalités trop large pour le considérer comme une simple base SQL ou un composant opaque derrière un ORM
- Une partie des tâches confiées à Redis peut aussi convenir à PostgreSQL
- Se passer de Redis peut être une option pour réduire les coûts d’exploitation et la complexité de développement liés à la dépendance à plusieurs services de données
1 commentaires
Avis sur Hacker News
Comme tout le monde insiste sur des architectures excessivement distribuées, on passe souvent à côté des vrais avantages de Redis. S’il tourne sur la même machine que l’application, il peut répondre en bien moins d’une milliseconde, ce qui permet de faire côté application des choses difficiles à réaliser avec Postgres.
Postgres est excellent, c’est vrai, mais il ne tourne pas en mémoire sur la même machine que l’application. Si vous n’avez besoin que de choses comme une file d’attente, vous n’avez peut-être pas besoin d’un stockage clé-valeur en mémoire. Le cœur d’un stockage clé-valeur en mémoire, c’est de faire des choses qui nécessitent les caractéristiques de performance de la RAM, et on ne les obtient pas au-delà d’une connexion réseau.
Il n’y a pas de magie particulière dans Postgres : c’est simplement un programme qui tourne dans un autre processus, comme Redis. Pour les connexions locales, il utilise des pipes rapides afin de réduire la latence, et on peut aussi employer des modes de transfert de données en masse plus rapides. Je l’ai utilisé ainsi à plusieurs reprises.
Mais pour des outils métiers internes, on peut conserver longtemps une instance unique et la faire évoluer, et ce cache en mémoire les rend très rapides. django-cachalot est une bibliothèque qui gère automatiquement l’invalidation du cache à chaque écriture dans une table. C’est une approche un peu brutale, mais elle apporte un gain de performance avec presque aucun effort ; pour les applications métiers internes peu mises à jour, elles tournent en pratique depuis la RAM et ne reviennent aux requêtes classiques en base de données qu’en cas d’absence dans le cache.
https://github.com/noripyt/django-cachalot
La discussion ici contient beaucoup d’arguments défensifs du point de vue de Redis, mais bien sûr, Redis est meilleur dans certains domaines.
Cela dit, je ne pense pas que ce soit le cœur de l’article. La phrase clé pourrait se résumer ainsi : « PostgreSQL possède bien plus de fonctionnalités que ce à quoi on s’attend quand on l’aborde comme une simple base SQL ou comme une entité mystérieuse cachée derrière un ORM ». Si vous n’utilisez une base de données qu’à travers un ORM, vous passez probablement à côté de fonctionnalités, quelle que soit la base. Si vous devez ajouter un autre service comme Redis, il peut être préférable d’utiliser la base déjà configurée plutôt que d’ajouter une nouvelle dépendance.
Comprendre ce que Postgres peut faire, c’est une bonne chose. C’est une base de données puissante.
Le contre-argument, c’est que la barrière à l’adoption de Redis est très basse et qu’en échange on obtient de hautes performances, un riche support de bibliothèques et une réduction de la charge sur la base principale. Par exemple, on peut construire un cache de réponses d’API avec Postgres. Pour le TTL, il suffit qu’une tâche cron nettoie les anciennes valeurs du cache. Ou alors on utilise simplement Redis.
Les verrous consultatifs sont élégants et utiles, mais si l’on veut utiliser quelque chose comme PgBouncer, des problèmes peuvent apparaître entre les verrous consultatifs de session et l’entrelacement des transactions. Un système séparé a des inconvénients, comme les appels réseau, la disponibilité et les connaissances métier nécessaires, mais le compromis de Redis reste assez léger.
C’est un article assez ancien, mais c’est désormais un schéma très courant. Dans 90 % des projets qui n’ont besoin que d’une file de travaux pour envoyer des e-mails ou générer des rapports, il n’y a pas des millions de messages par seconde à traiter ; simplifier la stack vaut donc la peine d’être envisagé.
J’ai souvent utilisé ce schéma pour contourner les problèmes rencontrés avec Celery, puis je l’ai extrait dans un framework séparé : https://github.com/TkTech/chancy Les retours sont les bienvenus.
Il existe beaucoup d’outils de ce genre, et certains sont des services commerciaux, ce qui montre qu’il y a une demande réelle.
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 utilise FOR UPDATE SKIP LOCKED et LISTEN/NOTIFY de PostgreSQL pour fournir une file de travaux, des verrous et des notifications en temps réel.
C’est une alternative minimaliste qui évite Redis si vous utilisez déjà PostgreSQL.
https://github.com/janbjorge/PGQueuer
À noter : c’est moi qui l’ai créé.
J’aime Postgres, mais il a quelques limites
Si vous avez besoin d’un magasin clé-valeur, il faut vous demander si vous comprenez autovacuum, les limites du pool de connexions, et ce que vous privilégiez entre débit et sûreté. Si vous avez besoin d’une file, il faut voir s’il s’agit d’un traitement séquentiel, s’il y a une limitation de débit, du fan-out, ou une séparation par sujet. Si vous avez besoin de publication/abonnement, il faut déterminer si les réceptions en double vous posent problème, si la perte de messages vous inquiète, et si vous avez besoin de rejouer les messages. Si vous avez besoin de verrous, il faut connaître les limites du pool de connexions et statement_timeout. La plupart des problèmes ci-dessus peuvent être résolus, mais ce n’est pas si simple
Le gros obstacle du mécanisme publication/abonnement de Postgres est que la taille maximale d’un message est de 8000 octets
Le contournement recommandé consiste à placer les données dans une table et à n’envoyer que l’ID, mais si vous ne voulez pas les conserver indéfiniment, il faut faire du garbage collection sur ces données, ce qui ajoute du travail pour chaque message. Bien sûr, cela peut convenir dans certains cas, mais pour de nombreux usages de Redis, cette limite rend difficile de les considérer comme équivalents
Voyons si pgsql gère 15 000 connexions clientes
Quelqu’un de chez Planetscale a dit dans un podcast que les instances MySQL de GitHub gèrent chacune plus de 50 000 connexions. À l’inverse, avec Postgres, si vous avez besoin de plus de 100 connexions, vous avez déjà besoin de PgBouncer
Les files, les verrous et la publication/abonnement sont possibles. Mais l’usage le plus important de Redis, le caching, manque à l’appel
Les mises à jour dans Postgres sont notoirement plus coûteuses que les insertions, génèrent des déchets et nécessitent vacuum. L’écriture est aussi sensiblement ralentie par des garanties de durabilité qui n’ont pas d’importance pour du caching. L’expiration automatique est très pratique et réduit les erreurs
On peut aussi désactiver les commits synchrones, autovacuum, etc. Bien sûr, Redis restera plus rapide, mais l’écart n’est peut-être pas suffisant pour qu’une entreprise classique s’en soucie
Pour exprimer plus clairement l’idée de l’article : commencez avec Postgres, puis migrez vers Redis si le besoin apparaît
Il vaut mieux garder aussi peu de composants en mouvement que possible