Aider 14 000 nouveaux élèves par mois. 2 fois plus vite qu’avant.

LiveMentor accueille désormais 14 000 nouveaux élèves par mois. Et on essaye de rester AU TOP 😃 !

Souvent, je dîne avec ma maman. Et elle me pose toujours la même question :

“Mais Alexandre, ton site, il est terminé là, non ? Qu’est-ce que vous faites du coup de vos journées ?”

Chère maman, si tu lis ces lignes, cet article est pour toi. Un mythe populaire présente la création d’un site ainsi :

  • On s’enferme dans une cave
  • On code un site en 2 mois
  • On le lance et on n’y touche plus ensuite

La réalité est très différente et pour l’illustrer avec un exemple très concret (qui fera peur aux non-développeurs !), j’ai le plaisir de laisser la parole ici à Cybil, qui fait partie de l’équipe technique LiveMentor !

Cela fait bientôt deux ans qu’on travaille ensemble avec Cybil. Nous buvons du thé, échangeons des vidéos de chats et surtout réfléchissons à comment maintenir un niveau de qualité constant pour nos élèves !

(Si notre méthode de travail vous plaît, nous recrutons actuellement un développeur Web full-stack Ruby on Rails !)

Cybil, je te laisse la plume !

Derrière l’humain, la machine 🤖🤖

Bonjour à toutes et à tous !

LiveMentor est une école sur Internet pour les entrepreneurs, freelances et indépendants. Nous avons formé en ligne plus de 100 000 élèves l’année dernière avec toujours le même objectif : les aider à réaliser leur projet.

L’accompagnement personnalisé est un des points clés de nos formations en ligne. Nous avons constitué une équipe d’assistants pédagogiques. Ils sont à la disposition de nos élèves pour :

  • Leur expliquer des points de cours non compris
  • Répondre à leurs questions techniques
  • Les aider à prendre les bons choix pour leur projet

Et tout ça en un temps record ! Lancer son projet, c’est stressant. On a besoin d’une réponse rapide et nos assistants se démènent pour cette raison. Avec l’équipe technique LiveMentor, nous avons mis en place des process utilisant des outils externes:

  • Un logiciel de téléphonie (Aircall) capable de passer et recevoir des appels dans plus de 40 pays
  • Un CRM pour permettre à une vingtaine de personnes de répondre à tous les emails de nos élèves

(Le choix des outils pourrait faire l’objet d’un article dédié d’ailleurs.. Nous en avons testé plusieurs et continuons de garder un oeil très vigilant sur l’évolution du CRM Drift ou du logiciel d’emailing Drip par exemple)

Mais ces outils externes ne sont pas suffisants ! Nous avons également développé :

  • Une extension Google Chrome qui complète les fonctionnalités de notre CRM pour donner à nos assistants pédagogiques en temps réel toutes les informations sur nos élèves.
  • Un outil interne d’administration pour accéder aux profils de nos élèves.

Cet outil interne d’administration est la brique centrale de toute notre organisation. Sauf qu’il y a 2 mois, cette brique a bien failli s’effondrer et toutes les fondations de notre École pour entrepreneurs avec !

Vite, il faut sauver nos assistants pédagogiques !

Cet outil interne d’administration permet aux assistants pédagogiques de trouver la fiche d’un élève grâce à un champ de recherche. Dans cette fiche, nous avons :

  • Les coordonnées de l’élève (prénom et nom)
  • Son adresse e-mail et numéro de téléphone
  • Les formations suivies par cet élève
  • L’historique des notes prises sur cet élève à chaque session diagnostic

Nous avions créé le champ de recherche à nos débuts, sans optimisation ! En effet, à l’époque, nous n’utilisions cette fonctionnalité que quelques fois par semaines, pour seulement quelques milliers d’utilisateurs enregistrés.

Un design très standard (sobre, efficace, merveilleux)

Il y a deux mois, ce champ de recherche commença à rendre l’âme. Pourquoi ?

  • Parce que nous avons maintenant plus de 100 000 élèves inscrits
  • Et que nous accueillons désormais 14 000 nouveaux élèves chaque mois

Le champ n’étant pas optimisé, son utilisation devenait pénible et lente. Très Lente.

Veut-on que nos assistants pédagogiques ressemblent à ça ? NON !

Notre objectif : redonner toute sa force à notre champ de recherche

Nous avons premièrement éliminé la piste menant à l’intégration d’un nouvel outil tiers. Nous sommes pourtant des fans d’Algolia et nous avons longtemps regardé leur solution. Au final, pour ce challenge spécifique, coder notre propre solution était de loin préférable.

Première étape : comprendre le problème

Nous nous sommes premièrement rendus compte qu’il fallait mieux gérer dans notre code la prise en compte des majuscules et des accents.

Parfois, un élève aura donné à l’équipe un email ou une combinaison nom-prénom avec une “case” (“casse” en français) différente de celle renseignée à son inscription ou bien avec des accents : nous utilisons donc une comparaison LIKE avec LOWER et UNACCENT entre la valeur du champ de recherche et les valeurs de notre Base de données.

(query = la valeur recherchée; ici ‘cybil bo’’)

Avec l’augmentation du nombre d’élèves, le champ de recherche en l’état prenait plusieurs secondes pour donner une liste de résultats ! Afin d’avoir une auto-complétion sur ce champ de recherche, nous effectuions la recherche pour chaque caractère qui était tapé.

L’utilisation du LOWER et du UNACCENT obligeait donc une boucle sur chaque entrée de notre table USER : 1 caractère tapé = 100 000 requêtes !!(plusieurs secondes par caractère 😱 😱 😱)

Pour mettre des chiffres sur tout ça, nous avons regardé le temps d’execution de notre requête. Nous avons également utilisé le très pratique outil de Tatiyants pour profiler le tout :)

Voici un petit script Ruby que nous aimons utiliser en interne :

NB: utiliser `pbcopy` sous MacOs
Le temps d’execution de notre requête.

Petit conseil : lancer plusieurs fois la requête pour obtenir le ‘plan’ pour calculer une moyenne de temps d’execution. Lancée une seule fois, cela vous donnera probablement un résultat erroné dû au temps d’accès aux objets en mémoire si elle n’est pas toute fraîche 😉

Vous pouvez ensuite copier le ‘plan’ dans le premier champ du formulaire de la page puis copier votre ‘query’ dans le second champ. Une fois le formulaire validé, vous pourrez voir ce qui ne va pas.

Dans notre cas, un SEQ Scan (scan séquentiel) ce qui signifie que nous exécutions la même requête en boucle sur la totalité de nos entrées : c’est la pire des choses à faire !

Pour mieux profiler le temps de chargement de vos pages ou l’optimisation de vos requêtes, voici deux petites GEM vraiment top à intégrer dans vos projets : rack-mini-profiler et bullet :)

La solution : rajouter des index

Nous utilisons une DB PostgreSQL, et nous avons la possibilité de créer des INDEX dans notre DB afin de faciliter la recherche. Voici une super documentation sur le fonctionnement des Index dans une DB Postgre.

Nous devions donc faire une migration pour ajouter nos Index sur les noms et les prénoms de nos utilisateurs. Pour utiliser l’opérateur LIKE, nous devions utiliser des index text_pattern_ops et comparer des strings lower.

Explication venant de la documentation PostgreSQL :

The operator classes text_pattern_ops, varchar_pattern_ops, and bpchar_pattern_ops support B-tree indexes on the types text, varchar, and char respectively. The difference from the default operator classes is that the values are compared strictly character by character rather than according to the locale-specific collation rules. This makes these operator classes suitable for use by queries involving pattern matching expressions (LIKE or POSIX regular expressions)

Cependant, plusieurs Index ne peuvent pas se créer dans la même transaction ! Il faut donc créer une transaction par Index :

Ne pas oublier la migration down pour `DROP` les Index.

On pensait avoir tout prévu.. Sauf le problème d’UNACCENT !

Nous pensions avoir réglé le problème.. mais impossible de valider cette foutue migration.

PG::InvalidObjectDefinition: ERROR: functions in index expression must be marked IMMUTABLE
Un moment de solitude…

Après quelques heures de recherche, nous sommes tombés sur un article (merci StackOverFlow !) expliquant que la fonction `unaccent` est `MUTABLE` dans la version de Rails que nous utilisons. Or, en utilisant l’opérateur `LIKE`, nous ne pouvions utiliser que des fonctions `IMMUTABLE`.

Pour régler le problème, nous avons dû déclarer nous-même une fonction homemade unaccent IMMUTABLE :

pour l’utiliser dans la création de nos index :

N’oubliez pas d’utiliser également votre nouvelle fonction immutable dans le code de votre requête, sinon cette dernière n’utilisera pas votre Index 😉

Et voilà notre nouveau résultat :

Grâce aux Index, notre requête s’exécute en une seule fois 😃 !

Conclusion

Pour conclure sur cette petite aventure, voici une liste des bonnes nouvelles :

  • Un temps de réponse divisé par 200
  • Une augmentation de nos performances de 54%
  • Une bonne leçon sur la puissance des INDEX
  • La découverte d’outils de profiling
  • Et surtout : le bonheur des assistants pédagogiques et donc des élèves !

Il faut regarder dans “most improved” : nous sommes sur la bonne voie. Nous utilisons la géniale interface Skylight pour monitorer la vitesse de notre site et de notre outil d’administration.

À propos de Skylight, nous avons désormais l’habitude de prendre 1 heure chaque semaine pour consulter notre dashboard et optimiser les requêtes les plus lentes et les plus demandées (caching, ajout d’index, refactor, etc..)

Nos assistants pédagogiques aujourd’hui