Aller au contenu

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 :

  1. Dépense trop tôt — VPS dédié par idée produit, domaine marque pour un staging, serveur « à part » non branché SLY ;
  2. Dépense trop tard — Medisen/PAIEMENT en prod mutualisée trop longtemps, blast radius santé/argent ;
  3. 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 (recettermoa/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)

  1. Classer l’app (critique / importante…).
  2. Compléter inventaire I1–I8.
  3. Provisionner la cible (serveur/namespace, Postgres, réseau SLY Bus uniquement pour inter-app).
  4. Créer chemins Vault cibles (ne pas réutiliser les secrets source en clair ; rotation si exposition).
  5. Backup source vérifié (job OK) + restore dry-run sur environnement lab B' (pas la prod cible définitive si critique).
  6. Mesurer durée dump/restore → dimensionner la fenêtre ≥ 2× durée mesurée.
  7. Fixer TTL DNS bas (ex. 300 s) avant le jour J si cutover DNS.
  8. PV / checklist signée (owner app + ops).

Phase 1 — Expand (J−1)

  1. Schéma cible au même train de migrations Liquibase/Flyway que la source (version image connue).
  2. Si MEDIA : bucket/préfixe cible + politique copie (rclone/sync) avant freeze si volume gros ; delta le jour J.
  3. Déployer l’app sur B en mode dark (pas de trafic utilisateur) ; health via SLY staging/prod interne.
  4. 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)

  1. Annonce fenêtre ; activer bannière maintenance si B2C.
  2. Stop writers sur A (scale à 0 jobs d’écriture / maintenance mode) — les lectures peuvent rester selon produit.
  3. Drainer outbox / files (I5) : plus de message en vol ou rejeu documenté.
  4. Backup final A (pg_dump custom -Fc ou base backup + WAL) → stockage off-site chiffré.
  5. Restore sur B ; appliquer WAL si PITR.
  6. Contrôles d’intégrité (obligatoires) :
  7. comptes tables clés (users, orders, payments, tenants) source vs cible ;
  8. checksum agrégé ou COUNT(*) + MAX(updated_at) sur tables métier ;
  9. spot-check échantillon IDs (paiements, dossiers santé) ;
  10. fileId MEDIA : tirage aléatoire download OK.
  11. Smoke : login → JWT → appel métier via SLY → entitlements.
  12. Basculer trafic (SIRRAT route / DNS / Edge) vers B.
  13. Monitorer erreurs 5xx / files PSP ≥ 1× RPO (min 1 h critique).
  14. 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)

  1. A reste snapshot cold / lecture seule jusqu’à : max(RPO testé, 7 j importante, 30 j critique).
  2. Backup B actif + premier test restore post-cutover planifié ≤ 14 j.
  3. Décomissionner A seulement après PV « pas de rollback nécessaire ».
  4. 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

  1. Publier ADR + registre + règle Cursor + section cahier infra.
  2. Premier exercice : dry-run backup/restore socle staging dès VPS up.
  3. Pilote cutover documenté : DAWALALE staging→prod (quand go-live).
  4. 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.md
  • docs/runbooks/continuite-*.md
  • docs/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)