ADR-0028 — Quand acheter (domaine / serveur) et migration de données sans perte¶
Date : 2026-07-29 · Amendée : 2026-07-30 · 2026-07-31 (allègement : SG/TRACIUM/JARAAF sous dynors.com)
Catégorie : A (cadre ops / infra transverse — opposable avant dépense et avant cutover)
Décideur : Lead architecte DYNORS
Statut : Acceptée
S'appuie sur : évaluation portefeuille, ADR continuité/DR, ADR-0016/0017/0018, ADR-0027, DYNORS_URL_CONVENTION_RULES.md v1.2
Complète : la question « acheter dawalale.sn / serveur dédié / staging+prod mutualisés »
Contexte¶
Sans règles explicites, on risque :
- Dépense trop tôt — VPS dédié par idée produit, domaine marque pour un staging, serveur « à part » non branché SLY ;
- Dépense trop tard — Medisen/PAIEMENT en prod mutualisée trop longtemps, blast radius santé/argent ;
- Perte de données au déménagement — dump partiel, DNS basculé avant restore vérifié, secrets/Vault non migrés, médias R2 oubliés, cutover sans fenêtre ni rollback.
Le portefeuille long terme impose 2 serveurs d’abord (staging + prod mutualisés), puis extraction ciblée. Cet ADR fige quand acheter / quand ne pas acheter, et comment migrer pour zéro perte acceptable au regard des RPO.
Décision 1 — Matrice « acheter / ne pas acheter »¶
1.1 Domaines de marque (DNS)¶
| Acheter OUI quand… | Acheter NON (encore) quand… |
|---|---|
Produit grand public / santé : dawalale.sn ✅, medisen.sn, éventuellement elisa.sn |
SuperGest / TRACIUM / JARAAF → pas de .sn ni mail dédié (*.dynors.com, @dynors.com) ; backbone ; SeckShop / YOBALÉ retirés |
Emails pro uniquement pour les .sn (DAWALALE / Medisen) + landing Pages |
Qualif hors-prod → SLY (env,lane) ; SaaS B2B → mails @dynors.com seulement |
| Renouvellement d’un domaine déjà en doctrine URL | Dual-deploy yobale.sn + ELISA |
Règle : domaine ≠ serveur. Acheter le .sn n’implique pas un VPS dédié.
Liste CTO domaines propres (v1.2 — 2026-07-31) : claude-handoff-dynors/DYNORS_URL_CONVENTION_RULES.md § Exceptions.
INT / RMOA / DEV : jamais sur le domaine de marque prod. DNS marque → prod uniquement (ou parking/landing en attendant le cutover).
1.2 Serveurs (compute)¶
| Acheter / provisionner OUI | NON |
|---|---|
1 VPS staging (env int/rmoa) dès qu’on sort du full-local |
Un VPS par app en Phase 1 |
| 1 VPS prod distinct dès la 1re go-live réelle (isolation blast radius — cahier §8) | « Petit serveur prod » colocalisé avec staging |
| Dédié app si ≥ 1 critère d’extraction (§1.3) est vrai et durable | Dédié « au cas où » ou parce que le domaine est acheté |
| HA SLY / Vault (Phase 2–3) quand trafic / secrets le justifient | 2e Vault « pour DAWALALE » (un Vault plateforme) |
1.3 Critères d’extraction vers serveur / namespace dédié¶
Extraire une app du mutualisé seulement si au moins un critère est prouvé (métrique ou contrainte) :
| # | Critère | Exemples |
|---|---|---|
| E1 | Données santé / CDP sensible en prod réelle | Medisen patients |
| E2 | Hub argent (PSP, wallets) en volume réel | PAIEMENT, ELISA wallet à l’échelle |
| E3 | SPOF plateforme sous charge | SLY > seuil documenté (ex. ~500 req/min soutenus) → HA / nœuds dédiés |
| E4 | SLA client exige isolation (contrat) | SuperGest « 1 backend / enseigne » |
| E5 | GPU / stack hétérogène | DYNORSAI |
| E6 | Saturation CPU/RAM/IO du mutualisé mesurée 14 j + plan capacity échoué | DAWALALE pics examens, JARAAF batch paie |
Sans E1–E6 → rester mutualisé, brancher l’app une par une via SIRRAT/deploy-product.yml.
1.4 Ordre d’achat recommandé (ne pas inverser)¶
1. Domaines marque des go-lives ≤ 6 mois (ex. dawalale.sn)
2. VPS staging mutualisé + DNS staging.dynors.com / wildcard intranet
3. Vault + backups testés (restore) sur staging
4. VPS prod mutualisé (avant 1er trafic réel)
5. Extraction dédiée uniquement si E1–E6
Décision 2 — Migration de données sans perte¶
S’applique à : staging→prod, mutualisé→dédié, renommage env (recette→rmoa/int), restauration DR, changement d’hébergeur.
2.1 Principe¶
Pas de cutover DNS/trafic avant preuve de restore sur la cible.
Perte max tolérée = RPO de la catégorie (ADR continuité). Au-delà = incident.
Catégories rappel (ADR continuité) :
| Catégorie | RPO max | Apps typiques |
|---|---|---|
| Critique | 4 h (souvent plus serré en runbook) | JARAAF, FISCAL, PAIEMENT, Medisen, BOOKS |
| Importante | 24 h | DAWALALE, ELISA, SuperGest, TRACIUM, RAGNAR… |
| Plateforme | 1 h | SLY, auth, media, broker |
2.2 Inventaire obligatoire avant toute migration (checklist)¶
Une migration n’existe que si cette liste est complète pour l’app :
| # | Actif | Exemple |
|---|---|---|
| I1 | Bases PostgreSQL (noms, schémas multi-tenant) | dawalale_*, jaraaf_{tenant} |
| I2 | Fichiers / object storage | R2/S3 fileId MEDIA — pas seulement la DB |
| I3 | Secrets Vault chemins {env}/{lane}/{app} |
DB URL, transit SLY, JWT |
| I4 | Config SIRRAT / variables runtime | versions image, lane |
| I5 | Files d’events / outbox non drainés | dynors-events, webhooks PSP en vol |
| I6 | Certificats TLS / DNS TTL | bascule progressive |
| I7 | Jobs cron / batch | paie, backups, sync |
| I8 | Dépendances inter-app | qui appelle cette app via SLY |
Oubli I2 ou I5 = cause classique de « DB OK mais produit cassé / paiements en double ».
2.3 Pattern de migration retenu : Expand → Migrate → Contract (blue/green data)¶
[Source A — lit/écrit] [Cible B — provisionnée]
│ │
│ 1. EXPAND │
│ - Créer B (DB vide+schéma) │
│ - Secrets Vault env cible │
│ - Backup A chiffré off-site│
│ - Restore test sur B' (lab)│
▼ ▼
│ 2. MIGRATE (fenêtre) │
│ - Freeze écritures A │
│ OU dual-write contrôlé │
│ - Dump final / réplication │
│ - Restore + checksums B │
│ - Smoke SLY + parcours │
│ - Cutover DNS / SIRRAT │
│ - A en lecture seule │
▼ ▼
│ 3. CONTRACT │
│ - Garder A ≥ rétention RPO │
│ (rollback à chaud) │
│ - Puis décomissionner │
Interdit : « on arrête A, on espère que le dump sur B marche, on change le DNS dans la foulée » sans restore prouvé.
2.4 Procédure détaillée (runbook type)¶
À décliner en docs/runbooks/migration-<app>-<from>-to-<to>.md pour chaque cutover réel.
Phase 0 — Préparation (J−7 à J−2)¶
- Classer l’app (critique / importante…).
- Compléter inventaire I1–I8.
- Provisionner la cible (serveur/namespace, Postgres, réseau SLY Bus uniquement pour inter-app).
- Créer chemins Vault cibles (ne pas réutiliser les secrets source en clair ; rotation si exposition).
- Backup source vérifié (job OK) + restore dry-run sur environnement lab
B'(pas la prod cible définitive si critique). - Mesurer durée dump/restore → dimensionner la fenêtre ≥ 2× durée mesurée.
- Fixer TTL DNS bas (ex. 300 s) avant le jour J si cutover DNS.
- PV / checklist signée (owner app + ops).
Phase 1 — Expand (J−1)¶
- Schéma cible au même train de migrations Liquibase/Flyway que la source (version image connue).
- Si MEDIA : bucket/préfixe cible + politique copie (rclone/sync) avant freeze si volume gros ; delta le jour J.
- Déployer l’app sur B en mode dark (pas de trafic utilisateur) ; health via SLY staging/prod interne.
- Réplication logique optionnelle (Pub/Sub PG, logical replication) pour critiques à RPO court — sinon dump final suffit si fenêtre OK.
Phase 2 — Migrate / cutover (jour J)¶
- Annonce fenêtre ; activer bannière maintenance si B2C.
- Stop writers sur A (scale à 0 jobs d’écriture / maintenance mode) — les lectures peuvent rester selon produit.
- Drainer outbox / files (I5) : plus de message en vol ou rejeu documenté.
- Backup final A (
pg_dumpcustom-Fcou base backup + WAL) → stockage off-site chiffré. - Restore sur B ; appliquer WAL si PITR.
- Contrôles d’intégrité (obligatoires) :
- comptes tables clés (users, orders, payments, tenants) source vs cible ;
- checksum agrégé ou
COUNT(*)+MAX(updated_at)sur tables métier ; - spot-check échantillon IDs (paiements, dossiers santé) ;
fileIdMEDIA : tirage aléatoire download OK.- Smoke : login → JWT → appel métier via SLY → entitlements.
- Basculer trafic (SIRRAT route / DNS / Edge) vers B.
- Monitorer erreurs 5xx / files PSP ≥ 1× RPO (min 1 h critique).
- Si échec → Rollback §2.5 (DNS/SIRRAT → A ; A reprend writers). Ne pas « réparer B sous trafic » sans décision.
Phase 3 — Contract (J+1 → J+rétention)¶
- A reste snapshot cold / lecture seule jusqu’à :
max(RPO testé, 7 j importante, 30 j critique). - Backup B actif + premier test restore post-cutover planifié ≤ 14 j.
- Décomissionner A seulement après PV « pas de rollback nécessaire ».
- Mettre à jour runbook
continuite-<app>.md(chemins, dernière restore).
2.5 Rollback (sans perte supplémentaire)¶
| Situation | Action |
|---|---|
| Restore B échoue / checksum KO | Pas de cutover ; rester sur A ; rouvrir writers A |
| Cutover fait, B instable < fenêtre rollback | Repoint DNS/SIRRAT → A ; investiguer B hors prod |
| Corruption détectée après contract | Restaurer depuis backup off-site (ADR continuité) — game day |
Interdit : écrire en parallèle sur A et B sans mécanisme d’idempotence / dual-write validé (risque divergence irrécupérable).
2.6 Cas particuliers¶
| Cas | Règle |
|---|---|
| Multi-tenant schémas (JARAAF) | Migrer schéma par schéma ou dump global avec liste ; valider RLS après restore |
| Paiements / webhooks | Idempotency keys conservées ; rejouer webhooks PSP seulement une fois ; page PSP vers nouvelle URL après smoke |
| Médias | DB sans objets = perte métier ; sync object storage dans la même fenêtre logique |
| Vault | Migrer les références ; ne jamais committer les valeurs ; rotation post-migration si copie humaine |
| Downgrade / env client | Env possédé client ≠ couloir DYNORS (ADR-0019) — contrat migration séparé |
| ELISA hors workspace | Même checklist ; owner repo externe + ops DYNORS co-signent le PV |
2.7 Definition of Done « migration OK »¶
- [ ] Inventaire I1–I8 joint au PV
- [ ] Dry-run restore réussi (durée notée)
- [ ] Checksums / counts J OK
- [ ] Smoke SLY + parcours métier OK
- [ ] Fenêtre rollback encore ouverte ou rétention A respectée
- [ ] Backups B actifs + date du prochain game day
- [ ] Aucune écriture orpheline sur A après cutover
Conséquences¶
Positif : dépenses ordonnées ; pas de VPS fantôme ; cutovers reproductibles ; aligné RPO/RTO déjà adoptés.
Coût : discipline J−7 (dry-run) ; parfois courte maintenance B2C ; rétention double hébergement pendant contract.
Anti-patterns rejetés :
- Acheter un serveur parce que le domaine est acheté
- Migrer sans inventaire MEDIA/events
- Cutover DNS avant checksums
- Supprimer la source le jour J
Alternatives considérées¶
| Option | Verdict |
|---|---|
| Big-bang dump + DNS immédiat | Rejeté — perte / downtime non bornés |
| Dual-write permanent sans contract | Rejeté — divergence chronique |
| Un VPS par produit dès le jour 1 | Rejeté — coût + ops ; contredit Phase 1 |
| Domaine marque pour staging | Rejeté — ADR-0018 |
Plan d’exécution¶
- Publier ADR + registre + règle Cursor + section cahier infra.
- Premier exercice : dry-run backup/restore socle staging dès VPS up.
- Pilote cutover documenté : DAWALALE staging→prod (quand go-live).
- Extraction dédiée Medisen : runbook migration dédié avant 1ers patients prod.
Suivi¶
- [ ] Aucun achat VPS dédié sans critère E1–E6 tracé
- [ ] Aucun cutover prod sans PV migration (DoD §2.7)
- [ ] Game days restore critiques trimestriels (ADR continuité)
Références¶
docs/adr/ADR-2026-05-15-continuite-backup-disaster-recovery.mddocs/runbooks/continuite-*.mddocs/CAHIER_ACTIONS_INFRA_VPS_SOCLE.md- ADR-0016, ADR-0017, ADR-0018, ADR-0019, ADR-0027
DYNORS-INFRA-STRATEGY.md(blue-green)- Convention URL / domaines marque (
claude-handoff-dynors/DYNORS_URL_CONVENTION_RULES.md)