Aujourd'hui vous allez apprendre comment éviter ce qu'on appelle les "goulots d'étranglement" ou en d'autres termes, comment permettre à votre application de gérer un plus gros trafic en évitant d'augmenter la taille de votre serveur.
Chez Capsens, la concurrence a longtemps été un sujet d'ombre. Après avoir testé, cassé et s'être amélioré encore et encore, nous sommes aujourd'hui fiers de pouvoir vous partager notre modeste expertise sur le sujet. C'est Ismaël qui vous en parlera et pour le fun Johnny Hallyday nous servira d'exemple.
Au programme :
Éviter les goulots d'étranglement avec Sidekiq par Ismael
Le problème du lock PostgreSQL
Tests unitaires de la concurrence
Passage en asynchrone avec Sidekiq
Synchroniser le front-end
Le job de tes rêves !
Le défi (avec la réponse farfelue d'Antoine)
Bonjour et bienvenue sur la 10ème édition de Ruby Biscuit.
Vous êtes maintenant 202 abonnés 🥳 Si vous n’êtes pas déjà inscrit :
Éviter les goulots d'étranglement avec Sidekiq
Il arrive que l'on soit confronté à des situations qui impliquent un nombre de places limitées et où l'ordre est important. Dans la vie privée, nous appelons cela un shotgun mais pour cet article le terme FIFO (First In First Out) sera plus adapté.
Prenons un exemple concret :
Le dernier concert post mortem de Johnny Hallyday en petit comité est annoncé.
Il n'y a que 100 places et elles seront en vente le 31 janvier à 17h00 .
Grâce à une campagne marketing étonnante, l'événement fait le buzz.
Dans ce genre de cas, on se retrouve face à un problème de taille. En effet, nous savons que le trafic sur la plateforme sera très élevé le 31 janvier aux environs de 17h00 et que de nombreux utilisateurs tenteront de ravir ces places convoitées. Le souci est que seuls 100 d'entre eux y parviendront, traiter les demandes dans l'ordre chronologique est donc crucial.
Le problème du lock PostgreSQL
Une solution qui nous viendrait à l'esprit pourrait être d'avoir recours à un lock PostgreSQL pour éliminer les problèmes de concurrences, afin de s'assurer qu'on ne peut pas accepter deux souscriptions simultanément car il ne faut absolument pas qu'il soit possible de vendre plus de tickets que de places disponibles.
Dans le scénario ci-dessus, le lock PostgreSQL permet de s'assurer que les souscriptions sont bien traitées une par une. Si une requête est déjà passée dans le lock, alors la suivante doit attendre que le lock soit levé et ainsi de suite. Le ticket dispose aussi de validations afin de vérifier qu'il reste assez de place pour lui dans l'évènement.
Ce genre de système implique que le temps de traitement de chaque requête devient donc proportionnel au nombre de requêtes avant lui. En prenant un temps moyen raisonnable de 0.25 seconde de temps de traitement par requête, on peut aisément deviner qu'à partir de 60 requêtes en simultanée, on atteint les 15 secondes soit une expérience utilisateur plutôt médiocre voire un timeout selon comment est configuré votre serveur web.
Par souci de simplification, j'élude volontairement de nombreux autres problèmes que pose ce phénomène notamment le fait que cela implique de rapidement atteindre le nombre de threads total disponibles dans votre application : cela n'impacte donc plus uniquement ce formulaire mais toute votre application.
Tests unitaires de la concurrence
Reproduisons le scénario avec un test unitaire (rspec) :
Sans surprise, le dernier test échoue : chaque requête durant 0.25 secondes, 3 requêtes concurrentes prennent au minimum 0.75 seconde.
Passage en asynchrone avec Sidekiq
Une solution que nous adoptons régulièrement chez Capsens est de supprimer le lock et d'accepter toutes les demandes sans vérifier qu'il n'y a pas de souci de concurrence.
Les demandes sont considérées comme "en attente de traitement" puis on déclenche un job Sidekiq unique dont le travail sera de traiter toutes les demandes en attente de traitement dans leur ordre de création en base de données. La logique FIFO est donc conservée et le job étant unique, on est sûr de ne pas avoir de souci de concurrence ni de bloquer un nombre croissant de threads.
Voyons ceci dans la pratique.
On modifie le controller pour ne plus avoir de lock et appeler un job Sidekiq :
Notez que même si on ne se soucie plus de la concurrence, on peut tout de même ajouter une vérification afin de ne pas accepter plus de X tickets que le maximum de place. Rien ne sert de prendre 1000 participants si nous n'offrez que 100 places. En plus de faire espérer pour rien nos fans, cela ferait travailler le serveur inutilement.
On implémente ensuite notre Events::ProcessPendingTicketsJob
.
Le job utilise un lock Redis (grâce à la gem RedLockRb) afin de s'assurer de bien être toujours unique, c'est-à-dire qu'il ne puisse pas traiter des tickets pour un même évènement sur 2 threads en parallèle afin de préserver la logique FIFO :
Le job commence par tenter de déposer un lock Redis, s'il y a en déjà un, il se coupe.
Pour chaque ticket non traité de l'évènement, il le marque comme traité (afin qu'il ne soit plus traité par la suite) et le valide si l'évènement a encore de la place.
Note : on utilise un bang (!) pour s'assurer qu'en cas d'erreur, le job échoue et que l'utilisateur ne perde pas sa place à cause d'une erreur technique. Si un ticket échoue, tous les suivants doivent échouer.Le job se relance plus tard si de nouveaux tickets ont été crées pendant qu'il tournait
Le job retire le lock Redis
On met à jour le test de requête en conséquence (on en profite pour multiplier par 10 la concurrence au passage 🚀) :
On lance les tests :
Synchroniser le front-end
Bien entendu, pour avoir un parcours utilisateur complet, il faut implémenter un système pour faire patienter l'utilisateur dans une file d'attente en l'informant que sa demande est en cours de traitement.
Plusieurs solutions sont possibles pour tenir cette file d'attente synchronisée avec le serveur:
👌🏼 Lui envoyer un mail lorsque c'est terminé
✅ Faire du Short Polling en faisant une requête Ajax toutes les X secondes afin de demander au serveur si le ticket a été traité
🔥 Utiliser ActionCable (websocket) pour permettre au serveur de notifier le client directement lorsque son ticket est traité (la solution adoptée chez Capsens en général)
Ce genre de système vous permet aussi très facilement de garder les utilisateurs déçus en liste d'attente afin de pouvoir les re-inscrire en cas de désistement et donc maximiser vos chances que Johnny Hallyday ait bien 100 participants à son concert !
Le job de tes rêves !
Comme vous le savez, derrière Ruby Biscuit, il y a Capsens 👋 , nous sommes une agence web spécialisée dans le Ruby on Rails depuis 10 ans.
Avec le temps on s'est rendu compte que beaucoup de dévs choisissent leur entreprise un peu par hasard alors qu'ils pourraient mieux s'épanouir et se valoriser dans des structures qui leur correspondent bien.
Ce qui tombe super bien c'est que chez Capsens nous avons une excellente connaissance de l'écosystème Ruby on Rails en France, avec un réseau d'entreprises considérable.
C'est pourquoi on vous annonce à travers cette newsletter que nous mettons à profit notre connaissance du métier pour vous aider à trouver le poste de vos rêves !
Concrètement :
Tu souhaites trouver le job de tes rêves ? Alors réponds à cet email ! Tu peux dire "Coucou", ça suffit !
On te proposera aussitôt des créneaux pour une visio afin de faire connaissance
Puis nous te proposerons 3 entreprises qui correspondent à qui tu es. Et pour chacun de ces postes :
Tu pourras discuter avec un développeur de l'équipe (pas le recruteur lui-même) afin de savoir comment ça se passe de l'intérieur. No bullshit
Un développeur de Capsens t'aidera à :
Analyser le poste : ce qui a l'air bien, les dangers, quelles conditions poser pour que tout se passe bien
Te préparer pour que tu décroches le bon poste
Tu veux aller vers une vie professionnelle plus épanouie ? 😀 Eh bien, on attend ton
e-mail ! Et si tu aimes déjà ton travail, ne nous contacte surtout pas ! Ou alors fais-le pour nous recommander ta boite 😉
Le Défi
Le défi de la dernière newsletter n'était pas des moindres !
Pour rappel, la consigne était la suivante :
Écris “Ruby Biscuit” sans utiliser de Literals : pas de strings, de nombres, symboles, regex etc.
🎙️ Antoine vous partage sa réflexion pour réponse à ce défi, accrochez-vous bien :
Donc qu’est-ce qu’on peut faire sans litterals et comment on peut construire une string ?
On peut avoir nil
avec une variable d’instance @foo
non initialisée.
to_i
nous permet d’avoir 0, puis succ
les integers de notre choix :
@foo.to_i
=> 0
@foo.to_i.succ
=> 1
On peut convertir un integer en string avec to_s
bien sûr, mais aussi avec Integer#chr
qui retourne le caractère représenté par celui-ci.
Et vice versa avec String#ord
, qui retourne l’integer représentant le caractère :
“A”.ord
=> 65
66.chr
=> “B”
On pourrait donc faire @foo.to_i.succ.succ.succ.succ….chr + foo.to_i.succ.succ….
etc. pour construire notre String caractère par caractère.
Mais on est en Ruby, on peut aussi tricher avec les constantes.
Pourquoi se casse la tête ? En définissant une classe Biscuit
, ont peut faire Biscuit.to_s
pour avoir “Biscuit”
!
Ou encore plus concis, définir une méthode qui retourne le symbole représentant cette méthode.
Ainsi def Biscuit; end.to_s
retourne “Biscuit”.
Finalement, le seul caractère qui n’est pas trivial est l’espace ” “
.
” “.ord
=> 32. Bon on va quand même pas faire 32 .succ
d’affilé, on doit pouvoir l’avoir plus efficacement.
On peut avoir cette valeur en faisant la valeur d’un caractère moins celle d’un autre. Par exemple :
“A”.ord
=> 65
“a”.ord
=> 97
donc (“a”.ord - “A”.ord).chr
=> ” “
Avec tout ça, une solution lisible peut être :
Et en la condensant le plus possible :
Merci Antoine ! 🥲
Voici les règles du prochain défi :
Calculer le triangle de Pascal jusqu'à un nombre donné de lignes.
Dans le triangle de Pascal, chaque nombre est calculé en additionnant les nombres à droite et à gauche de la position actuelle dans la ligne précédente.
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
# ... etc
Tout comme les dernières fois, envoyez-moi vos réponses en répondant à cette newsletter et je publierai la meilleure dans la prochaine édition !
Si tu es arrivé jusqu'ici tu as deux options :
soit tu n'es pas encore abonné et c'est alors le moment
soit tu l'es déjà et dans ce cas je compte sur toi pour partager cette édition 😉
Ismaël, Antoine et Mélanie 🙂
Et merci Stan pour la relecture !
Je ne suis pas objectif, mais : Mon Dieu que cette newsletter est quali 😍