La reprise de l'historique
Migrer l'historique d'un ancien CRM vers une base de production, sans perdre une ligne et sans couper le service.
Le problème
Le client gérait jusque-là son activité dans un ancien CRM low-code. Cet outil ne suivait plus : rigide, sans cartographie, avec un modèle de bien immobilier trop pauvre et l'impossibilité de rattacher plusieurs fiches de financement à un même dossier. Un nouveau CRM sur mesure avait donc été construit — mais un CRM sans son historique n'a aucune valeur opérationnelle. Il fallait tout faire entrer.
Le volume vérifié : 16 402 enregistrements et environ 12 185 archives de documents, soit près de 103 Go. Des données hétérogènes, de qualité inégale, et un ancien outil qui exposait ses champs sous des noms génériques illisibles — impossible de deviner la structure, il fallait l'inspecter enregistrement par enregistrement.
Pourquoi pas un simple export / import
La solution évidente — un gros fichier exporté, transformé, réinjecté d'un coup — a deux défauts rédhibitoires quand la cible est une base vivante : elle est tout-ou-rien, et elle est difficile à rejouer. Si elle casse au dossier 9 000, on ne sait ni où on en est, ni comment reprendre sans dupliquer.
J'ai préféré une approche par flux, et idempotente : l'ancien outil émettait un appel réseau par dossier au moment de son passage au statut « migré », et chaque dossier était reçu, stocké, puis traité indépendamment des autres.
Une porte d'entrée
Une fonction serveur reçoit le dossier et son archive de documents, et les dépose tels quels — sans aucune transformation.
Une table tampon
La donnée brute atterrit dans une table d'attente et un stockage privé. La source n'est jamais perdue, quoi qu'il arrive ensuite.
Un traitement par lots
Un second passage lit la structure réelle de chaque dossier et crée les entités du nouveau CRM : dossier, client, bien, fiche, documents.
Une clé stable
L'idempotence tient à une clé issue de la référence d'origine. Rejouer un dossier déjà importé le met à jour, ne le duplique jamais.
Tolérer les données sales sans renoncer à la rigueur
Le vrai défi n'était pas le volume, c'était la qualité de la source. Exiger des données parfaites avant de migrer aurait été irréaliste : rien n'aurait jamais migré. J'ai donc adopté une posture de tolérance traçable — importer le dossier incomplet, mais marquer explicitement ses manques.
- Marquer les champs comblés. Un champ obligatoire manquant était rempli par une valeur par défaut reconnaissable, et le dossier recevait un drapeau. La donnée n'a jamais été inventée en silence : elle reste identifiable et corrigeable après coup.
- Prévoir une unité de repli. Quand l'unité opérationnelle d'un dossier était introuvable, plutôt que de bloquer l'import, je le rattachais à une unité de repli dédiée. La migration continue, et les cas à réexaminer restent isolés au même endroit.
- Séparer le vivant du mort. Tous les dossiers n'avaient pas la même valeur opérationnelle. À partir d'une date butoir métier, j'ai distingué les dossiers réellement actifs de l'historique mort — et retrouvé ainsi près de 9 900 dossiers au cœur de l'activité d'audit, qu'il était hors de question de laisser se diluer dans la masse archivée.
Ce dernier point cachait le risque le plus grave du chantier. Le statut d'un dossier n'est pas un libellé mais l'état d'une machine à états — on en comptait vingt-sept, avec des transitions autorisées selon le rôle. Un dictionnaire de correspondance incomplet aurait transformé silencieusement ces 9 900 dossiers actifs en dossiers « clôturés », donc invisibles. La frontière entre une migration réussie et un sinistre fonctionnel tenait à la complétude d'une table de traduction.
Une donnée imparfaite peut entrer, à condition de savoir laquelle et pourquoi.
Dédoublonner sans jamais supprimer
L'import répété et l'historique de l'ancien outil avaient produit des fiches clients en double. J'ai conçu un traitement de fusion guidé par un principe non négociable : jamais de suppression.
- Regrouper. Les doublons sont rassemblés en grappes selon une clé combinant prénom, nom, téléphone, ville et unité opérationnelle.
- Élire une fiche de référence. Dans chaque grappe, on garde la plus riche, selon une priorité claire : le plus de dossiers, puis le plus de documents, puis l'ancienneté.
- Repointer, puis archiver. Les dossiers, documents, contacts et propriétés de société sont repointés vers la fiche de référence ; les fiches perdantes sont archivées, jamais effacées, avec une trace de la fusion qui permet le retour arrière. Le tout dans une transaction atomique, pour n'avoir aucun état intermédiaire incohérent.
- Ne pas tout automatiser. Un garde-fou interdisait toute fusion entre deux unités opérationnelles différentes. Et les cas ambigus — deux conjoints partageant un même numéro, une grappe hétérogène — ont été laissés à l'arbitrage humain. Une fusion automatique incertaine est plus dangereuse qu'un reliquat manuel.
Résultat : 104 fiches fusionnées et 108 dossiers repointés, sans créer un seul orphelin — intégrité vérifiée par requête après coup.
Cinq pièges payés en production
- Des caractères que PostgreSQL refuse. Certains enregistrements contenaient des caractères binaires nuls. L'ingestion échouait dessus en silence. Il a fallu les nettoyer avant toute insertion.
- Un faux négatif massif. Une requête d'analyse renvoyait zéro partout, laissant croire que des champs étaient absents de toute la base. En réalité, le chemin d'accès au champ était faux. Sur des données opaques, il faut inspecter un enregistrement réel avant de conclure, jamais se fier à une recherche par nom de champ.
- Des requêtes qui dépassent le délai. Filtrer les 16 402 lignes de la table tampon sur des champs profondément imbriqués dépassait le temps maximal autorisé. La parade : filtrer d'abord sur un critère simple et indexé, trier ensuite — plutôt que demander à la base un tri coûteux sur l'ensemble.
- Un retour en arrière silencieux. Une procédure stockée centrale, redéfinie intégralement à chaque évolution, est un jour repartie d'une version trop ancienne : elle a annulé sans bruit des changements intermédiaires. J'ai réconcilié une version de référence unique et ajouté un contrôle préalable des colonnes attendues.
- Un quota de stockage à 103 %. Le volume de documents dépassait le quota, avec une taille moyenne par archive bien supérieure à ce que le métier laissait attendre. Plutôt que réagir à l'aveugle, j'ai écrit un script d'échantillonnage pour chercher des doublons internes aux archives avant de décider quoi que ce soit. Mesurer avant d'agir.
Ce que j'en retiens
Le principe directeur de cette migration tient en une phrase : ne jamais perdre la donnée d'origine, et rendre chaque étape rejouable. La table tampon et l'idempotence sont les deux mécanismes qui ont transformé un chantier risqué en opération contrôlable. Une opération qu'on peut rejouer vaut mieux qu'une opération qu'on croit parfaite.
La tolérance a eu un coût, que j'assume : une part de nettoyage manuel est restée nécessaire, et la question du coût de stockage est restée ouverte — je ne l'ai pas tranchée seul.