← Toutes les réalisations
Rapport de démonstration. Ce document montre la méthode et le niveau de détail d'un audit applicatif HANDICODE. L'entreprise est anonymisée et les situations décrites illustrent des défauts réellement rencontrés sur des applications comparables ; elles ne sont pas le relevé d'une mission chez un client identifiable.

Dossier C-2026-047 · Audit applicatif

ERP sur mesure d'une entreprise de services

Diagnostic d'une application métier développée en interne depuis six ans. Les écrans les plus utilisés mettaient jusqu'à dix-huit secondes à s'afficher, et personne ne savait pourquoi.

Secteur · Services aux entreprises Utilisateurs · 62, dont 41 quotidiens Technologie · PHP 8.2, MySQL 8, 180 tables Historique · 6 ans, 4 développeurs successifs Observation · 1er au 19 septembre 2026 Méthode · Profilage, EXPLAIN, revue de code

Section 1Périmètre et méthode

Trois semaines d'observation sur l'environnement de production, sans interruption de service. Le profilage a été activé sur un échantillon d'une requête sur vingt, pour ne pas peser sur les utilisateurs.

SourceVolumeCe qu'on en tire
Profilage applicatif18 400 requêtesTemps par écran, requêtes SQL par page
Journal des requêtes lentes30 joursRequêtes au-delà de 500 ms
Plans d'exécution47 requêtesIndex utilisés ou ignorés
Revue de code12 contrôleursLes écrans les plus appelés
Schéma de la base180 tablesIndex, clés, types, volumétrie
Entretiens utilisateurs6 personnesCe qui gêne réellement au quotidien

Une précaution de méthode. Les entretiens viennent en dernier, après les mesures. Demander d'abord aux utilisateurs ce qui est lent oriente le regard vers ce dont on se plaint, pas vers ce qui coûte le plus. Les deux ne coïncident pas toujours : l'écran le plus lent n'était pas celui que l'on citait.

Section 2Ce qu'il faut retenir

L'application est fonctionnellement riche et répond au métier. Son problème est d'avoir grandi sans que la base de données suive : les requêtes écrites quand les tables contenaient mille lignes en contiennent aujourd'hui deux millions.

cible 1 s Tableau de bord 18,4 s Liste des dossiers 9,2 s Planning 6,8 s Fiche client 3,1 s Facturation 2,4 s Recherche 1,2 s 0 10 s 20 s
Médiane sur 18 400 requêtes profilées. Le tableau de bord est ouvert par chaque utilisateur à chaque connexion : c'est l'écran le plus coûteux et le plus fréquent à la fois.

critiqueLe tableau de bord exécute 3 847 requêtes SQL

Pour afficher une page, l'application interroge la base 3 847 fois. Chaque requête prend 2 à 4 millisecondes : individuellement anodines, ensemble elles forment l'essentiel des 18,4 secondes.

critiqueQuarante et un utilisateurs, 7 heures perdues par semaine

Le tableau de bord est ouvert en moyenne 6 fois par jour et par personne. À 17 secondes d'attente évitable, cela représente environ 7 heures hebdomadaires pour l'équipe, passées à regarder un écran se charger.

majeureAucune sauvegarde testée depuis la mise en service

Les sauvegardes existent et tournent. Aucune restauration n'a jamais été essayée : personne ne sait combien de temps elle prendrait, ni si elle fonctionnerait.

Section 3Où passent les dix-huit secondes

14,2 s · SQL 2,1 s rendu PHP sérialisation 1,4 s réseau 0,7 s Répartition du temps d'affichage du tableau de bord
77 % du temps part dans la base. Optimiser le code PHP ou changer de serveur n'adresserait que les 23 % restants : c'est l'erreur classique sur ce genre de dossier.

Section 4Le défaut dominant : la requête dans la boucle

Sur les 3 847 requêtes du tableau de bord, 3 790 sont des répétitions de cinq requêtes identiques à un paramètre près. C'est le défaut connu sous le nom de « N+1 » : une requête pour obtenir une liste, puis une requête par élément de cette liste.

// Le code en place. Il se lit bien, et c'est ce qui le rend dangereux : // rien n'indique qu'une boucle de 480 dossiers produit 1 920 requêtes. $dossiers = Dossier::where('actif', true)->get(); // 1 requête, 480 lignes foreach ($dossiers as $dossier) { $dossier->client->raison_sociale; // +1 requête par dossier $dossier->responsable->nom; // +1 requête par dossier $dossier->interventions()->count(); // +1 requête par dossier $dossier->dernierDocument?->cree_le; // +1 requête par dossier } // Total : 1 + (480 × 4) = 1 921 requêtes pour un seul bloc de la page.

Le correctif tient en une ligne par relation. Les données sont chargées en quelques requêtes groupées, et le reste du code ne change pas.

$dossiers = Dossier::where('actif', true) ->with(['client:id,raison_sociale', 'responsable:id,nom']) ->withCount('interventions') ->with(['dernierDocument' => fn ($q) => $q->select('id', 'dossier_id', 'cree_le')]) ->get(); // Total : 5 requêtes au lieu de 1 921. Le select ciblé évite en plus // de ramener 38 colonnes dont on n'affiche que deux.
ÉcranRequêtes avantAprèsTemps avantAprès
Tableau de bord3 8473418,4 s0,9 s
Liste des dossiers1 921129,2 s0,6 s
Planning1 402186,8 s0,8 s
Fiche client28793,1 s0,4 s

La garde qui empêche le défaut de revenir

Corriger ces quatre écrans ne sert à rien si le défaut réapparaît au prochain développement. Une garde activée en environnement de développement fait échouer le code dès qu'une relation est chargée dans une boucle.

// Dans le fournisseur de services, hors production : // le développeur voit l'erreur avant le client. Model::preventLazyLoading(! $this->app->isProduction());

Section 5Index manquants et tables qui ont grossi

Quarante-sept requêtes lentes ont été analysées par leur plan d'exécution. Onze parcourent une table entière faute d'index.

TableLignesColonne filtréeIndexLignes lues
interventions2 140 000dossier_id, dateabsent2 140 000
documents684 000dossier_idabsent684 000
journal_actions5 900 000cree_leabsent5 900 000
dossiers18 400actifprésent480
-- Le plan d'exécution avant : type=ALL, la table entière est lue. EXPLAIN SELECT * FROM interventions WHERE dossier_id = 4821 AND date >= '2026-01-01'; -- id select_type table type key rows Extra -- 1 SIMPLE interventions ALL NULL 2140000 Using where CREATE INDEX idx_interventions_dossier_date ON interventions (dossier_id, date); -- Après : type=range, 142 lignes lues au lieu de 2,14 millions. -- 1 SIMPLE interventions range idx_… 142 Using index condition

L'ordre des colonnes compte. Un index sur (date, dossier_id) n'aurait pas servi cette requête : MySQL ne peut utiliser un index composite que de gauche à droite. La colonne la plus sélective, ici le dossier, doit venir en premier.

majeureLe journal d'actions n'est jamais purgé

5,9 millions de lignes depuis six ans, dont aucune n'est consultée au-delà de trois mois. La table occupe 38 % du volume de la base et ralentit chaque sauvegarde. Une purge au-delà de 13 mois, avec archivage, la ramène à 900 000 lignes.

Section 6Dette technique et risque d'exploitation

PointÉtatRisque si rien n'est faitGravité
Restauration de sauvegardejamais testéeDurée et succès inconnus le jour venuhaute
Tests automatisésaucunToute correction peut en casser une autrehaute
Dépendances3 versions majeures de retardCorrectifs de sécurité non appliquéshaute
Environnement de recetteabsentLes correctifs se vérifient en productionmoyenne
Documentationdans la tête de deux personnesDépendance forte aux individusmoyenne
Journalisation applicativepartielleIncidents difficiles à expliquerbasse
Chiffrement en transitTLS 1.3——
Séparation des droits5 rôles distincts——

critiqueUne sauvegarde non testée n'est pas une sauvegarde

C'est le point le plus grave du dossier, et il n'a rien à voir avec la lenteur. Tant qu'une restauration n'a pas été conduite de bout en bout sur un serveur vierge, personne ne peut affirmer que l'entreprise sait redémarrer après un incident. L'essai prend une demi-journée.

Pourquoi les tests viennent avant les corrections

Une application sans test, modifiée sur quatre écrans à la fois, se casse ailleurs sans que personne ne s'en aperçoive. L'ordre d'intervention proposé pose donc d'abord un filet sur les parcours critiques, avant de toucher au code.

// Le minimum vital : un test par parcours qui ne doit jamais casser. public function test_le_tableau_de_bord_affiche_les_dossiers_actifs(): void { $this->actingAs($this->utilisateur()) ->get('/tableau-de-bord') ->assertOk() ->assertSee('Dossiers actifs') ->assertSee($this->dossier->reference); }

Section 7Ce que disent les données elles-mêmes

Au-delà de la performance, l'examen du schéma révèle des incohérences qui finiront par produire des erreurs de gestion.

ConstatOccurrencesConséquence
Montants stockés en nombre à virgule flottante7 colonnesÉcarts d'arrondi au centime sur les cumuls
Dates stockées en texte4 colonnesTris et comparaisons faux hors format ISO
Contraintes de clé étrangère absentes23 relationsLignes orphelines après suppression
Lignes orphelines existantes1 847Écrans qui plantent sur un enregistrement absent
Doublons sur une référence censée être unique34Deux dossiers pour un même client
-- Les montants en DOUBLE produisent des écarts invisibles jusqu'au cumul. SELECT 0.1 + 0.2 = 0.3; -- renvoie 0, pas 1 -- Sur 2,1 millions de lignes d'intervention, l'écart cumulé mesuré -- entre la somme des lignes et le total stocké atteint 847,32 €. ALTER TABLE interventions MODIFY montant_ht DECIMAL(12, 2) NOT NULL;

Ce point se traite avant les autres : chaque jour qui passe ajoute des lignes dans le mauvais type, et la reprise devient plus lourde.

Section 8Préconisations détaillées

Chaque préconisation porte son critère d'acceptation : la mesure qui dira, après intervention, si elle a produit l'effet attendu. L'ordre suit le risque, pas la gêne ressentie.

P-01 · priorité 1 · continuité

Restaurer une sauvegarde sur un serveur vierge, chronomètre en main

Constat
Les sauvegardes tournent depuis six ans. Aucune restauration n'a jamais été essayée : ni la durée, ni le succès ne sont connus.
Préconisation
Restaurer la dernière sauvegarde sur une machine neuve, chronométrer l'opération de bout en bout, vérifier que l'application démarre et que les données du dernier jour sont présentes.
Mise en œuvre
Une sauvegarde non restaurée n'est pas une sauvegarde : c'est une hypothèse. L'essai se fait à froid, sans toucher à la production.
Critère d'acceptation
Application fonctionnelle sur la machine d'essai, durée mesurée et écrite dans la procédure, écart de données inférieur à 24 heures.
Risque de régression
Nul sur la production. Le vrai risque est de découvrir que la restauration échoue : c'est précisément ce qu'il vaut mieux apprendre un mardi matin qu'un jour d'incident.
Effort · Gain
0,5 jour · transforme une hypothèse en certitude
P-02 · priorité 1 · filet

Poser un environnement de recette et douze tests de parcours

Constat
Aucun test automatisé, aucun environnement de recette. Les correctifs se vérifient en production, sur une application de six ans et quatre développeurs successifs.
Préconisation
Un environnement de recette avec une copie anonymisée de la base. Douze tests couvrant les parcours qui ne doivent jamais casser : connexion, création de dossier, saisie d'intervention, facturation, export.
Mise en œuvre
Ces tests viennent avant les corrections de performance. Modifier quatre écrans sans filet, sur une application sans test, casse ailleurs sans que personne ne s'en aperçoive.
Critère d'acceptation
Les douze tests passent sur la recette. Un défaut introduit volontairement dans un parcours fait échouer le test correspondant : c'est la seule preuve qu'un test sert à quelque chose.
Risque de régression
Aucun sur la production. Veiller à ce que la copie de base soit anonymisée : elle contient des données de clients et de salariés.
Effort · Gain
3 jours · rend les six lots suivants sûrs
P-03 · priorité 2 · performance

Créer onze index et purger le journal d'actions

Constat
Onze requêtes parcourent une table entière faute d'index, dont une table de 2,14 millions de lignes. Le journal d'actions, jamais purgé, compte 5,9 millions de lignes et occupe 38 % de la base.
Préconisation
Créer les onze index, en plaçant la colonne la plus sélective en premier. Purger le journal au-delà de 13 mois, après archivage.
Mise en œuvre
Un index sur (date, dossier_id) ne servirait pas la requête : un index composite ne s'utilise que de gauche à droite. Créer les index hors heures ouvrées, l'opération verrouille brièvement les grandes tables.
Critère d'acceptation
EXPLAIN renvoie type=range et non ALL sur les onze requêtes. Liste des dossiers sous 2 secondes. Base réduite d'environ un tiers.
Risque de régression
Les index ralentissent légèrement les écritures. Négligeable ici : l'application lit bien davantage qu'elle n'écrit. La purge, elle, est irréversible : l'archivage doit être vérifié avant suppression.
Effort · Gain
1 jour · liste des dossiers 9,2 s → environ 2 s
P-04 · priorité 2 · performance

Charger les relations en groupe et poser une garde permanente

Constat
Le tableau de bord exécute 3 847 requêtes pour une page, dont 3 790 sont la répétition de cinq requêtes à un paramètre près.
Préconisation
Charger les relations en groupe, avec une sélection ciblée des colonnes réellement affichées. Activer en développement une garde qui fait échouer le code dès qu'une relation est chargée dans une boucle.
Mise en œuvre
Corriger les quatre écrans ne sert à rien si le défaut revient au prochain développement. La garde est ce qui rend la correction durable.
Critère d'acceptation
Moins de 40 requêtes sur le tableau de bord, mesurées par le profileur. Affichage sous 1 seconde. La garde fait échouer un code fautif écrit exprès.
Risque de régression
Réel : une sélection de colonnes trop étroite casse un affichage qui utilisait un champ non demandé. D'où les tests du lot P-02 en amont.
Effort · Gain
4 jours · tableau de bord 18,4 s → environ 0,9 s, soit 7 heures hebdomadaires rendues à l'équipe
P-05 · priorité 2 · intégrité des données

Passer les montants en décimal et les dates en type date

Constat
Sept colonnes de montants stockées en nombre à virgule flottante, quatre colonnes de dates stockées en texte. Écart cumulé mesuré de 847,32 € entre la somme des lignes et les totaux stockés.
Préconisation
Convertir les montants en DECIMAL(12,2) et les dates en type date. Reprendre les valeurs existantes dans la même opération.
Mise en œuvre
Ce lot se traite tôt : chaque jour ajoute des lignes dans le mauvais type, et la reprise devient plus lourde. Les dates en texte ne se trient correctement que si elles sont déjà au format ISO.
Critère d'acceptation
La somme des lignes égale le total stocké au centime près sur les 2,14 millions d'interventions. Un tri par date rend le même ordre qu'un tri par la nouvelle colonne.
Risque de régression
Élevé si une date est mal formée : la conversion la rejettera. Relever et corriger les valeurs hors format avant la migration, pas pendant.
Effort · Gain
3 jours · supprime une source d'écart comptable
P-06 · priorité 3 · intégrité relationnelle

Poser les clés étrangères et traiter les 1 847 orphelins

Constat
Vingt-trois relations sans contrainte, 1 847 lignes orphelines déjà présentes, 34 doublons sur une référence censée être unique.
Préconisation
Traiter les orphelins et les doublons d'abord, poser les contraintes ensuite. L'ordre inverse échoue : la base refuse une contrainte que les données existantes violent.
Mise en œuvre
Chaque orphelin demande une décision : rattacher ou supprimer. Les 34 doublons relèvent du métier, pas de la technique : c'est au gestionnaire de dire lequel garder.
Critère d'acceptation
Zéro orphelin, zéro doublon de référence, 23 contraintes actives. Une suppression d'essai en recette refuse bien de laisser un orphelin.
Risque de régression
Modéré. Une contrainte peut révéler un traitement qui supprimait silencieusement des lignes liées : ce traitement échouera désormais, ce qui est l'effet recherché mais demande d'être prêt à le corriger.
Effort · Gain
2 jours · supprime les écrans qui plantent sur un enregistrement absent

Section 9Plan d'intervention et arbitrage

L'ordre suit le risque, pas la gêne ressentie. La lenteur agace tous les jours ; une sauvegarde qui ne se restaure pas ne se manifeste qu'une fois.

LotContenuEffortEffet
1 · ImmédiatRestauration d'essai sur serveur vierge, chronométrée0,5 jSavoir si l'entreprise sait redémarrer
2 · Semaine 1Environnement de recette, 12 tests sur les parcours critiques3 jLe filet avant de toucher au code
3 · Semaine 2Les 11 index manquants, purge du journal d'actions1 jListe des dossiers 9,2 s → ~2 s
4 · Semaine 2-3Chargement groupé sur les 4 écrans, garde anti-N+14 jTableau de bord 18,4 s → ~0,9 s
5 · Semaine 4Montants en décimal, dates en type date, reprise des données3 jÉcarts d'arrondi, tris faux
6 · Semaine 5Clés étrangères, nettoyage des 1 847 orphelins2 jÉcrans qui plantent
7 · À planifierMise à niveau des dépendances, trois versions majeures5 à 8 jCorrectifs de sécurité
■ mesuré ■ après les lots 3 et 4 Tableau de bord 18,4 s 0,9 s Liste des dossiers 9,2 s 0,6 s Planning 6,8 s 0,8 s Fiche client 3,1 s 0,4 s
Mesures obtenues sur une copie de la base de production, avec les mêmes volumes. Ce sont des mesures de recette, à confirmer en production après déploiement.

Ce que je ne recommande pas

Un remplacement de l'ERP par une solution du marché a été évoqué. L'application porte six ans de règles métier qu'aucun progiciel ne reproduira sans paramétrage lourd, et le budget d'une migration dépasse de loin les dix-neuf jours de ce plan. La question se reposera, mais elle ne se pose pas tant que l'application n'a pas été remise en état : on ne compare pas un logiciel sain à un logiciel négligé.

Section 10Annexe : ce qui n'a pas été mesuré

  • La sécurité applicative. Aucun test d'intrusion, aucune tentative d'élévation de privilège, aucune revue des injections. Cela demande une autorisation écrite et un périmètre contractuel.
  • La conformité des traitements. L'application contient des données personnelles de clients et de salariés. Le registre des traitements et les durées de conservation n'ont pas été examinés.
  • Les 168 autres tables. Douze contrôleurs ont été revus, sur une application qui en compte plus de soixante. Les défauts relevés se retrouvent probablement ailleurs, mais cela reste une hypothèse.
  • Le coût des 7 heures hebdomadaires. Le calcul repose sur la fréquence d'ouverture observée et le nombre d'utilisateurs. Il ne tient pas compte de ce que les personnes font pendant l'attente.
  • L'ergonomie. Six entretiens ne constituent pas une étude d'usage. Plusieurs remarques recueillies portaient sur des parcours, pas sur des temps.

PrestationTransparence : ce que coûte cette intervention

Nous publions le détail de nos prestations parce qu'un audit dont on ne connaît ni le temps ni le prix se juge mal. Les montants ci-dessous sont des ordres de grandeur indicatifs pour une intervention de ce calibre ; seul un devis nominatif engage.

PosteTempsCe qui est faitMontant indicatif HT
Audit et diagnostic5 jRelevés sur 3 semaines, rapprochements, entretiens, rédaction du présent rapport2 400 €
Mise en œuvre13,5 jLes lots du plan d'intervention, du premier au dernier, avec leurs critères d'acceptation6 480 €
Accompagnement et transfert2 jAteliers, documentation, formation de l'équipe, reprise en main sans nous960 €
Total20,5 jTaux journalier unique de 480 € HT, sans forfait caché ni frais de dossier9 840 €

Ce qui est compris, et ce qui ne l'est pas

Compris dans le montantHors prestation
Le rapport complet, remis en main propre et commentéHébergement, noms de domaine, abonnements tiers, facturés au réel par le fournisseur
Les ateliers et la formation de l'équipeLicences logicielles et services d'intelligence artificielle, à la consommation
La documentation et les procédures laissées en placeCréation de contenu éditorial, photographies, traductions
Les points de suivi : 4 points de 45 min sur le premier semestre, inclusDemandes hors périmètre, chiffrées séparément avant d'être engagées
Les correctifs sur ce que nous avons livré, sans limite de duréeÉvolutions fonctionnelles nouvelles, qui font l'objet d'un devis

Accompagnement, pédagogie et ressources

Pédagogie

Trois séances de 2 h : administration courante, lecture des erreurs, procédure de restauration jouée en réel. Notre critère de réussite est que l'équipe sache faire sans nous : un client qui dépend de son prestataire pour comprendre son propre outil a été mal servi.

Ressources mises à disposition

Copie de recette anonymisée, jeu de données de test, journal d'audit consultable, modèles de requêtes de contrôle. Tout cela reste au client, dans un format lisible et sans nous.

Rendu

Rapport de 22 pages, 2 ateliers, guide d'exploitation, procédure de sauvegarde et de restauration éprouvée. Chaque chiffre du rapport renvoie à son relevé horodaté, et chaque préconisation porte son critère d'acceptation : vous pouvez vérifier vous-même que le travail est fait, y compris contre nous.

Ce que nous ne promettons pas

Les gains annoncés par préconisation sont des hypothèses sauf mention contraire explicite, et ils sont présentés comme telles. Nous nous engageons sur les critères d'acceptation, qui sont vérifiables, pas sur un résultat commercial qui dépend aussi de ce que fait le marché.

HANDICODE · Dossier C-2026-047 · Rapport de démonstration
Méthode : 18 400 requêtes profilées, 47 plans d'exécution analysés, 12 contrôleurs revus, 6 entretiens.
Les mesures d'un audit réel sont horodatées et rejouables ; chaque chiffre renvoie à son relevé.

Les autres rapports

Le même regard sur votre site

L'Audit 360 mesure votre site en une minute, sans inscription, et vous rend les constats avec leurs sources et leurs limites.

Lancer un audit gratuit