Synchronisation incrémentale des données avec points de contrôle : aligner les systèmes en toute sécurité
La synchronisation incrémentale des données avec points de contrôle vous aide à garder les systèmes alignés via des curseurs, des hashs et des jetons de reprise pour reprendre en toute sécurité sans réimporter.

Pourquoi les réimportations complètes posent toujours problème
Les réimportations complètes donnent l'impression d'être sûres parce qu'elles semblent simples : supprimer, recharger, terminé. En pratique, elles sont une des façons les plus faciles de créer des synchronisations lentes, des factures plus élevées et des données désordonnées.
Le premier problème est le temps et le coût. Récupérer l'ensemble du jeu de données à chaque exécution signifie que vous retéléchargez sans cesse les mêmes enregistrements. Si vous synchronisez 500 000 clients chaque nuit, vous payez le calcul, les appels API et les écritures en base même si seuls 200 enregistrements ont changé.
Le deuxième problème est la justesse des données. Les réimportations complètes créent souvent des doublons (les règles de correspondance sont imparfaites), ou elles écrasent des modifications récentes avec des données plus anciennes présentes dans l'export. Beaucoup d'équipes constatent aussi une dérive des totaux dans le temps parce que le “supprimer et recharger” échoue silencieusement en cours de route.
Les symptômes typiques ressemblent à ceci :
- Les totaux ne correspondent pas entre les systèmes après une exécution
- Des enregistrements apparaissent en double avec de petites différences (majuscules dans les e-mails, formatage des téléphones)
- Des champs récemment modifiés reviennent à une valeur antérieure
- La synchronisation « se termine » parfois mais manque un lot de données
- Les tickets de support augmentent après chaque fenêtre d'import
Un point de contrôle est simplement un petit marqueur enregistré qui dit « j'ai traité jusqu'ici ». La fois suivante, vous continuez depuis ce marqueur au lieu de repartir de zéro. Le marqueur peut être un horodatage, un identifiant d'enregistrement, un numéro de version ou un jeton retourné par une API.
Si votre objectif réel est de garder deux systèmes alignés dans le temps, la synchronisation incrémentale des données avec points de contrôle est généralement la meilleure option. Elle est particulièrement utile quand les données changent fréquemment, que les exports sont volumineux, que les API ont des limites de débit, ou que vous devez que la synchronisation puisse reprendre en toute sécurité après un crash (par exemple, quand un job échoue à mi-chemin dans un outil interne que vous avez construit sur une plateforme comme AppMaster).
Définissez l'objectif de la synchronisation avant de choisir une méthode
La synchronisation incrémentale avec points de contrôle ne fonctionne bien que lorsque vous êtes clair sur ce que signifie « correct ». Si vous sautez cette étape et passez directement aux curseurs ou aux hashs, vous finirez souvent par reconstruire la synchronisation plus tard parce que les règles n'avaient jamais été écrites.
Commencez par nommer les systèmes et décider qui détient la vérité. Par exemple, votre CRM peut être la source de vérité pour les noms et numéros de téléphone des clients, tandis que votre outil de facturation est la source de vérité pour le statut des abonnements. Si les deux systèmes peuvent modifier le même champ, vous n'avez pas une seule source de vérité et devez prévoir la gestion des conflits.
Ensuite, définissez ce que signifie « aligné ». Avez-vous besoin d'une correspondance exacte en tout moment, ou est-ce acceptable que les mises à jour apparaissent sous quelques minutes ? La correspondance exacte implique souvent un ordre plus strict, des garanties plus fortes autour des checkpoints et une gestion plus prudente des suppressions. La cohérence éventuelle est généralement moins coûteuse et plus tolérante aux pannes temporaires.
Décidez de la direction de la synchronisation. La synchronisation unidirectionnelle est plus simple : le système A alimente le système B. La synchronisation bidirectionnelle est plus compliquée car chaque mise à jour peut être un conflit, et il faut éviter les boucles infinies où chaque côté « corrige » l'autre.
Questions à répondre avant de construire
Écrivez des règles simples sur lesquelles tout le monde s'accorde :
- Quel système est la source de vérité pour chaque champ (ou chaque type d'objet) ?
- Quel délai est acceptable (secondes, minutes, heures) ?
- Est-ce unidirectionnel ou bidirectionnel, et quels événements circulent dans chaque sens ?
- Comment les suppressions sont-elles gérées (suppression définitive, suppression logique, tombstones) ?
- Que se passe-t-il lorsque les deux côtés modifient le même enregistrement ?
Un jeu de règles de conflit pratique peut être aussi simple que « la facturation l'emporte pour les champs d'abonnement, le CRM l'emporte pour les champs de contact, sinon la mise à jour la plus récente gagne ». Si vous construisez l'intégration dans un outil comme AppMaster, capturez ces règles dans votre logique Business Process afin qu'elles restent visibles et testables, et non pas enfermées dans la mémoire de quelqu'un.
Curseurs, hashs et jetons de reprise : les blocs de base
La synchronisation incrémentale avec points de contrôle s'appuie généralement sur l'un des trois « emplacements » que vous pouvez stocker et réutiliser en toute sécurité. Le bon choix dépend de ce que le système source peut garantir et des pannes que vous devez survivre.
Un checkpoint de type curseur est le plus simple. Vous enregistrez « la dernière chose traitée », comme un dernier ID, un dernier updated_at, ou un numéro de séquence. À la prochaine exécution, vous demandez les enregistrements après ce point. Cela fonctionne bien lorsque la source trie de façon cohérente et que les ID ou les horodatages avancent de manière fiable. Ça casse quand les mises à jour arrivent en retard, que les horloges diffèrent, ou que des enregistrements peuvent être insérés « dans le passé » (par exemple, des données backfillées).
Les hashs vous aident à détecter un changement quand un simple curseur ne suffit pas. Vous pouvez hasher chaque enregistrement (sur la base des champs qui vous importent) et ne synchroniser que lorsque le hash change. Ou vous pouvez hasher un lot entier pour détecter rapidement une dérive puis zoomer sur les éléments. Les hashs par enregistrement sont précis mais ajoutent du stockage et du calcul. Les hashs de lot sont moins coûteux mais peuvent masquer quel item a changé.
Les jetons de reprise sont des valeurs opaques émises par la source, souvent pour la pagination ou les flux d'événements. Vous ne les interprétez pas, vous les stockez et les renvoyez pour continuer. Les jetons sont excellents quand l'API est complexe, mais ils peuvent expirer, devenir invalides après des fenêtres de rétention, ou se comporter différemment selon l'environnement.
Que choisir, et ce qui peut mal tourner
- Curseur : rapide et simple, mais attention aux mises à jour hors ordre.
- Hash par enregistrement : détection précise des changements, mais coût plus élevé.
- Hash de lot : signal de dérive bon marché, mais peu précis.
- Jeton de reprise : pagination la plus sûre, mais peut expirer ou être à usage unique.
- Hybride (curseur + hash) : courant quand
updated_atn'est pas totalement fiable.
Si vous construisez une synchronisation dans un outil comme AppMaster, ces checkpoints résident généralement dans une petite table « sync state », de sorte que chaque exécution puisse reprendre sans deviner.
Concevoir le stockage des checkpoints
Le stockage des checkpoints est la petite pièce qui rend la synchronisation incrémentale fiable. S'il est difficile à lire, facile à écraser ou pas lié à un job spécifique, votre sync aura l'air correct jusqu'à la première défaillance, puis vous serez en train de deviner.
D'abord, choisissez où stocker les checkpoints. Une table de base de données est généralement la plus sûre car elle supporte les transactions, l'audit et des requêtes simples. Un magasin clé-valeur peut fonctionner si vous en utilisez déjà un et qu'il supporte des mises à jour atomiques. Un fichier de config n'est raisonnable que pour des synchronisations mono-utilisateur et à faible risque, car il est difficile à verrouiller et facile à perdre.
Que stocker (et pourquoi)
Un checkpoint est plus qu'un curseur. Sauvegardez assez de contexte pour déboguer, reprendre et détecter une dérive :
- Identité du job : nom du job, tenant ou id de compte, type d'objet (par exemple, customers)
- Progression : valeur du curseur ou jeton de reprise, plus un type de curseur (time, id, token)
- Signaux de santé : dernier run, statut, enregistrements lus et écrits
- Sécurité : dernier curseur réussi (pas seulement le dernier tenté) et un court message d'erreur pour la dernière défaillance
Si vous utilisez des hashs pour la détection de changement, stockez aussi la version de la méthode de hash. Sinon, vous pouvez changer le hash plus tard et traiter accidentellement tout comme « modifié ».
Versionner et gérer de nombreux jobs de sync
Quand votre modèle de données change, versionnez vos checkpoints. L'approche la plus simple est d'ajouter un champ schema_version et de créer de nouvelles lignes pour une nouvelle version, au lieu de muter les anciennes données. Conservez les anciennes lignes un moment pour pouvoir revenir en arrière.
Pour plusieurs jobs de synchronisation, namespacez tout. Une bonne clé est (tenant_id, integration_id, object_name, job_version). Cela évite le bug classique où deux jobs partagent un curseur et sautent silencieusement des données.
Exemple concret : si vous construisez la sync comme un outil interne dans AppMaster, stockez les checkpoints dans PostgreSQL avec une ligne par tenant et par objet, et mettez-la à jour uniquement après un commit de lot réussi.
Pas à pas : implémenter une boucle de synchronisation incrémentale
Une synchronisation incrémentale avec points de contrôle fonctionne mieux quand votre boucle est ennuyeuse et prévisible. L'objectif est simple : lire les changements dans un ordre stable, les écrire en sécurité, puis avancer le checkpoint uniquement quand vous savez que l'écriture est terminée.
Une boucle simple et fiable
D'abord, choisissez un ordre qui ne change jamais pour le même enregistrement. Les horodatages peuvent fonctionner, mais seulement si vous incluez aussi un brise-égalité (comme un ID) pour que deux mises à jour au même instant ne se mélangent pas.
Ensuite, exécutez la boucle ainsi :
- Décidez de votre curseur (par exemple : last_updated + id) et de la taille de page.
- Récupérez la page suivante d'enregistrements plus récents que le checkpoint stocké.
- Faites un upsert de chaque enregistrement dans la cible (créer si absent, mettre à jour si présent) et capturez les échecs.
- Validez les écritures réussies, puis persistez le nouveau checkpoint à partir du dernier enregistrement traité.
- Répétez. Si la page est vide, dormez, puis réessayez.
Gardez la mise à jour du checkpoint séparée de la récupération. Si vous enregistrez le checkpoint trop tôt, un crash peut ignorer des données silencieusement.
Backoff et retries sans doublons
Supposez que des appels vont échouer. Lorsqu'une récupération ou une écriture échoue, réessayez avec un court backoff (par exemple : 1s, 2s, 5s) et un nombre maximal de retries. Rendez les retries sûrs en utilisant des upserts et en rendant vos écritures idempotentes (même entrée, même résultat).
Un petit exemple pratique : si vous synchronisez les mises à jour clients chaque minute, vous pouvez récupérer 200 changements à la fois, les upserter, puis ne stocker que le (updated_at, id) du dernier client comme nouveau curseur.
Si vous construisez cela dans AppMaster, vous pouvez modéliser le checkpoint dans une table simple (Data Designer) et exécuter la boucle dans un Business Process qui récupère, upserte et met à jour le checkpoint dans un flux contrôlé.
Rendre les reprises sûres : idempotence et checkpoints atomiques
Si votre sync peut reprendre, elle reprendra au pire moment possible : après un timeout, un crash ou un déploiement partiel. L'objectif est simple : relancer le même lot ne doit pas créer de doublons ni perdre des mises à jour.
L'idempotence est le filet de sécurité. Vous l'obtenez en écrivant d'une manière qui peut être répétée sans changer le résultat final. En pratique, cela signifie généralement des upserts, pas des inserts : écrivez l'enregistrement avec une clé stable (comme customer_id) et mettez à jour les lignes existantes quand elles existent.
Une bonne « clé d'écriture » est quelque chose de fiable entre les retries. Options courantes : un ID naturel du système source, ou une clé synthétique que vous stockez la première fois que vous voyez l'enregistrement. Soutenez-la par une contrainte d'unicité pour que la base de données impose la règle même quand deux workers sont en concurrence.
Les checkpoints atomiques ont autant d'importance. Si vous avancez le checkpoint avant que les données ne soient commit, un crash peut vous faire sauter des enregistrements pour toujours. Traitez la mise à jour du checkpoint comme faisant partie de la même unité de travail que vos écritures.
Voici un schéma simple pour une synchronisation incrémentale avec points de contrôle :
- Lisez les changements depuis le dernier checkpoint (curseur ou jeton).
- Upsertez chaque enregistrement en utilisant une clé de déduplication.
- Commitez la transaction.
- Ensuite seulement, persistez le nouveau checkpoint.
Les mises à jour hors ordre et les données arrivant en retard sont l'autre piège courant. Un enregistrement peut être modifié à 10:01 mais arriver après un enregistrement de 10:02, ou une API peut livrer des changements plus anciens lors d'un retry. Protégez-vous en stockant un last_modified source et en appliquant une règle « dernier écrit gagne » : n'écrasez que lorsque l'enregistrement entrant est plus récent que ce que vous avez déjà.
Si vous avez besoin d'une protection plus forte, conservez une fenêtre de recouvrement petite (par exemple, relire les dernières minutes) et comptez sur des upserts idempotents pour ignorer les répétitions. Cela ajoute un peu de travail, mais rend les reprises ennuyeuses, ce qui est exactement ce que vous voulez.
Dans AppMaster, la même idée s'applique proprement à un Business Process : faites d'abord la logique d'upsert, commit, puis stockez le curseur ou le jeton de reprise comme étape finale.
Erreurs fréquentes qui cassent la synchronisation incrémentale
La plupart des bugs de sync ne sont pas du code. Ils viennent de quelques hypothèses qui semblent sûres jusqu'à ce que des données réelles apparaissent. Si vous voulez que la synchronisation incrémentale avec points de contrôle reste fiable, surveillez ces pièges tôt.
Points de défaillance habituels
Une erreur courante est de faire trop confiance à updated_at. Certains systèmes réécrivent les horodatages lors de backfills, corrections de fuseau horaire, modifications groupées, ou même read-repairs. Si votre curseur est seulement un horodatage, vous pouvez manquer des enregistrements (horodatage qui recule) ou retraiter de larges plages (horodatage qui saute en avant).
Un autre piège est de supposer que les IDs sont continus ou strictement croissants. Les imports, le sharding, les UUID et les lignes supprimées brisent cette idée. Si vous utilisez « dernier ID vu » comme checkpoint, les écarts et écritures hors ordre peuvent laisser des enregistrements de côté.
Le bug le plus dommageable est d'avancer le checkpoint sur un succès partiel. Par exemple, vous récupérez 1 000 enregistrements, en écrivez 700, puis crash, mais enregistrez quand même le « curseur suivant » issu de la récupération. Au redémarrage, les 300 restants ne seront jamais retentés.
Les suppressions sont aussi faciles à ignorer. Une source peut faire une suppression logique (flag), une suppression définitive (ligne supprimée) ou un simple changement d'état. Si vous ne faites qu'upserter les enregistrements actifs, la cible dérive lentement.
Enfin, les changements de schéma peuvent invalider d'anciens hashs. Si vos hashs de détection étaient construits à partir d'un ensemble de champs, ajouter ou renommer un champ peut transformer un « pas de changement » en « changé » (ou l'inverse) à moins de versionner votre logique de hash.
Voici des valeurs par défaut plus sûres :
- Préférez un curseur monotone (ID d'événement, position de log) aux horodatages bruts quand c'est possible.
- Traitez les écritures de checkpoint comme faisant partie de la même frontière de succès que vos écritures de données.
- Suivez explicitement les suppressions (tombstones, transitions de statut, ou réconciliation périodique).
- Versionnez les inputs des hashs et gardez les anciennes versions lisibles.
- Ajoutez une petite fenêtre de recouvrement (relire les N derniers items) si la source peut réordonner les mises à jour.
Si vous construisez ceci dans AppMaster, modélisez le checkpoint comme sa propre table dans le Data Designer et gardez l'étape « écrire les données + écrire le checkpoint » dans une seule exécution Business Process, afin que les retries ne sautent pas de travail.
Surveillance et détection de dérive sans être bruyant
Une bonne surveillance pour la synchronisation incrémentale avec points de contrôle concerne moins « plus de logs » et plus quelques chiffres fiables à chaque exécution. Si vous pouvez répondre à « qu'avons-nous traité, combien de temps cela a pris, et où allons-nous reprendre ? », vous pouvez déboguer la plupart des problèmes en quelques minutes.
Commencez par écrire un enregistrement de run compact à chaque exécution. Gardez-le consistant pour pouvoir comparer les runs et repérer les tendances.
- Curseur de départ (ou jeton) et curseur de fin
- Enregistrements récupérés, écrits, ignorés
- Durée du run et temps moyen par enregistrement (ou par page)
- Nombre d'erreurs avec la raison principale
- Statut d'écriture du checkpoint (succès/échec)
La détection de dérive est la couche suivante : elle vous dit quand les systèmes « fonctionnent tous les deux » mais divergent lentement. Les totaux seuls peuvent être trompeurs, combinez donc un contrôle léger des totaux avec de petits contrôles ponctuels. Par exemple, une fois par jour comparez le total de clients actifs dans les deux systèmes, puis échantillonnez 20 IDs clients aléatoires et confirmez quelques champs (status, updated_at, email). Si les totaux diffèrent mais que les échantillons correspondent, vous pourriez manquer des suppressions ou des filtres. Si les échantillons diffèrent, vos hashs de détection ou le mapping des champs est probablement incorrect.
Les alertes doivent être rares et actionnables. Une règle simple : n'alertez que lorsqu'un humain doit agir maintenant.
- Curseur bloqué (le curseur de fin n'avance pas pendant N runs)
- Taux d'erreur en hausse (par ex. 1% -> 5% sur une heure)
- Runs plus lents (durée au-dessus du seuil normal)
- Backlog qui grossit (les nouveaux changements arrivent plus vite que vous ne les sync)
- Dérive confirmée (totaux mismatch deux fois de suite)
Après une panne, relancez sans nettoyage manuel en rejouant en toute sécurité. L'approche la plus simple est de reprendre depuis le dernier checkpoint commité, pas depuis le dernier enregistrement « vu ». Si vous utilisez une petite fenêtre de recouvrement (relire la dernière page), rendez les écritures idempotentes : upsert par ID stable et n'avancez le checkpoint qu'après réussite de l'écriture. Dans AppMaster, les équipes implémentent souvent ces contrôles dans un Business Process et envoient des alertes par email/SMS ou Telegram pour que les échecs soient visibles sans surveiller constamment un dashboard.
Checklist rapide avant mise en production
Avant d'activer une synchronisation incrémentale avec points de contrôle en production, faites un rapide contrôle des quelques détails qui causent généralement des surprises tardives. Ces vérifications prennent quelques minutes mais évitent des jours de debugging « pourquoi avons-nous manqué des enregistrements ? ».
Voici une checklist pratique avant déploiement :
- Assurez-vous que le champ utilisé pour l'ordre (timestamp, séquence, ID) est vraiment stable et indexé côté source. S'il peut changer après coup, votre curseur va dériver.
- Confirmez que votre clé d'upsert est garantie unique, et que les deux systèmes la traitent de la même façon (sensibilité à la casse, trimming, format). Si un système stocke "ABC" et l'autre "abc", vous aurez des doublons.
- Stockez les checkpoints séparément pour chaque job et chaque jeu de données. Un « curseur global » peut sembler simple mais casse dès que vous synchronisez deux tables, deux tenants ou deux filtres.
- Si la source est finalement consistante, ajoutez une petite fenêtre de recouvrement. Par exemple, en reprenant depuis
last_updated = 10:00:00, redémarrez depuis 09:59:30 et comptez sur les upserts idempotents pour ignorer les répétitions. - Prévoyez une réconciliation légère : sur un calendrier, prenez un échantillon (par ex. 100 enregistrements aléatoires) et comparez les champs clés pour détecter une dérive silencieuse.
Test de réalité rapide : mettez la sync en pause au milieu d'une exécution, redémarrez-la, et vérifiez que vous obtenez les mêmes résultats. Si redémarrer change des totaux ou crée des lignes supplémentaires, corrigez avant le lancement.
Si vous construisez la sync dans un outil comme AppMaster, gardez les données de checkpoint de chaque flux d'intégration liées au process et au dataset spécifiques, et non partagées entre automatisations non liées.
Exemple : synchroniser les enregistrements clients entre deux apps
Imaginez un scénario simple : votre CRM est la source de vérité pour les contacts, et vous voulez que les mêmes personnes existent dans un outil de support (pour que les tickets correspondent à des clients réels) ou dans un portail client (pour que les utilisateurs puissent se connecter et voir leur compte).
Lors du premier run, faites un import initial. Récupérez les contacts dans un ordre stable, par exemple par updated_at plus id comme tiebreaker. Après avoir écrit chaque lot dans la destination, enregistrez un checkpoint comme : last_updated_at et last_id. Ce checkpoint est votre point de départ pour toutes les exécutions futures.
Pour les runs suivants, ne récupérez que les enregistrements plus récents que le checkpoint. Les mises à jour sont simples : si le contact CRM existe déjà, mettez à jour la destination ; sinon, créez-le. Les fusions sont la partie délicate. Les CRM fusionnent souvent des doublons et gardent un contact « gagnant ». Traitez cela comme une mise à jour qui « retire » aussi le contact perdant en le marquant inactif (ou en le mappant au gagnant) pour ne pas finir avec deux utilisateurs de portail pour la même personne.
Les suppressions apparaissent rarement dans les requêtes « updated since », donc prévoyez-les. Options courantes : un flag de suppression dans la source, un flux séparé « contacts supprimés », ou une réconciliation périodique légère qui vérifie les IDs manquants.
Cas de panne : la sync plante à mi-chemin. Si vous ne stockez un checkpoint qu'à la fin, vous retraiterez une grosse portion. Au lieu de cela, utilisez un jeton de reprise par lot.
- Démarrez un run et générez un
run_id(votre jeton de reprise) - Traitez un lot, écrivez les changements dans la destination, puis enregistrez atomiquement le checkpoint lié à
run_id - Au redémarrage, détectez le dernier checkpoint sauvegardé pour ce
run_idet continuez
Le succès ressemble à de l'ennui : les totaux restent stables jour après jour, les temps d'exécution sont prévisibles, et relancer la même fenêtre ne produit aucune modification inattendue.
Prochaines étapes : choisissez un pattern et construisez sans tout refaire
Une fois votre première boucle incrémentale en place, le moyen le plus rapide d'éviter de retravailler est d'écrire les règles de la synchronisation. Restez concis : quels enregistrements sont en scope, quels champs gagnent en cas de conflit, et à quoi ressemble « terminé » après chaque run.
Commencez petit. Choisissez un dataset (par ex. customers) et exécutez-le de bout en bout : import initial, mises à jour incrémentales, suppressions et reprise après une panne volontaire. Il est plus facile de corriger les hypothèses maintenant que quand vous avez ajouté cinq tables de plus.
Une reconstruction complète reste parfois la bonne décision. Faites-la quand l'état des checkpoints est corrompu, quand vous changez les identifiants, ou quand un changement de schéma casse votre détection de changement (par ex. vous utilisiez un hash et la signification des champs a changé). Si vous reconstruisez, traitez-le comme une opération contrôlée, pas comme un bouton d'urgence.
Voici une façon sûre de réimporter sans downtime :
- Importez dans une table shadow ou un dataset parallèle, en laissant l'actuel en production.
- Validez les totaux et spot-checkez des échantillons, y compris les cas limites (nulls, enregistrements fusionnés).
- Backfillez les relations, puis basculez les lecteurs vers le nouveau dataset en une coupure planifiée.
- Conservez l'ancien dataset pendant une courte fenêtre de rollback, puis nettoyez.
Si vous voulez construire cela sans écrire de code, AppMaster peut vous aider à garder les pièces au même endroit : modélisez les données dans PostgreSQL avec le Data Designer, définissez les règles de sync dans le Business Process Editor, et exécutez des jobs planifiés qui récupèrent, transforment et upsertent les enregistrements. Parce qu'AppMaster régénère du code propre lorsque les besoins changent, ajouter « encore un champ » devient moins risqué.
Avant d'étendre à d'autres datasets, documentez votre contrat de synchronisation, choisissez un pattern (curseur, jeton de reprise ou hash) et fiabilisez une synchronisation. Ensuite, répétez la même structure pour le dataset suivant. Si vous voulez tester rapidement, créez une application dans AppMaster et lancez un petit job de sync planifié.


