BUT Informatique study materials
BUT Informatique study resources

Page de reporting à 9,4 s : prioriser les optimisations

BUT Informatique · Put your knowledge to work in context.

All case studies

Page de reporting à 9,4 s : prioriser les optimisations

La page « bilan des stages » de l'application Django de l'IUT met 9,4 s à s'afficher en moyenne (mesure sur 20 chargements, données de production). Elle liste les stages filtrés par année et par statut, avec le nom de l'étudiant, l'entreprise et le tuteur. Le profiling montre 421 requêtes SQL par affichage. La table stage contient 180 000 lignes, indexée uniquement sur sa clé primaire. EXPLAIN ANALYZE sur la requête principale affiche un Seq Scan puis un Sort sur disque (work_mem à 4 Mo). Les colonnes filtrées sont annee (6 valeurs distinctes) et statut (4 valeurs, dont « validé » à 78 %). Le total des stages affichés est calculé par une boucle Python qui parcourt les objets un par un. Un cache Redis existe déjà, mais il est invalidé à chaque écriture dans n'importe quelle table du projet, et son taux de hit est de 12 %. L'objectif exprimé par le responsable pédagogique est un affichage sous 1 s.

1. Classez les optimisations par ordre de priorité en vous appuyant sur les mesures, et justifiez l'ordre. Pour chaque piste, indiquez le gain attendu et le risque.

Read the worked answer

1. Le N+1 (421 requêtes) - gain majeur, risque faible. 421 allers-retours coûtent déjà plusieurs dixièmes de seconde en latence pure et chargent des objets partiellement utiles. select_related sur les clés étrangères (étudiant, entreprise, tuteur) et prefetch_related sur les relations inverses ramènent l'affichage à 3 ou 4 requêtes. C'est la seule correction qui agit sur la cause, pas sur le symptôme.

2. L'agrégat calculé en Python. Remplacer la boucle par Count/annotate ou une requête agrégée : on évite de rapatrier des lignes pour produire un total. Gain proportionnel au volume, risque faible, et le calcul devient juste même avec la pagination.

3. L'indexation. Un index simple sur statut serait inutile : avec 78 % de « validé », le planificateur préférera un Seq Scan. Un index composite (annee, statut), ou un index partiel WHERE statut = 'validé' si c'est le filtre dominant, aide quand la sélectivité vient de annee. À valider par EXPLAIN ANALYZE avant/après, jamais à l'intuition.

4. Le tri sur disque. work_mem à 4 Mo provoque un tri externe : soit un SET LOCAL work_mem sur la session de reporting, soit un index couvrant l'ORDER BY.

Ordre retenu : du moins risqué au plus incertain, avec une mesure après chaque étape. Sinon, on ne sait pas ce qui a réellement payé.

2. Un collègue propose de régler le problème en cachant la page entière une heure dans Redis. Évaluez cette proposition.

Read the worked answer

La proposition ne tient pas, pour trois raisons. D'abord, un cache invalidé par n'importe quelle écriture du projet a un taux de hit de 12 % : on ajoute de la complexité et un point de panne pour un gain quasi nul. Ensuite, elle ne supprime pas la cause : le premier affichage (cache froid) reste à 9,4 s, et après chaque invalidation plusieurs requêtes simultanées recalculent le même bilan - thundering herd. Enfin, une heure de fraîcheur sur des données de scolarité est une décision métier, pas un choix de développeur : elle doit être validée par le responsable pédagogique.

Ce qui serait correct. Corriger le N+1 et l'agrégat Ramène d'abord le calcul sous la seconde. Ensuite seulement, si le besoin persiste, cacher des fragments avec une clé précise (année, statut, et rôle de l'utilisateur si les lignes affichées en dépendent), une TTL courte et une invalidation ciblée sur les écritures de stage uniquement. Un cache n'est pas une optimisation : c'est une décision de fraîcheur assumée, avec un risque de fuite de données si la clé ignore l'utilisateur.

3. Comment vérifier que le gain est réel et éviter une régression une fois en production ?

Read the worked answer

Mesurer dans des conditions réalistes. Charger un jeu de 180 000 lignes (dump anonymisé), puis tester des combinaisons contrastées : une année avec peu d'inscriptions, le statut « validé » majoritaire, la première et la dernière page. Relever le p95 et non la moyenne, à cache froid et à cache chaud.

Isoler les changements. Un commit par optimisation, avec EXPLAIN (ANALYZE, BUFFERS) avant et après. On évite ainsi d'attribuer au cache un gain dû au N+1, et on sait quelle étape a produit quel effet - utile pour arbitrer si un index doit être conservé.

Verrouiller la non-régression. Un test assertNumQueries(< 10) sur la vue de listing échoue si un select_related disparaît lors d'un futur refactoring. Un test de durée n'est pas fiable en CI (machines variables), mais un compteur de requêtes l'est. Ajouter côté production un log de durée par endpoint avec une alerte au-delà de 1 s en p95, car le volume de données va continuer de croître : la performance se surveille, elle ne se décrète pas une fois pour toutes.