Health Sync — Synchronisation des données des établissements vers le centre
Health Sync est le pipeline d’ingestion de la couche source pour les données DME des établissements du HIC : il transfère les enregistrements des bases de données des dossiers médicaux électroniques des établissements (MySQL basé sur OpenMRS) vers le magasin PostgreSQL central en quasi temps réel, d’où ils alimentent l’environnement analytique du HIC (couches lakehouse, tableaux de bord, systèmes de surveillance).
Cette page documente ce qu’est le système, son fonctionnement et les décisions de conception qui le sous-tendent. La procédure d’intégration d’un site, étape par étape, se trouve dans le runbook de déploiement.
Pourquoi ce système existe
Les établissements de santé du Rwanda exploitent des systèmes DME locaux isolés à l’intérieur de chaque établissement. La fonction nationale d’intelligence sanitaire a besoin de ces données consolidées au niveau central, en quasi temps réel, pour l’analyse, les tableaux de bord et la surveillance des épidémies — le cas d’usage déterminant a été la riposte à une épidémie d’Ebola, où la détection précoce des cas exige de voir les données de consultation des établissements en quelques minutes, et non dans le rapport de fin de mois.
Trois réalités ont façonné la conception :
- La connectivité des établissements n’est pas fiable. Les liaisons tombent, le courant est coupé, et un système de synchronisation ne doit perdre aucune donnée pendant les pannes.
- Personne ne peut se déplacer sur un site pour déployer un correctif. Les établissements sont à des heures de route ; le logiciel doit se mettre à jour tout seul.
- Les serveurs des établissements sont fragiles et ne nous appartiennent pas. Ils font tourner des systèmes cliniques de production sur du matériel modeste. Tout ce qui est installé doit être minimal, isolé, réversible et manifestement inoffensif.
En termes de médaillon, Health Sync remplit l’amont du pipeline — des données brutes au niveau établissement, fiables et continues, arrivant au centre :
DME établissement (MySQL) ──▶ Health Sync ──▶ PostgreSQL central ──▶ Analytique HIC
(lakehouse, Superset,
tableaux de surveillance)Vue d’ensemble du système
Health Sync se compose de deux petits services Node.js indépendants :
| Composant | S’exécute | Rôle |
|---|---|---|
| Runner | Dans chaque établissement de santé (conteneur Docker) | Lit les lignes modifiées dans le MySQL du DME local, les met en file d’attente durable sur le disque local, les pousse vers l’API centrale. Se met à jour automatiquement depuis le dépôt de code. |
| API centrale | Sur le serveur central (Docker, plusieurs réplicas derrière un répartiteur de charge) | Reçoit les lots, les écrit dans PostgreSQL (un schéma par établissement), suit les battements de cœur, les journaux et les métriques d’ingestion. |
MySQL (établissement) → runner → file SQLite locale → HTTPS → API centrale → PostgreSQL
│ │
└── interroge GitHub, s'auto-met à jour └── schéma par site,
battements de cœur,
journaux, métriquesLe runner : trois boucles isolées
- Boucle de synchronisation — toutes les 60 s (configurable) : pour chaque table
configurée, lit les lignes plus récentes qu’un watermark par table
(
WHERE timestamp_col > watermark ORDER BY timestamp_col ASC LIMIT 500), les met en file dans une base SQLite locale durable, puis vide la file vers l’API centrale avec backoff exponentiel. Si le réseau est coupé, les lignes s’accumulent simplement en local et s’écoulent au retour de la connectivité. - Boucle de mise à jour — interroge l’API des commits GitHub ; quand un nouveau commit arrive sur la branche principale, elle récupère le code et se termine proprement. La politique de redémarrage de Docker la relance sur la nouvelle version. Personne ne se déplace dans un établissement pour déployer un correctif.
- Boucle de battement de cœur — toutes les 30 s, remonte la profondeur de la file et l’heure de la dernière synchronisation au centre, alimentant le tableau de bord d’état en ligne / obsolète / hors ligne.
Tout l’état du runner — file, watermarks, version du code déployée — vit dans un seul fichier SQLite qui survit aux plantages, redémarrages et mises à jour.
L’API centrale
POST /sync/batch— upsert de lot authentifié (INSERT … ON CONFLICT DO UPDATE). Crée automatiquement le schéma et les tables de l’établissement à la première synchronisation à partir des types de colonnes déclarés en configuration ; marque chaque ligne d’un horodatage_synced_at.POST /heartbeat,POST /log,GET /status— supervision de la flotte.GET /metrics+ un tableau de bord HTML autonome — histogrammes horaires d’ingestion par site et par table, pour que les opérateurs voient un site à l’arrêt comme un trou visuel plutôt qu’en lisant des horodatages bruts.
Organisation PostgreSQL : les tables opérationnelles partagées (jetons de site, battements
de cœur, journaux, métriques) dans un schéma ; les données cliniques de chaque
établissement dans son propre schéma (p. ex. gisenyi.obs), offrant une isolation
naturelle et un contrôle d’accès par site.
Les décisions de conception et leur pourquoi
Ces décisions ont été prises délibérément, et plusieurs ont été validées par des incidents de production.
Durabilité : accepter les doublons, interdire les trous
| Décision | Justification |
|---|---|
| File durable locale entre la lecture de la base et l’envoi à l’API | Les lignes sont en sécurité sur le disque de l’établissement dès leur lecture. Les pannes, quelle que soit leur durée, ne coûtent rien. |
| Le watermark avance atomiquement avec la mise en file, avant l’appel réseau, et n’est jamais annulé | Les lignes sont déjà en sécurité localement ; annuler en cas d’échec de l’API relirait des lignes toujours en file → croissance sans limite. |
| Livraison « au moins une fois » + upserts idempotents plutôt qu’« exactement une fois » | La livraison en double est attendue et inoffensive (le centre fait des upserts). La justesse vient de l’idempotence, pas de la lutte contre les doublons. Renvoyer est gratuit ; un trou perd des données. |
| Watermarks qui se chevauchent volontairement | Quand l’historique est préchargé depuis un dump, le watermark de départ du runner est placé avant la date de coupure du dump. Le chevauchement est ré-upserté sans dommage ; un trou serait une perte de données silencieuse. |
Cette philosophie a été éprouvée en conditions réelles : un bug de file a un jour bloqué ~4 000 lignes sur un site pendant plusieurs jours. Quand le correctif est arrivé (via l’auto-mise à jour elle-même), la file bloquée s’est vidée complètement — aucune donnée perdue, aucun nettoyage manuel nécessaire.
Sécurité : l’identité vient de la cryptographie, pas de la configuration
| Décision | Justification |
|---|---|
| L’identité du site provient du jeton d’API, jamais du corps de la requête | Un runner mal configuré ne peut pas écrire dans le schéma d’un autre établissement. |
| Jetons stockés uniquement sous forme de hachés HMAC, comparés en temps constant, révocables par site | Une fuite de la base ne divulgue aucun jeton utilisable. |
| Validation stricte par liste blanche de chaque nom de table/colonne avant d’atteindre le SQL | Les identifiants issus de la configuration sont un vecteur d’injection ; les listes blanches battent l’assainissement. |
| Utilisateurs MySQL en lecture seule dans les établissements ; utilisateurs PostgreSQL à moindre privilège au centre | Le système de synchronisation ne peut physiquement pas endommager les données cliniques. |
Résilience : pas d’échec silencieux
- Chaque tâche périodique est encapsulée pour que toute erreur soit toujours journalisée — jamais avalée.
- Les commandes shell s’exécutent avec des délais explicites ; une commande bloquée ne peut pas figer les boucles.
- Les trois boucles du runner sont totalement isolées : une défaillance de la mise à jour ne peut jamais arrêter la synchronisation, et inversement.
- Arrêt propre : à l’arrêt, le runner termine le travail en cours, ferme son état proprement et sort dans la fenêtre de grâce de Docker.
- Les écritures de supervision sont « fire-and-forget » : un échec de métriques ne peut jamais affecter une réponse de synchronisation. Le pipeline de données est sacré ; tout le reste est au mieux.
La simplicité comme stratégie opérationnelle
- Auto-mise à jour =
git pull+ sortie propre + politique de redémarrage du conteneur. Aucune plateforme d’orchestration nécessaire dans les établissements. - Le tableau de bord d’état est une seule page HTML autonome — pas d’étape de build, rien de plus à exploiter.
- Les types de colonnes sont déclarés en configuration, pas déduits automatiquement — le déterminisme bat l’ingéniosité.
- Des non-objectifs explicites ont permis de livrer le MVP : pas de propagation des suppressions, pas de moteur de migration de schéma, pas d’interface de gestion des jetons.
État actuel en un coup d’œil
| Dimension | État (juillet 2026) |
|---|---|
| Établissements en production | 9 (hôpitaux de district, provinciaux et de référence) |
| Tables synchronisées par site | 18 (cœur clinique + module de facturation) |
| Lignes historiques préchargées | Des dizaines de millions par site (plus grande table : 27,5 M de lignes) |
| Latence de synchronisation | Quelques minutes (cycle de 60 s + vidage de la file) |
| Incidents de perte de données | 0 (la conception de la file a tenu à travers chaque panne et chaque bug) |
| Mises à jour de la flotte | Entièrement à distance via l’auto-mise à jour ; zéro déplacement sur site pour les correctifs |
| Déploiement central | API dockerisée (répliquée, répartie en charge) + PostgreSQL, sur infrastructure gouvernementale partagée |
| Supervision | État des battements de cœur (en ligne / obsolète / hors ligne) + histogrammes horaires d’ingestion par site/table |
| Suite de tests | 42 tests unitaires + 9 tests bout en bout (avec infrastructure de mise à jour simulée) |
| Outillage | Installateur de site en une commande · script de préchargement reprenable avec mode incrémental · console de débogage sur chaque runner |
Sites en production : Gisenyi DH, Mugonero DH, Byumba L2TH, Kibuye RH, Bushenge PH, Murunda DH, Mibilizi DH, Kibogora DH et Nemba DH.
Limites connues et feuille de route
- Les suppressions ne sont pas propagées (par conception) ; les suppressions définitives à la source restent à la source.
- Les migrations de schéma sont manuelles — l’API crée les tables mais ne les modifie pas.
- Les tables à horodatage de granularité journalière nécessitent des rattrapages périodiques du watermark (suivis et scriptés).
- Prévu : compression HTTP pour les liaisons à faible bande passante ; une machine intermédiaire entre les réseaux des établissements et le réseau central ; des connecteurs au-delà de MySQL 8 (l’interface connecteur est enfichable — un fichier par nouveau type de base de données).
Pages liées
- Runbook de déploiement — la procédure exacte d’intégration d’un site, commande par commande.
- Sources de données — les intégrations des systèmes nationaux via Prefect que Health Sync complète.
- Health Sync dans l’échange d’apprentissage — la méthodologie, les leçons apprises et les questions d’adaptation pays telles que présentées aux délégués de l’échange.