Architecture systèmeArchitecte Système

Comment architecturer une synchronisation de données sans temps d'arrêt entre une base de données monolithique héritée et un écosystème de microservices distribués tout en maintenant les propriétés ACID lors d'une migration progressive ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse à la question

Historique de la question

L'évolution des architectures monolithiques vers les microservices a créé un besoin critique de stratégies de migration incrémentales. Les organisations ne peuvent pas se permettre le luxe d'une migration totale à l'arrêt, en particulier celles qui fonctionnent à grande échelle avec des systèmes hérités Oracle ou SQL Server. Cette question a émergé de scénarios réels où les entreprises devaient se moderniser sans sacrifier l'intégrité des données historiques ou accepter des fenêtres de maintenance qui durent des heures.

Le problème

Le principal défi réside dans le déséquilibre entre les transactions ACID monolithiques s'étendant sur plusieurs domaines et la nature distribuée des microservices. Lors de la décomposition d'une base de données, vous êtes confronté au scénario de split-brain où des mises à jour se produisent à la fois dans le système hérité et dans les nouveaux services simultanément. Maintenir l'intégrité référentielle à travers les frontières réseau tout en gardant les deux systèmes opérationnels crée un problème de consensus distribué qui ne peut pas être résolu par une simple réplication de base de données.

La solution

Mettre en œuvre une Architecture Orientée Événements utilisant le Change Data Capture (CDC) avec un Modèle Outbox pour garantir une publication d'événements fiable. Déployer des connecteurs Debezium pour capturer les changements au niveau des lignes du journal des transactions de la base de données héritée, diffusant les événements vers Apache Kafka comme le système nerveux central. En parallèle, mettre en œuvre le Modèle Saga dans la couche de microservices pour gérer les transactions distribuées, assurant une cohérence éventuelle tout en maintenant l'autonomie opérationnelle de chaque service.

Situation de la vie réelle

Une plateforme de commerce électronique du Fortune 500 devait migrer son système de gestion des commandes d'un monolithe Oracle vieux de dix ans vers des microservices basés sur PostgreSQL. Les modules d'inventaire, de tarification et d'exécution des commandes étaient étroitement couplés avec des contraintes de clé étrangère à travers douze tables principales. Pendant les saisons de vacances, le système traitait 50 000 transactions par minute avec une tolérance nulle pour la perte de données ou les temps d'arrêt.

Solution A : Stratégie d'écriture duale

L'équipe d'ingénierie a d'abord envisagé de modifier le code de l'application hérité pour écrire simultanément dans à la fois Oracle et les nouveaux services PostgreSQL. Cette approche promettait une simplicité en maintenant les écritures synchrones et cohérentes. Cependant, elle introduisait des risques de couplage catastrophiques – si le nouveau service rencontrait des délais ou des échecs, l'ensemble du système hérité s'effondrerait. De plus, la mise en œuvre de transactions distribuées via le protocole XA dégraderait sévèrement les performances, augmentant potentiellement les temps de réponse de 400 % pendant les périodes de forte charge.

Solution B : Déclencheurs et Vues de Base de Données

Une autre option consistait à créer des déclencheurs de base de données dans Oracle qui invoqueraient directement les points de terminaison REST lors des modifications de lignes. Cela semblait attractif car cela n'exigeait aucun changement d'application. Pourtant, cela créait un couplage étroit entre l'infrastructure de base de données et la topologie réseau, rendant le système fragile. Si le point de terminaison du microservice était inaccessible, le déclencheur échouerait, provoquant un rollback de l'ensemble de la transaction héritée – une violation de l'exigence de zéro temps d'arrêt. De plus, la gestion des migrations de schéma devenait presque impossible lorsque les déclencheurs dépendaient de structures de colonnes spécifiques.

Solution C : Change Data Capture avec Sourcing d'Événements

L'architecture choisie exploitait Debezium pour surveiller le journal de ré-exécution d'Oracle, capturant chaque insertion, mise à jour et suppression en tant qu'événements immuables publiés dans Apache Kafka. Les microservices consommaient ces événements via Kafka Streams, les transformant et les conservant dans PostgreSQL en utilisant le Modèle Outbox pour garantir des sémantiques exactes une seule fois. Un Registre de Schéma géré par Confluent a enforce l'interopérabilité en arrière et en avant à l'aide de schémas Avro. Cela a découplé le système hérité de la complexité de la migration – Oracle est resté indifférent à la nouvelle architecture pendant que les services consommaient les événements à leur propre rythme.

Solution choisie et justification

L'équipe a sélectionné la Solution C car elle respeçtait le Principe de Responsabilité Unique et offrait une isolation des fautes. Contrairement aux écritures duales, la performance du système hérité n'était pas affectée par la latence des microservices. Par rapport aux déclencheurs, Debezium fonctionnait de manière asynchrone sans bloquer les transactions. Le journal des événements fournissait une trace d'audit immuable, et les politiques de rétention de Kafka permettaient de rejouer les données historiques si les microservices avaient besoin d'une reprocessement lors de l'évolution du schéma.

Résultat

Après une migration de huit mois, la plateforme a réussi à déplacer 200 To de données transactionnelles avec 99,97 % de temps de disponibilité. Le système a géré le trafic du Black Friday avec une latence inférieure de 40 % à celle de l'année précédente. Lorsqu'un bug de calcul de prix a été découvert dans les nouveaux services, l'équipe a rejoué trois jours d'événements depuis Kafka sans toucher au système hérité Oracle, corrigeant 2,3 millions d'enregistrements sans temps d'arrêt. Le pipeline CDC sert désormais de colonne vertébrale pour l'analyse en temps réel utilisant Apache Flink.

Ce que les candidats oublient souvent

Comment gérez-vous l'évolution des schémas lorsque le monolithe change sa structure de table pendant que les microservices consomment des événements CDC ?

Les candidats suggèrent souvent de geler le schéma pendant la migration, ce qui est impraticable pour les entreprises agiles. L'approche correcte consiste à mettre en œuvre le Registre de Schéma Confluent avec des schémas Avro en utilisant des modes de compatibilité avant et arrière. Lorsque les tables Oracle changent, le connecteur Debezium publie des événements avec les schémas mis à jour, mais le registre impose des règles de compatibilité. Les services doivent mettre en œuvre le modèle Schema-on-Read en utilisant les règles de résolution d'Apache Avro – en ignorant les champs inconnus et en utilisant des valeurs par défaut pour les manquants. De plus, déployez un modèle CQRS où les modèles de lecture peuvent évoluer indépendamment du schéma source, en utilisant des transformateurs Kafka Connect pour aplatir les structures imbriquées avant qu'elles atteignent les points de terminaison de consommation.

Que se passe-t-il lorsque les deux systèmes mettent à jour la même entité simultanément pendant la période de transition ?

Cela crée un scénario de split-brain qui ne peut être résolu par de simples horodatages. Les architectes doivent mettre en œuvre des Horloges Vectorielles ou des CRDTs (Types de Données Répliqués sans Conflit) pour une résolution de conflits déterministe. Déployez un composant de Synchronisation Bi-Directionnelle qui consomme des événements de microservices et écrit à nouveau dans Oracle en utilisant Kafka Connect JDBC Sink, mais avec de strictes sémantiques Last-Write-Wins (LWW) basées sur des horloges logiques hybrides.

Plus important encore, mettez en œuvre des frontières de Conception Orientée Domaine - pendant la migration, attribuez la seule propriété d'écriture à soit le monolithe, soit le microservice par racine agrégée, jamais les deux. Utilisez des Drapeaux de Base de Données dans Oracle pour indiquer l'état de migration, dirigeant le trafic d'écriture en conséquence via une Passerelle API utilisant le Modèle Fig Strangler.

Décrivez le modèle pour assurer l'intégrité transactionnelle lorsqu'une opération commerciale s'étend à la fois à la base de données héritée et aux nouveaux microservices.

La plupart des candidats suggèrent incorrectement des transactions distribuées utilisant le Commit à Deux Phases (2PC) à travers des systèmes hétérogènes, ce qui crée un couplage fragile et des problèmes de disponibilité. La solution appropriée emploie le Modèle Saga avec des Transactions Compensatoires. Lorsque l'action d'un utilisateur nécessite des mises à jour à la fois sur Oracle (hérité) et PostgreSQL (nouveau), orchestrez cela via un Orchestrateur de Saga construit sur Camunda ou Temporal. Le processus exécute des transactions locales de manière séquentielle : d'abord mettre à jour Oracle, puis publier un événement de domaine, puis exécuter l'opération du microservice. Si une étape échoue, exécutez des transactions compensatoires – si l'engagement du microservice échoue, déclenchez un événement de rollback que le système hérité consomme pour revenir sur le changement d'Oracle. Cela maintient une cohérence éventuelle sans verrouiller les ressources à travers les frontières réseau.