Aller au contenu
Alice de Bernyen
Retour au CV

Klara · 2023 – 2026

Matrice de compétences

Donner aux managers et aux RH une vue d'ensemble des compétences de leurs collaborateurs.

  • React
  • react-window
  • Ruby on Rails
  • PostgreSQL
  • GraphQL

Contexte

Chez Klara, j'ai réalisé la première implémentation d'une matrice de compétences destinée aux managers et aux administrateurs (responsables RH). Elle leur donne une vision globale des compétences de chaque collaborateur qu'ils supervisent, pour proposer des formations, suivre l'expiration des certifications et organiser les remplacements.

La matrice devait :

  • croiser chaque compétence avec chaque collaborateur managé
  • signaler au niveau du collaborateur une certification expirée
  • pouvoir être filtrée
  • afficher au clic le détail de chaque positionnement (collaborateur, managers)

Une compétence est calculée à partir des réponses à certaines questions du questionnaire d'entretien. Plusieurs réponses peuvent être candidates : parmi les réponses les plus récentes des managers, il faut retenir la meilleure. Or la table des réponses ne contenait alors aucun indicateur de la bonne réponse. Il fallait donc la calculer à chaque lecture.

Un compte moyen regroupe environ 3 000 collaborateurs pour une centaine de compétences (nombre variable selon le compte), soit de l'ordre de 300 000 cellules.

Approche

Première version : livrer dans les temps en réutilisant l'existant

Le projet était cadré en Shape Up. Pour garantir une livraison complète dans le temps imparti, tests et revues compris, j'ai limité le périmètre et réutilisé au maximum l'existant :

  • Côté frontend, la matrice est virtualisée avec react-window, module déjà présent dans le projet. Seules les cellules visibles à l'écran sont rendues, ce qui allège le DOM.
  • Côté backend, j'ai réutilisé les méthodes existantes de calcul des bonnes réponses, en les paginant selon le scroll de l'utilisateur. Le calcul pouvait être très long sur l'ensemble des collaborateurs, en particulier pour les administrateurs, qui voient tout le monde.
  • Un léger overscan précharge les données suivantes avant que l'utilisateur ne les atteigne.
  1. Matrice virtualisée (react-window)
  2. Requête paginée au scroll
  3. Calcul de la bonne réponse
  4. Table des réponses
Première version : la bonne réponse est recalculée à chaque lecture, page par page.

Évolution : calculer à l'écriture plutôt qu'à la lecture

La matrice a ensuite beaucoup évolué au sein de l'équipe :

  1. Flag calculé à l'écriture. Un autre membre de l'équipe a ajouté un flag, calculé à chaque modification d'une réponse, qui désigne directement la bonne ligne. Plus besoin de chercher la bonne réponse à la lecture donc la pagination a pu être supprimée et les données chargées en une fois.
  2. Vue matérialisée. Une vue matérialisée construite sur ce flag a ensuite été testée pour précalculer les données de la matrice. Mais la table des réponses est très souvent modifiée et la matrice doit refléter rapidement les dernières valeurs. Rafraîchie toutes les 5 minutes par un job Rails, la vue était trop souvent en retard et chaque rafraîchissement recalculait l'ensemble des données. Elle a été abandonnée.
  3. Endpoint JSON et filtrage côté client. Toutes les données du périmètre de l'utilisateur sont servies par un endpoint JSON et les filtres s'appliquent côté client, sans nouvel appel au serveur.
  1. Modification d'une réponse
  2. Calcul du flag
  3. Table des réponses (flag)
  4. Endpoint JSON
  5. Filtrage côté client
Version actuelle : le calcul est fait une fois à l'écriture ; la lecture récupère directement les lignes flaguées.

Résultat

Première version : la matrice a été livrée dans les délais du cycle et la virtualisation a permis un DOM léger et un rendu fluide, même avec des milliers de lignes. La pagination, en revanche, n'a pas apporté le gain attendu. Les pages étaient triées par nom de collaborateur et de compétence, mais côté serveur, les bonnes réponses étaient calculées sur l'ensemble des données avant d'être triées puis découpées. Le premier affichage était un peu plus rapide et les pages déjà chargées revenaient instantanément depuis le cache Apollo. Mais chaque nouvelle page refaisait le calcul complet, ce qui se traduisait par de l'attente au scroll.

Seconde version : un premier affichage, des tris et des filtres assez performants côté client avec l'indexation du flag. Avec des comptes de plusieurs milliers de collaborateurs, appelés à grossir, le temps du chargement complet reste le point à surveiller.

Avec le recul

Paginer ne fait gagner du temps que si le serveur peut produire une page sans traiter tout le jeu de données. Ici, le tri portait sur des noms, très peu coûteux à trier : il aurait suffi de paginer d'abord les collaborateurs et les compétences par nom, puis de ne calculer les bonnes réponses que pour ceux de la page. Le coût d'une page n'aurait alors plus dépendu de la taille du compte.

Si le tri avait porté sur des valeurs calculées, comme un niveau de compétence, l'alternative aurait été de calculer et trier une seule fois, de garder ce résultat en cache côté serveur, puis de servir chaque page comme une simple tranche de ce cache.

Pistes d'amélioration

  • Mesurer où passe le temps de chargement (requête, sérialisation, réseau, rendu) avant d'optimiser.
  • Côté base : un partitionnement de la table des réponses selon le flag si elle devient bien plus grosse que la mémoire disponible.

Ce que j'en retiens

  • Une première version qui réutilise l'existant permet de livrer vite et d'observer l'usage réel avant d'optimiser.
  • Paginer n'accélère rien si chaque page oblige le serveur à traiter toutes les données.
  • Quand une donnée est lue bien plus souvent qu'elle n'est écrite, mieux vaut faire le calcul une fois à l'écriture qu'à chaque lecture.