Vous héritez d’un système maintenu par des cron jobs, du savoir tribal et un schéma de base de données qui a l’air d’avoir été conçu lors d’un exercice d’évacuation. Quelqu’un prononce les mots magiques : « Réécrivons-le depuis zéro. » Les têtes hochent. Les feuilles de route sont rafraîchies. Un nouveau dépôt apparaît comme un cahier tout neuf le 1er janvier.
Puis la production arrive. La réécriture ne connaît pas vos clients, vos cas limites, vos contraintes opérationnelles ni la gravité des données. Et votre rotation on-call ne s’est certainement pas engagée pour « deux systèmes, tous deux cassés, pour toujours ».
Le mythe : pourquoi « réécrire depuis zéro » semble vrai
Les réécritures vendent de l’espoir. Elles offrent une rupture propre avec le bazar accumulé : plus de frameworks hérités, plus de hacks « temporaires » de 2017, plus de modules non testables, plus de cette procédure stockée que tout le monde redoute de toucher. L’argument a raison sur le plan émotionnel. Le problème est que les systèmes en production ne fonctionnent pas sur les émotions. Ils fonctionnent sur des invariants.
Une réécriture depuis zéro est généralement vendue comme un projet technique. C’est en réalité un pari organisationnel : que vous pouvez reconstruire non seulement le code, mais aussi le comportement, la sémantique des données, la posture opérationnelle et la gestion des pannes — tandis que l’ancien système continue d’évoluer sous la charge réelle des clients.
Voici la partie que les gens omettent dans le diaporama de réécriture : l’ancien système est un registre fossile d’incidents réels. Il contient le tissu cicatriciel des pannes, des tentatives de fraude, des appareils clients bizarres, des défaillances partielles et des surprises réglementaires. Ce tissu cicatriciel est laid. Il est aussi précieux.
Les réécritures ignorent que les « exigences » ne sont pas dans le système de tickets. Elles sont dans les graphiques de production, dans les notes du on-call et dans les hypothèses silencieuses qui maintiennent les lumières allumées. Quand vous réécrivez, vous supprimez ces hypothèses — puis vous les redécouvrez à 2h13 du matin.
Blague n°1 : Un plan de réécriture, c’est comme acheter un nouveau tapis de course pour se mettre en forme. L’achat donne l’impression d’être productif ; la partie course est celle où la réalité se manifeste.
Pourquoi les réécritures échouent en production (les vraies raisons)
1) La parité fonctionnelle est un piège, pas un jalon
Les équipes considèrent la « parité de fonctionnalités » comme une checklist. En pratique, l’ancien système n’a pas des fonctionnalités ; il a des comportements. Les comportements incluent des valeurs par défaut non documentées, des bizarreries de temporisation, des attentes d’idempotence, des sémantiques de retry et des flux de correction de données qui se produisent hors du chemin heureux.
Quand une réécriture vise la parité, elle cible la surface visible UI/API et rate les parties sales qui comptent : comment le système se comporte quand une passerelle de paiement timeoute, quand un downstream est lent, quand un client retry un POST, quand les horloges divergent, quand il faut retraiter une journée d’événements.
2) Les données sont le produit, et les données sont lourdes
La plupart des systèmes sont des systèmes de données déguisés en interface utilisateur. Une réécriture qui ne commence pas par la sémantique des données — ce que signifient les enregistrements, comment ils évoluent, ce qui peut être en cohérence éventuelle — dérivera vers « un nouveau schéma de base de données qui semble plus joli » puis s’écrasera contre la réalité au moment du cutover.
La migration des données n’est pas un projet de week-end. C’est un exercice de fiabilité soutenu avec backfills, écritures doubles (ou change data capture), réconciliation et plans de rollback. Si votre plan de réécriture n’inclut pas des mois d’exécution des deux chemins de données, vous ne planifiez pas un cutover — vous planifiez un lancer de pièce.
3) La réécriture crée une organisation à cerveau divisé
Deux bases de code signifie deux priorités, deux files de bugs, deux modèles opérationnels et une base client partagée qui attend le même service. En général, les personnes seniors sont attirées par la réécriture, laissant le système legacy avec une capacité réduite et un risque croissant. Puis un incident survient dans le legacy, et l’agenda de réécriture est pillé pour répondre. La réécriture ralentit. Le legacy se dégrade. Tout le monde perd.
4) La préparation opérationnelle n’est pas « on a maintenant Kubernetes »
Les stacks modernes peuvent aggraver les choses quand elles sont utilisées comme accessoires de crédibilité. Échanger un ensemble de modes de défaillance contre un autre n’est pas un progrès ; ce sont juste de nouvelles façons de pager les gens.
La préparation opérationnelle concerne : des SLO bien définis, de l’instrumentation, la qualité des alertes, des rollouts contrôlés, la modélisation de capacité, la gestion des dépendances, des runbooks et une culture capable de soutenir le changement continu. Si l’équipe de réécriture ne sait pas bien faire tourner l’ancien système, elle ne fera pas mieux avec le nouveau — juste avec du YAML plus brillant.
5) La performance est une propriété émergente, on ne peut pas l’« unit-test »
L’ancien système comporte des optimisations de performance issues du combat : cache à des couches étranges, tables dénormalisées, agrégats précomputés, index savamment placés, coalescence de requêtes et règles « ne faites pas ça sur le chemin chaud ». La réécriture commence souvent propre, puis des régressions de performance apparaissent sous une charge de production. Ensuite vous collez des caches, des queues et des jobs en arrière-plan, reconstruisant finalement la même complexité — sans la mémoire institutionnelle.
6) Le nouveau système est correct à petite échelle et faux à grande échelle
Les revues de code détectent les problèmes locaux. Elles ne détectent pas le comportement système sous pannes partielles. Les réécritures échouent parce qu’elles modélisent le monde comme fiable et cohérent. La production n’est ni l’un ni l’autre.
7) La sécurité et la conformité ne sont pas « pour plus tard »
Les réécritures reportent fréquemment les contrôles de sécurité, les pistes d’audit, les règles de rétention et le principe du moindre privilège. Ensuite vous découvrez que les « logs bizarres » et les patterns d’accès de l’ancien système existaient parce qu’un auditeur avait posé une question très précise. Vous paniquez ou vous retardez le cutover. Les deux coûtent cher.
Faits et histoire : l’industrie est déjà passée par là
- Années 1980–1990 : De grandes organisations ont tenté à répétition des réécritures pilotées par des outils CASE des systèmes mainframe ; beaucoup se sont effondrées sous la complexité du périmètre et de la migration des données.
- L’effort de l’an 2000 (Y2K) a enseigné aux entreprises une leçon brutale : remplacer tout n’est rarement faisable ; la correction ciblée et le triage basé sur le risque l’emportent souvent.
- L’ère des déploiements ERP « big bang » a montré un schéma : les cutovers échouent quand les processus métier ne sont pas cartographiés vers des workflows réels et leurs exceptions.
- L’essor de l’architecture orientée services (SOA) promettait de la modularité ; beaucoup de projets ont livré des monolithes distribués avec plus de latence et un débogage plus difficile.
- Popularité des microservices (mi-2010s) a accru la tentation de réécrire, mais a aussi augmenté le coût de la maturité opérationnelle : tracing, cartographie des dépendances et confinement des pannes sont devenus obligatoires.
- L’outillage Change Data Capture (CDC) a mûri et a rendu les migrations incrémentales plus pratiques, déplaçant l’économie loin des réécritures en big-bang.
- L’élasticité du cloud a réduit certains risques de capacité, mais introduit de nouveaux : voisins bruyants, quotas de services et incidents liés à la facturation.
- L’observabilité comme discipline (métriques, logs, traces) est devenue mainstream ; elle a exposé que beaucoup de pannes « legacy » étaient en réalité des problèmes de dépendance et de capacité.
Trois mini-récits des tranchées d’entreprise
Mini-récit 1 : L’incident causé par une mauvaise hypothèse
Une entreprise SaaS de taille moyenne a réécrit son service de facturation dans un nouveau langage pour « le rendre maintenable ». L’équipe a fait un travail minutieux sur les tests unitaires et le contrat API. Ils ont construit un schéma de base de données propre et déployé derrière un feature flag.
L’hypothèse erronée était subtile : ils supposaient que tous les clients traiteraient POST /charge comme non-idempotent et ne renverraient jamais automatiquement de retry. Le système legacy avait implémenté en douce l’idempotence via un token fourni par le client, parce qu’années auparavant un client mobile renvoyait des requêtes sur des réseaux instables.
La réécriture ne l’a pas implémenté. Lors d’un événement de jitter réseau entre régions, un sous-ensemble de clients a renvoyé des charges. Le nouveau service a créé plusieurs charges. Le support s’est enflammé. Les finances sont intervenues. Les ingénieurs ont été pagés pour un « incident d’exactitude des données », un type d’incident qui ne cesse de faire mal même quand les graphiques redeviennent verts.
La correction n’était pas « ajouter plus de tests ». Il a fallu traiter l’idempotence et les retries comme des exigences de premier ordre, les documenter comme invariants et construire des outils de réconciliation. Ils ont aussi ajouté un canary qui simulait des retries et vérifiait que le grand-livre restait stable.
Morale : si vous ne modélisez pas explicitement le comportement des clients, le réseau le fera pour vous.
Mini-récit 2 : L’optimisation qui s’est retournée contre eux
Une équipe plateforme interne d’entreprise a réécrit un pipeline de reporting pour réduire les coûts. Ils ont remplacé un job d’agrégation piloté par la base par un pipeline streaming et ont réglé les batchs de façon agressive pour minimiser CPU et stockage.
L’optimisation brillait dans les benchmarks synthétiques. La production était différente. Leur batching a augmenté la latence de bout en bout et créé des pics de charge sur les services downstream. Un pipeline « bon marché » est devenu un générateur de thundering herd. Le downstream a limité le débit. Les retries se sont accumulés. Les buffers internes du système streaming ont grossi, puis la logique de backpressure a commencé à abandonner des messages sous charge soutenue.
Le système avait maintenant deux problèmes au lieu d’un : les rapports étaient en retard et certains étaient erronés. L’équipe a passé des semaines à construire des contrôles compensatoires : dead-letter queues, outils de replay et un processus de correction des « données tardives ». Les coûts ont augmenté, pas diminué, parce que la surcharge opérationnelle est aussi un coût — payé en attention humaine.
Ils ont finalement réduit le batching, accepté un coût de calcul steady-state plus élevé et mis des SLO stricts sur la fraîcheur et l’exactitude. L’« optimisation » avait optimisé la mauvaise chose : la facture, pas le produit.
Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une société modernisant une pile d’authentification a résisté à la tentation de réécrire et a fait quelque chose douloureusement peu glamour : ils ont construit une suite de tests de compatibilité basée sur le trafic de production réel. Pas seulement des tests unitaires — des requêtes enregistrées, des tokens cas limites et des modes de défaillance représentatifs.
Ils ont déployé le nouveau service d’abord en shadow reader. Il validait les tokens et calculait des décisions sans les appliquer. Pendant des semaines il comparait ses sorties à celles du système legacy et consignait les divergences avec assez de contexte pour déboguer.
Ils ont découvert une longue traîne d’anomalies : tolérance au skew d’horloge, un ancien algorithme de signature encore utilisé par un client, et un code d’erreur spécifique sur lequel un partenaire comptait. Rien de cela n’était dans la spécification. Tout cela comptait.
Quand ils ont finalement basculé le trafic, le lancement a été presque ennuyeux. Le on-call a reçu quelques pages — surtout parce que les tableaux de bord étaient trop sensibles — puis les choses se sont stabilisées. La « pratique ennuyeuse » a été de traiter la migration comme un exercice de collecte de preuves, pas comme un saut héroïque.
Ce qui fonctionne réellement : des patterns qui survivent au contact de la réalité
Commencez par les invariants, pas par l’architecture
Avant de débattre des frameworks, écrivez les invariants. Les choses qui doivent rester vraies même quand les dépendances échouent :
- Règles d’idempotence : quelles opérations peuvent être réessayées sans risque, et comment.
- Contraintes d’exactitude des données : ce qui « ne peut pas arriver » (double facturation, solde négatif, enregistrement d’audit perdu).
- Budgets de latence et objectifs de disponibilité : ce que l’utilisateur tolérera.
- Exigences de cohérence : où la cohérence éventuelle est acceptable et où elle ne l’est pas.
- Exigences de rollback : ce que signifie annuler un déploiement, une migration, un backfill.
Utilisez le pattern strangler fig (et engagez-vous à l’appliquer)
Le pattern strangler fig fonctionne parce qu’il respecte que les systèmes en production sont des écosystèmes vivants. Vous ne remplacez pas l’arbre en un jour ; vous faites pousser un nouveau système autour et vous transférez progressivement les responsabilités.
Concrètement, cela signifie :
- Mettre une couche de routage (API gateway, reverse proxy ou ingress service mesh) devant l’ancien système.
- Déplacer un endpoint, un workflow ou une tranche de domaine à la fois.
- Conserver un rollback rapide : router le trafic en arrière immédiatement.
- Utiliser des lectures shadow et des comparaisons lorsque c’est possible.
Privilégiez « remplacer derrière une interface » plutôt que « tout réécrire »
Quand un sous-système est vraiment pourri, remplacez-le derrière une interface stable. Gardez le contrat. Gardez les métriques. Gardez les runbooks opérationnels. Changez l’implémentation interne. Cela réduit le rayon d’explosion et empêche les équipes de reconstruire tout l’univers juste pour réparer un mur.
Migration des données : dual-write ou CDC, plus réconciliation
Choisissez votre poison avec soin :
- Dual-write : l’application écrit dans l’ancien et le nouveau store. Conceptuellement plus simple, plus difficile à rendre correct sous défaillance partielle.
- CDC : traiter l’ancienne base comme source de vérité et streamer les changements vers le nouveau store. Souvent plus robuste, mais exige un contrôle strict de l’ordre et de l’évolution des schémas.
Quoi qu’il en soit, vous avez besoin de réconciliation : jobs périodiques comparant comptes, checksums et invariants entre ancien et nouveau. Sans réconciliation, vous fonctionnez au feeling.
Construisez la parité opérationnelle avant la parité fonctionnelle
La parité opérationnelle est la capacité à faire tourner, déboguer et récupérer. Elle inclut :
- Tableaux de bord montrant saturation, erreurs, latence et santé des dépendances.
- Alertes actionnables (pages sur les symptômes, pas sur le bruit).
- Runbooks qui supposent des pannes partielles et incluent des rollbacks.
- Tests de charge reproduisant la forme du trafic production, pas seulement le volume.
Une citation à coller sur votre écran
L’espoir n’est pas une stratégie.
— James Cameron
Les opérations ne sont pas cyniques ; elles sont allergiques à la pensée magique. Planifiez les modes de défaillance que vous aurez forcément.
Gardez l’ancien système sain pendant la migration
C’est là que le leadership doit grandir. Si vous affamez le système legacy pendant que vous construisez le remplacement, le legacy s’effondrera et consommera l’équipe de remplacement. Allouez une capacité explicite pour le travail de fiabilité legacy pendant la migration. Traitez-le comme de la réduction de risque, pas comme un « effort gaspillé ».
Blague n°2 : La seule chose pire qu’un système fragile est deux systèmes fragiles qui ne sont pas d’accord sur qui est responsable.
Tâches pratiques : commandes, sorties, ce que cela signifie et ce que vous décidez
Voici les tâches que vous exécutez réellement quand quelqu’un affirme « la réécriture résoudra la performance/la fiabilité ». Vous n’argumentez pas. Vous mesurez. Chaque tâche ci‑dessous inclut une commande réaliste, une sortie typique, ce que cette sortie signifie et la décision que vous en tirez.
1) Identifier saturation CPU vs plaintes de latence
cr0x@server:~$ uptime
14:22:01 up 37 days, 4:11, 2 users, load average: 18.42, 17.96, 16.88
Ce que cela signifie : Une charge moyenne bien au-dessus du nombre de CPU (vous devez connaître le nombre de cœurs) suggère une contention CPU ou un embouteillage de la queue runnable, éventuellement aussi de l’attente I/O suivant la charge.
Décision : Ne lancez pas une réécriture parce que « c’est lent ». Déterminez d’abord si vous êtes lié par le CPU, l’I/O ou des verrous. Ensuite : vérifiez la répartition CPU et la file d’exécution.
2) Vérifier l’utilisation par CPU et iowait
cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.5.0 (prod-app-01) 02/04/2026 _x86_64_ (16 CPU)
22:22:10 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
22:22:11 all 62.11 0.00 11.83 18.44 0.00 0.52 0.00 0.00 0.00 7.10
22:22:11 0 71.00 0.00 12.00 10.00 0.00 0.00 0.00 0.00 0.00 7.00
Ce que cela signifie : Un %iowait élevé suggère que le CPU attend le stockage disque/réseau. Un %usr élevé suggère une charge processeur. Ici c’est mixte : le CPU est occupé et attend de l’I/O.
Décision : Enquêter sur la latence de stockage et de base de données avant de réécrire la logique applicative. Une réécriture ne changera pas vos disques.
3) Vérifier la pression mémoire et le swapping
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 62Gi 54Gi 1.1Gi 1.8Gi 6.9Gi 4.2Gi
Swap: 8.0Gi 2.7Gi 5.3Gi
Ce que cela signifie : L’utilisation du swap est non négligeable. Si le système swappe activement, la latence tail va grimper.
Décision : Avant de réécrire, corrigez le dimensionnement mémoire, les fuites ou les limites de conteneurs. Si vous ne pouvez pas faire tourner le service actuel sans swap, le nouveau le fera probablement aussi — juste plus vite.
4) Vérifier le swapping actif et les fautes majeures
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
8 2 2795520 1187328 9120 6852140 0 64 1220 1780 8210 9230 61 11 7 21 0
7 1 2795584 1169000 9120 6854100 0 128 1100 1650 8030 9012 60 12 8 20 0
Ce que cela signifie : so (swap out) indique du swapping actif. wa est aussi élevé, cohérent avec de l’attente I/O.
Décision : Traitez cela comme un incident ops, pas comme une opportunité roadmap. Réduisez l’empreinte mémoire, corrigez les voisins bruyants ou scalez. La réécriture ne guérira pas le swapping.
5) Trouver les plus gros consommateurs CPU
cr0x@server:~$ ps -eo pid,comm,%cpu,%mem --sort=-%cpu | head
PID COMMAND %CPU %MEM
8123 java 345.2 18.4
9001 redis-server 72.1 3.2
7442 nginx 38.0 0.8
Ce que cela signifie : Un seul processus consommant plusieurs cœurs peut être attendu, mais confirmez que cela s’aligne sur le débit. Si le CPU est élevé et que le débit est faible, vous êtes en train de tourner en rond ou enfermé sur des locks.
Décision : Profilez le processus chaud et vérifiez la contention des threads. Si l’argument de réécriture est « le nouveau langage sera plus rapide », exigez des preuves avec des flame graphs d’abord.
6) Identifier rapidement les goulots disques
cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (prod-db-01) 02/04/2026 _x86_64_ (16 CPU)
Device r/s w/s rMB/s wMB/s await svctm %util
nvme0n1 220.0 410.0 35.2 88.1 18.40 0.90 92.50
Ce que cela signifie : %util élevé et await croissant implique que le périphérique est saturé ou que les queues s’accumulent. Un svctm bas avec await élevé indique la profondeur de queue/la latence, pas la lenteur brute du périphérique.
Décision : Vous avez besoin d’optimisation de requêtes, de changements d’index ou d’une distribution I/O. Une réécriture qui conserve les mêmes patterns d’accès heurtera le même mur.
7) Vérifier la capacité du système de fichiers et l’épuisement des inodes
cr0x@server:~$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 900G 855G 45G 96% /
cr0x@server:~$ df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 5900000 5892000 8000 100% /
Ce que cela signifie : Un disque presque plein est mauvais ; l’épuisement des inodes est plus sournois et peut casser les déploiements, le logging et les fichiers temporaires.
Décision : Stoppez. Nettoyez. Ajoutez des politiques de rétention. Si votre réécriture est « parce que les déploiements échouent », et que la raison est les inodes, vous n’avez pas besoin d’une nouvelle base de code — vous avez besoin d’entretien.
8) Vérifier les erreurs réseau et les retransmissions
cr0x@server:~$ netstat -s | egrep -i 'retrans|listen|listenoverflows|packet receive errors' | head
12455 segments retransmitted
37 packet receive errors
Ce que cela signifie : Les retransmissions et erreurs de réception peuvent produire de la « latence aléatoire ». Votre appli peut être innocente.
Décision : Enquêtez sur la NIC, des mismatches MTU, des load balancers surchargés ou des problèmes inter-AZ avant de réécrire la couche service.
9) Inspecter les états de connexion TCP (fuites ou clients lents)
cr0x@server:~$ ss -s
Total: 14021
TCP: 10234 (estab 812, closed 9132, orphaned 5, timewait 7210)
Transport Total IP IPv6
RAW 0 0 0
UDP 29 25 4
TCP 1102 1011 91
INET 1131 1036 95
FRAG 0 0 0
Ce que cela signifie : Un nombre excessif de timewait peut indiquer des connexions de courte durée sans keep-alives, ou un comportement client agressif en retry.
Décision : Ajustez la réutilisation des connexions, les paramètres du load balancer et le comportement des clients. Une réécriture ne changera pas la physique TCP.
10) Vérifier le throttling des conteneurs (les limites CPU Kubernetes mordent)
cr0x@server:~$ kubectl -n payments top pods | head
NAME CPU(cores) MEMORY(bytes)
payments-api-6d8d6c6b6c-2qz7m 980m 740Mi
payments-api-6d8d6c6b6c-pk9h4 995m 755Mi
cr0x@server:~$ kubectl -n payments describe pod payments-api-6d8d6c6b6c-2qz7m | egrep -i 'Limits|Requests|throttl' -n | head -n 20
118: Limits:
119: cpu: 1
120: memory: 1Gi
Ce que cela signifie : Des pods coincés à la limite CPU subissent probablement du throttling, provoquant des pics de latence qui ressemblent à « la nouvelle appli est plus lente ».
Décision : Réexaminez les limits/requests CPU et les politiques HPA. Si vous réécrivez sur Kubernetes sans comprendre le throttling, vous avez juste déplacé le problème dans du YAML.
11) Identifier la contention sur les verrous de base de données (un angle mort classique des réécritures)
cr0x@server:~$ psql -U postgres -d appdb -c "select pid, wait_event_type, wait_event, state, query from pg_stat_activity where wait_event_type is not null order by pid limit 5;"
pid | wait_event_type | wait_event | state | query
------+-----------------+----------------+--------+------------------------------------------
4142 | Lock | transactionid | active | UPDATE invoices SET status='paid' ...
4221 | Lock | relation | active | ALTER TABLE ledger ADD COLUMN ...
Ce que cela signifie : Les requêtes sont bloquées sur des verrous. Les problèmes de performance peuvent être dus à des DDL de migration, pas à la qualité du code applicatif.
Décision : Planifiez les migrations lourdes, réduisez la portée des verrous, utilisez des techniques de changement de schéma en ligne. Ne réécrivez pas parce que « Postgres est lent » alors que vous tenez des verrous.
12) Vérifier les requêtes lentes et cibler les plus coupables
cr0x@server:~$ psql -U postgres -d appdb -c "select calls, mean_exec_time, rows, left(query,120) as q from pg_stat_statements order by mean_exec_time desc limit 5;"
calls | mean_exec_time | rows | q
-------+----------------+------+------------------------------------------------------------
412 | 982.14 | 12 | SELECT * FROM orders WHERE customer_id = $1 ORDER BY created_at DESC LIMIT 50
201 | 744.33 | 1 | SELECT balance FROM accounts WHERE id = $1 FOR UPDATE
Ce que cela signifie : Vous avez des cibles concrètes : ajouter des index, changer la forme des requêtes, réduire le locking. Ceci est généralement moins coûteux qu’une réécriture.
Décision : Corrigez d’abord les requêtes chaudes. Si vous voulez quand même réécrire, au moins conservez les leçons des requêtes afin que le nouveau système ne les reproduise pas.
13) Valider le lag de réplication avant un cutover
cr0x@server:~$ mysql -e "SHOW SLAVE STATUS\G" | egrep -i 'Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running'
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 43
Ce que cela signifie : Un lag de réplication signifie que votre « lecture depuis le nouveau système » peut être obsolète. Cela peut casser les attentes des utilisateurs pendant la migration.
Décision : Acceptez la staleness explicitement (et concevez pour ça) ou ne basculez pas les lectures tant que le lag n’est pas constamment bas.
14) Détecter les changements de taux d’erreur pendant un canary
cr0x@server:~$ kubectl -n payments logs deploy/payments-api --since=5m | egrep -c " 5[0-9][0-9] "
27
Ce que cela signifie : Une augmentation des 5xx après un déploiement est une défaillance canary jusqu’à preuve du contraire.
Décision : Rollback rapide, puis debug avec traces et vérifications de dépendances. Ne « forcez pas » parce que la feuille de route de réécriture l’exige.
15) Confirmer que vous avez assez de descripteurs de fichiers sous charge
cr0x@server:~$ ulimit -n
1024
cr0x@server:~$ cat /proc/$(pgrep -n nginx)/limits | egrep -i "open files"
Max open files 1024 1024 files
Ce que cela signifie : 1024 FD est souvent trop bas pour des proxies/services très sollicités. Vous pouvez rencontrer des échecs de connexion qui ressemblent à « la nouvelle appli est instable ».
Décision : Augmentez les limites, vérifiez les paramètres du runtime de conteneur et retestez. Encore une fois : corrigez les fondamentaux avant de redessiner l’univers.
Playbook de diagnostic rapide : quoi vérifier en premier/deuxième/troisième
Ceci est le drill quand quelqu’un affirme « le système legacy est le goulot » ou « la réécriture sera plus rapide ». Vous pouvez l’exécuter en moins d’une heure sur un incident live (avec précaution) ou en staging avec une charge proche de la production.
Première étape : saturation, erreurs ou latence dépendance ?
- Vérifier les taux d’erreur (pics 5xx/4xx, timeouts). Si les erreurs ont grimpé, la performance peut être un symptôme d’une défaillance partielle.
- Vérifier la saturation : CPU iowait, await disque, retransmissions réseau, connexions BD, pools de threads.
- Vérifier la santé des dépendances : BD, cache, broker de messages, APIs externes, DNS, expiration de certificats.
Objectif : classer le problème : lié au calcul, à l’I/O, aux verrous, au réseau ou à une défaillance de dépendance.
Deuxième étape : trouver la boucle serrée dans le chemin de requête
- Choisir un endpoint orienté utilisateur (trafic le plus élevé ou latence la plus importante).
- Tracer de bout en bout (distributed tracing si disponible ; sinon corrélation par IDs de logs).
- Mesurer le temps passé dans : CPU appli, requête BD, cache, upstream, sérialisation, retries.
Objectif : identifier où le temps est réellement passé, pas où il en a l’air.
Troisième étape : valider par une expérience contrôlée
- Faire un seul changement (index, TTL de cache, taille du pool de connexions, limite CPU).
- Canary sur une petite tranche de trafic.
- Comparer : percentiles de latence, taux d’erreur, métriques de saturation.
Objectif : décisions basées sur des preuves. Si la proposition de réécriture ne résiste pas à ce niveau de contrôle, c’est un projet de moral, pas un projet d’ingénierie.
Erreurs courantes : symptômes → cause racine → correctif
« On a réécrit et la latence a empiré »
Symptômes : latence p95/p99 en hausse, CPU correct, tableaux de bord montrent plus d’appels réseau.
Cause racine : Vous avez décomposé en services sans budget de latence, transformant des appels in-process en chaînes RPC. Vous avez construit un monolithe distribué.
Correctif : Resserrez des frontières bavardes, batcher les appels, introduisez un cache local et imposez des budgets par hop. Privilégiez des APIs coarse-grained plutôt que des frontières microservice « pures ».
« Le cutover a fonctionné, puis un dérive des données est apparue »
Symptômes : Les rapports ne correspondent pas, les soldes diffèrent, les clients voient des états incohérents des jours plus tard.
Cause racine : Écriture double sans sémantique exactly-once ; absence de réconciliation ; événements hors ordre ; règles de fuseau/arrondi incohérentes.
Correctif : Implémentez des jobs de réconciliation et des checks d’invariants ; utilisez des clés d’idempotence ; définissez une source autoritaire par champ ; adoptez CDC avec garanties d’ordre quand possible.
« Le nouveau système est stable, mais l’on-call est pire »
Symptômes : Plus d’alertes, debug plus difficile, plus d’inconnues.
Cause racine : Observabilité et runbooks différés ; alertes basées sur métriques brutes plutôt que sur signaux d’impact client ; absence de tracing.
Correctif : Instrumentez les golden signals (latence, trafic, erreurs, saturation). Ajoutez du tracing. Réécrivez les alertes pour qu’elles soient basées sur les symptômes et liées aux SLO.
« On ne peut pas livrer parce qu’on course la parité pour toujours »
Symptômes : Projet de réécriture qui dure trimestres/années, le business continue d’ajouter des fonctionnalités au legacy, la réécriture n’arrive jamais à rattraper.
Cause racine : Mentalité big-bang ; pas de cutovers incrémentaux ; équipe de réécriture isolée des priorités produit réelles.
Correctif : Pattern strangler avec tranches verticales fines. Déplacez un workflow bout à bout. Geler certaines fonctionnalités legacy ou rediriger les nouvelles fonctionnalités vers le nouveau chemin uniquement.
« On a remplacé le schéma de la base et tout a souffert »
Symptômes : Requêtes lentes, contention de verrous, fenêtres de migration qui s’allongent, rollbacks risqués.
Cause racine : Redesign du schéma ignorant les patterns d’accès et les contraintes opérationnelles ; index manquants ; migrations non bornées.
Correctif : Commencez par le profiling des requêtes et une stratégie d’index. Utilisez des migrations en ligne, des backfills et des contraintes phaseées. Conservez l’ancien schéma comme couche d’adaptation si nécessaire.
« On a réécrit pour améliorer la sécurité et on a introduit de nouvelles failles »
Symptômes : Pistes d’audit manquantes, contrôles d’autorisation plus faibles, prolifération des secrets.
Cause racine : Les contrôles de sécurité étaient implicites dans le legacy et non modélisés ; la nouvelle stack a été livrée sans threat modeling.
Correctif : Inventoriez les invariants de sécurité (règles authz, logs d’audit, rétention). Ajoutez des contrôles automatisés dans le CI. Utilisez le moindre privilège et une gestion centralisée des secrets dès le premier jour.
Checklists / plan pas à pas
Checklist de décision : devriez-vous réécrire du tout ?
- Pouvez-vous nommer le goulot ? Si non, faites de la mesure d’abord (voir tâches et playbook de diagnostic).
- Le problème est-il la maintenabilité du code ou le comportement du système ? Si les incidents sont principalement de capacité/dépendance, réécrire le code applicatif n’aidera pas.
- Existe-t-il un contrat stable ? Si l’interface est instable, verrouillez-la avant de changer l’implémentation interne.
- Y a-t-il un plan de données ? Si vous ne pouvez pas articuler dual-write/CDC, réconciliation et rollback, vous n’êtes pas prêt.
- Avez-vous la maturité ops ? Tableaux de bord, alertes, tracing, runbooks, rollouts en étapes. Si non, construisez cela d’abord.
- Pouvez-vous staffer deux systèmes ? Si non, faites un remplacement incrémental, pas des réécritures parallèles.
Un plan de modernisation plus sûr (fonctionne même avec peu de temps)
- Inventoriez les invariants : idempotence, règles d’exactitude, rétention, authz, codes d’erreur, limites de débit.
- Instrumentez le système legacy s’il est aveugle : ajoutez des request IDs, histogrammes de latence, taxonomies d’erreur.
- Mettez une couche de routage devant : gateway/proxy qui peut splitter le trafic et rollbacker instantanément.
- Choisissez une tranche verticale : un workflow qui apporte une vraie valeur et sollicite des dépendances réelles.
- Shadow d’abord : le nouveau système calcule des réponses et logge les divergences, mais ne les sert pas.
- Canary : 1% trafic, puis 5%, puis 25%, en mesurant SLOs et invariants.
- Basculez les lectures prudemment : les lectures obsolètes sont visibles par l’utilisateur. Utilisez des budgets de cohérence et un comportement clair.
- Basculez les écritures en dernier : assurez-vous que l’idempotence, les retries et la réconciliation sont prouvés.
- Retirez en morceaux : supprimez les endpoints legacy au fur et à mesure qu’ils arrivent à zéro trafic ; conservez des chemins d’accès d’archive pour l’audit.
Checklist de release pour un composant migré
- SLO défini ; tableaux de bord montrent les golden signals.
- Alerting réglé ; on-call a des runbooks et instructions de rollback.
- Capacité testée avec une forme de charge proche de la production.
- Timeouts et retries des dépendances configurés (avec budgets).
- Idempotence implémentée pour les opérations non sûres.
- Jobs de réconciliation des données en place ; processus de triage des divergences défini.
- Contrôles de sécurité validés : parité authz, logs d’audit, rétention.
- Game day réalisé : défaillance de dépendance, BD lente, déploiement partiel, rollback.
FAQ
1) Quand une réécriture est-elle réellement justifiée ?
Quand le système actuel ne peut pas être évolué en toute sécurité : runtime non supporté avec risque de sécurité inpatchable, contraintes de licence ou architecture qui bloque des exigences métier critiques. Même dans ce cas, préférez un remplacement incrémental derrière des interfaces stables.
2) La migration incrémentale n’est-elle pas plus lente que la réécriture ?
L’incrémental paraît plus lent parce qu’il est honnête sur l’opération de deux réalités. Les grosses réécritures paraissent rapides jusqu’à ce que l’intégration, les données et les opérations se pointent — puis le temps explose. La migration incrémentale gagne en livrant de la valeur tôt et en réduisant le risque existentiel.
3) Nous avons un code de qualité épouvantable. Cela n’exige-t-il pas une réécriture ?
La mauvaise qualité demande des frontières, des tests autour des invariants et une visibilité opérationnelle. Souvent vous pouvez isoler les pires modules et les remplacer derrière une interface. Une réécriture complète remet la qualité du code à « inconnue », ce qui n’est pas automatiquement mieux.
4) Comment éviter « deux systèmes pour toujours » ?
En migrant par tranches qui retirent complètement les responsabilités legacy. Ne construisez pas un système parallèle qui duplique tout avant de livrer. Routez le trafic, basculez une tranche, puis supprimez l’ancienne tranche. La suppression est un jalon.
5) Quel est le plus grand risque caché des réécritures ?
Dérive sémantique : le nouveau système se comporte différemment sous retries, pannes partielles et entrées étranges. Les utilisateurs ne déposent pas de tickets pour la « dérive sémantique ». Ils déposent des tickets pour de l’argent manquant, des données erronées et « votre API est instable ».
6) L’architecture microservices exige-t-elle une réécriture ?
Non. Vous pouvez découper un monolithe en services au fil du temps. La première étape est souvent de créer des frontières modulaires internes et d’extraire un domaine avec une propriété claire et des contrats de données.
7) Comment gérer la migration des données sans downtime ?
Utilisez CDC ou dual-write, puis réconciliez. Basculez les lectures quand la staleness est acceptable ou atténuée, basculez les écritures en dernier avec une idempotence forte. Ayez toujours une route de rollback et un plan pour les backfills.
8) Que doit mesurer la direction pour savoir si la migration est saine ?
Pas les story points. Mesurez l’atteinte des SLO, le taux d’incidents, la fréquence des rollbacks, le temps de détection/temps de récupération et le progrès de migration en surface legacy retirée (endpoints/workflows supprimés).
9) Comment empêcher les ingénieurs de « bouillir l’océan » ?
Définissez une tranche verticale fine qui atteint la production, puis exigez que chaque extension inclue un plan de sortie pour le chemin legacy équivalent. Récompensez la suppression et la stabilité opérationnelle, pas la nouveauté.
Prochaines étapes que vous pouvez livrer ce trimestre
Si vous êtes dans une réunion où quelqu’un propose une réécriture comme panacée, voici ce que vous faites à la place — concrètement, sans drame :
- Exécutez le playbook de diagnostic rapide et publiez la classification du goulot. Sortez le débat du registre esthétique.
- Rédigez les invariants (idempotence, exactitude, budgets de latence, règles authz). Rendez-les révisables et testables.
- Choisissez un workflow et migrez-le en utilisant routage + canary + rollback. Prouvez que vous pouvez déplacer des tranches en sécurité.
- Investissez dans la parité opérationnelle : tableaux de bord, tracing, hygiène des alertes, runbooks. Rendez l’exploitation des systèmes plus facile que d’en discuter.
- Faites de la réconciliation des données une fonctionnalité produit, pas une quête secondaire. Si vous ne pouvez pas prouver l’exactitude des données, vous n’avez pas d’exactitude.
Le mythe du « réécrire depuis zéro » survit parce qu’il offre une histoire où la complexité disparaît. Dans les systèmes réels, la complexité ne disparaît pas ; elle se déplace. Votre travail est de la déplacer vers des endroits où elle est mesurable, contrôlable et ennuyeuse. L’ennuyeux est sous-estimé. L’ennuyeux expédie.