2 points par GN⁺ 2024-11-04 | 1 commentaires | Partager sur WhatsApp
  • 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 LOCKED de 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/NOTIFY de 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 LOCKED dans les instructions SELECT ... 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 en running, puis à la renvoyer
    • BEGIN
    • SELECT ... FOR UPDATE SKIP LOCKED
    • UPDATE 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 LISTEN et NOTIFY fournissent 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 NOTIFY sur ce canal, tous les clients abonnés reçoivent la notification
    • il est aussi possible d’y joindre un petit message
  • 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

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

    • Si un seul processus local doit utiliser Redis sur la machine comme cache en mémoire, autant utiliser directement les structures de données du langage de programmation employé.
    • Je me demande à quel point les surcoûts de Postgres et de Redis diffèrent lorsqu’ils tournent en local. Je ne vois pas non plus pourquoi on considérerait que Postgres ne tourne pas en local.
      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.
    • Django intègre un cache compatible Redis, et propose aussi une option de cache en mémoire, mais elle est indiquée comme « non destinée à la production ». C’est parce que, si l’on a plusieurs instances Django, chaque cache en mémoire devient différent des autres.
      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
    • Dans cette configuration, ce qui est plus rapide que Redis, c’est une hashmap. Rien n’empêche Postgres de tourner sur le même serveur que l’application, et c’est même une configuration assez courante.
    • La surconception et la distribution prématurée sont de vrais problèmes, mais Redis signifie « Remote Dictionary Server ». Son objectif initial n’est absolument pas de tourner en local. Bien sûr, l’exécution locale peut aussi être un choix de conception légitime, par exemple lorsque le dictionnaire de base du langage ne prend pas en charge les requêtes par plage.
  • 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.

    • Avoir une chose de moins à gérer en production est un avantage. Commencez avec PostgreSQL, puis ajoutez un système spécialisé quand les performances, le passage à l’échelle ou les coûts le rendent nécessaire.
  • 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)

    • Je cherchais une file de travaux simple. Huey est correct aussi, mais comme j’utilise déjà Postgres et que les travaux mis en file n’arrivent qu’environ une fois par heure, une solution basée sur Redis m’a toujours semblé excessive.
    • Côté Node, il y a aussi PG-Boss : https://github.com/timgit/pg-boss
  • 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

    • Il faut aussi vérifier que l’implémentation ne s’effondre pas lorsque des transactions longues empêchent vacuum de supprimer les tuples
  • 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

    • La différence entre Postgres et MySQL est ici vraiment étonnamment grande
      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
    • Il suffit d’utiliser du pooling de connexions. Avec AWS RDS Proxy, par exemple, cela s’active en quelques clics
  • 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

    • La plupart des fonctionnalités inutiles peuvent être désactivées. Par exemple, on peut créer une table unlogged : https://www.postgresql.org/docs/current/sql-createtable.html...
      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

    • Redis n’est pas particulièrement difficile à déployer. Plutôt que de planifier une transition dont on ignore les effets de bord imprévus, il peut aussi être tout à fait raisonnable d’utiliser Redis dès le départ