Présentation du projet
- NewCodes est un service de curation de blogs techniques d’entreprise
- Architecture Spring Boot + PostgreSQL
- Mise en œuvre d’une fonctionnalité d’autocomplétion : recommandations basées sur les Term, recherche avec décomposition des jamo, recherche par consonnes initiales, recommandation de pages d’entreprise
Découverte du problème de performance
- 110 000 données accumulées dans la table Term
- Le temps de réponse de l’API a dépassé 1000 ms
- Objectif : répondre en moins de 100 ms
1re tentative : ajout d’index (1000 ms → 700 ms)
- Création d’index optimisés pour les recherches par préfixe avec LIKE en utilisant
varchar_pattern_ops
- Création des index avec l’option
CONCURRENTLY, sans interruption de service
- Application d’un index sur chacune des colonnes term, decomposed_term et chosung
2e tentative : index sur la fonction LOWER (700 ms → 110 ms)
- Identification d’un problème de scan complet provoqué par l’utilisation de la fonction
LOWER()
- Création d’un index fonctionnel (Functional Index)
- Refonte des index sous la forme
LOWER(nom_de_colonne) varchar_pattern_ops
3e tentative : JOIN → EXISTS (110 ms → 100 ms)
- Le INNER JOIN entre Corporation et Article était le principal goulot d’étranglement
- Remplacement par une sous-requête EXISTS pour réduire la zone de scan
- Optimisation pour ne vérifier que la « présence de données »
4e tentative : dénormalisation & index de couverture (100 ms → 90 ms)
- Ajout de la colonne
total_frequency pour supprimer les opérations d’agrégation
- Remplacement des opérations GROUP BY et SUM par des valeurs pré-calculées
- Réduction des opérations d’I/O grâce à un index de couverture
- Inclusion de term et total_frequency dans l’index avec la clause
INCLUDE
5e tentative : JDBC Template (90 ms → 80 ms)
- Suppression de la surcharge de JPA/Hibernate
- Exécution directe des requêtes avec JDBC Template
- Pour les lectures simples, ignorer la couche ORM s’est révélé efficace
Résolution du problème de Rate Limiting Nginx
- Configuration initiale : limite de 2 requêtes par seconde, burst 10
- Des échecs de requêtes sont apparus à cause d’un debouncing à 100 ms
- Amélioration : passage à 10 requêtes par seconde, burst 20
- Changement du code de statut de 444 à 429
Réduction de la taille des données de réponse
- Suppression des noms de champs JSON et passage à une réponse basée sur des tableaux
- Distinction des types par des numéros (0: Corporation, 1: Theme, 2: Term)
- Réduction du temps de transfert réseau
Traitement parallèle avec CompletableFuture
- Exécution simultanée et indépendante des requêtes Corporation, Theme et Term
- Le temps total ne correspond plus qu’au temps de réponse maximal, au lieu d’une exécution séquentielle
- Ajout d’ExecutorService et de la gestion des exceptions
Résultat final
- 1000 ms au départ → 80 ms au final (serveur de développement), 40 ms (serveur de production)
- Amélioration des performances de plus de 90 %
Principaux enseignements
- Importance de bien définir le problème et la direction à prendre
- Trouver le bon équilibre entre utilisation de l’IA et validation par le développeur
- Nécessité d’une conception à l’échelle de toute l’architecture
- Choix du type d’index : index simple / composite / de couverture
- Attention à l’invalidation des index lors de l’utilisation de fonctions
- Compréhension du fonctionnement interne de JPA
- Analyse des plans d’exécution des requêtes avec EXPLAIN
Pistes d’amélioration futures
- Utilisation d’une structure de données Trie
- Mise en cache des termes les plus recherchés
- Utilisation d’un CDN (dans le cas d’un service mondial)
Aucun commentaire pour le moment.