Nous avons une situation où je dois faire face à un afflux massif d'événements arrivant sur notre serveur, à environ 1000 événements par seconde, en moyenne (le pic pourrait être ~ 2000).
Le problème
Notre système est hébergé sur Heroku et utilise une base de données Heroku Postgres relativement chère , qui permet un maximum de 500 connexions DB. Nous utilisons le pool de connexions pour se connecter du serveur à la base de données.
Les événements arrivent plus rapidement que le pool de connexions DB ne peut gérer
Le problème que nous avons est que les événements arrivent plus rapidement que le pool de connexions ne peut les gérer. Au moment où une connexion a terminé l'aller-retour du réseau du serveur à la base de données, afin qu'elle puisse être renvoyée dans le pool, plus d' n
événements supplémentaires arrivent.
Finalement, les événements s'empilent, attendant d'être enregistrés et parce qu'il n'y a pas de connexions disponibles dans le pool, ils expirent et l'ensemble du système est rendu non opérationnel.
Nous avons résolu l'urgence en émettant les événements haute fréquence incriminés à un rythme plus lent de la part des clients, mais nous voulons toujours savoir comment gérer ces scénarios dans le cas où nous aurions besoin de gérer ces événements haute fréquence.
Contraintes
D'autres clients pourraient vouloir lire les événements simultanément
D'autres clients demandent continuellement de lire tous les événements avec une clé particulière, même s'ils ne sont pas encore enregistrés dans la base de données.
Un client peut interroger GET api/v1/events?clientId=1
et obtenir tous les événements envoyés par le client 1, même si ces événements ne sont pas encore enregistrés dans la base de données.
Existe-t-il des exemples «en classe» sur la façon de gérer cela?
Solutions possibles
Mettre les événements en file d'attente sur notre serveur
Nous pourrions mettre les événements en file d'attente sur le serveur (la file d'attente ayant une concurrence maximale de 400 afin que le pool de connexions ne s'épuise pas).
C'est une mauvaise idée car:
- Il consommera de la mémoire disponible sur le serveur. Les événements mis en file d'attente empilés consomment d'énormes quantités de RAM.
- Nos serveurs redémarrent toutes les 24 heures . Il s'agit d'une limite stricte imposée par Heroku. Le serveur peut redémarrer pendant la mise en file d'attente des événements, ce qui nous fait perdre les événements mis en file d'attente.
- Il introduit l'état sur le serveur, ce qui nuit à l'évolutivité. Si nous avons une configuration multi-serveurs et qu'un client veut lire tous les événements mis en file d'attente + enregistrés, nous ne saurons pas sur quel serveur les événements mis en file d'attente sont en direct.
Utiliser une file d'attente de messages distincte
Je suppose que nous pourrions utiliser une file d'attente de messages (comme RabbitMQ ?), Où nous y pompons les messages et à l'autre extrémité, il y a un autre serveur qui ne s'occupe que de la sauvegarde des événements sur la base de données.
Je ne sais pas si les files d'attente de messages permettent d'interroger les événements mis en file d'attente (qui n'ont pas encore été enregistrés), donc si un autre client veut lire les messages d'un autre client, je peux simplement obtenir les messages enregistrés de la base de données et les messages en attente de la file d'attente et les concaténer ensemble afin que je puisse les renvoyer au client de demande de lecture.
Utilisez plusieurs bases de données, chacune enregistrant une partie des messages avec un serveur central de coordinateur de base de données pour les gérer
Une autre solution que nous avons envisagée consiste à utiliser plusieurs bases de données, avec un "coordinateur DB / équilibreur de charge" central. À la réception d'un événement, ce coordinateur choisirait l'une des bases de données dans lesquelles écrire le message. Cela devrait nous permettre d'utiliser plusieurs bases de données Heroku, augmentant ainsi la limite de connexion à 500 x nombre de bases de données.
Lors d'une requête de lecture, ce coordinateur peut émettre des SELECT
requêtes vers chaque base de données, fusionner tous les résultats et les renvoyer au client qui a demandé la lecture.
C'est une mauvaise idée car:
- Cette idée ressemble à ... ahem .. une ingénierie excessive? Serait aussi un cauchemar à gérer (sauvegardes etc.). C'est compliqué à construire et à entretenir et à moins que ce ne soit absolument nécessaire, cela ressemble à une violation de KISS .
- Il sacrifie la cohérence . Faire des transactions sur plusieurs bases de données est un no-go si nous allons avec cette idée.
ANALYZE
les requêtes elles-mêmes et elles ne posent aucun problème. J'ai également construit un prototype pour tester l'hypothèse du pool de connexions et vérifié que c'est bien le problème. La base de données et le serveur lui-même vivent sur des machines différentes d'où la latence. De plus, nous ne voulons pas abandonner Heroku à moins que cela ne soit absolument nécessaire, ne pas s'inquiéter des déploiements est un énorme avantage pour nous.
select null
sur 500 connexions. Je parie que vous constaterez que le pool de connexions n'est pas le problème.