14 — Tenant Migration Guide¶
Scope: The store-by-store migration process — how a Pricer Server (R3Server) store is migrated from standalone operation to running on the DTOflow cloud platform. Covers Shadow Mode, validation, switch-over, and migration candidates.
Validated: 2026-06-30 — against live GCP services (
platform-dev-p01), GitHub repos, and the PLT-2484/2487 handover diagrams.
1. Migration Overview¶
Migration is store-by-store, not tenant-wide. Each store runs through the same process independently — this lets us validate one store, learn, and then roll out to the rest.
| Phase | What happens | Risk to labels |
|---|---|---|
| Shadow Mode | DTOflow pipeline runs in parallel with R3Server; labels still driven by R3Server | Zero |
| Validation | Compare cloud outputs against R3Server baselines; run API parity checks | Zero |
| Switch | Controlled cutover — R3Server becomes thin edge; DTOflow becomes authoritative | Brief window |
---
config:
layout: elk
---
flowchart LR
SM["Shadow Mode<br/>(days/weeks)"] --> VAL["Validation<br/>(automated + manual)"] --> SW["Switch<br/>(controlled cutover)"]
SW --> LIVE["Live on DTOflow"]
style SM fill:#e8f5e9,stroke:#2e7d32,color:#000
style VAL fill:#fff3e0,stroke:#f57c00,color:#000
style SW fill:#ffebee,stroke:#c62828,color:#000
style LIVE fill:#e3f2fd,stroke:#1565c0,color:#000
2. Shadow Mode Operation¶
2.1 What Shadow Mode Means During Migration¶
During Shadow Mode, the R3Server updates its configuration to enable data export to DTOflow. Once enabled:
- Every item update committed to the R3Server MySQL is also published to DTOflow as
storeitemvalues - The DTOflow pipeline processes changes in parallel — evaluator and renderer both receive notifications independently
- DTOflow runs as the main flow — changes are pushed to DTOflow and forwarded to Pub/Sub, triggering the full pipeline
- R3Server continues driving labels from its own MySQL — labels are unaffected
2.2 Data Push Order¶
Data is pushed to DTOflow by the CQS client running in R3Server (PLT-1870). The order of data pushed does not matter for most DTO types — each service reacts independently to whatever arrives. The one exception:
Config must be pushed first. Configuration DTOs (
store,storeesl, etc.) establish the store context. Without them, downstream services lack the metadata needed to process item/link events.
| Order | DTO type | Why |
|---|---|---|
| 1 | Config DTOs (store, storeesl) |
Store context — must exist before items/links |
| 2+ | storeitemvalues, links, etc. |
Any order; services tolerate missing references and retry |
2.3 What DTOflow Does With Incoming Data¶
---
config:
layout: elk
---
flowchart TB
R3["R3Server<br/>(Shadow Mode enabled)"]
SP[("Spanner 'dtoflow'")]
PS["Pub/Sub<br/>dtoflow-changes-*"]
subgraph pipeline["DTOflow Pipeline"]
SLE["studio-link-evaluator"]
SR["studio-renderer"]
ECCLP["ecc-link-projector"]
ECCR["ecc-renderer"]
EIM["esl-image-merger"]
end
R3 -->|"write config DTOs first"| SP
R3 -->|"write items, links"| SP
SP --> PS
PS -.->|subscribed| SLE
PS -.->|subscribed| SR
PS -.->|subscribed| ECCLP
PS -.->|subscribed| ECCR
SLE -->|writes| SP
SR -->|writes| SP
ECCLP -->|writes| SP
ECCR -->|writes| SP
ECCR --> EIM
SR --> EIM
EIM -->|writes eslimage| SP
Note["No transmit to labels.<br/>R3Server still drives labels from its own MySQL."]
style R3 fill:#A9DFBF,stroke:#1E8449,color:#1a1a1a
style SP fill:#AED6F1,stroke:#2E86C1,color:#1a1a1a
style PS fill:#F9E79F,stroke:#D4AC0D,color:#1a1a1a
Key point: In Shadow Mode, the entire DTOflow pipeline runs end-to-end — evaluate, render, merge — but no images reach labels. R3Server's own rendering+transmission continues independently.
3. Validation¶
Validation runs during Shadow Mode. Two tracks run in parallel:
3.1 Image Comparison¶
Goal: Prove DTOflow produces identical images to R3Server.
For every label in the store, compare the last image rendered by DTOflow against the currently displayed image from R3Server:
- Byte-level comparison of rendered bitmaps (or hash-based for efficiency)
- Track drift: if DTOflow renders a different image, log the diff for investigation
- Once 100% match is stable for a representative period (e.g., 24 hours with normal update volume), the rendering pipeline is considered validated
3.2 API Parity¶
Goal: Prove DTOflow APIs match R3Server functionality for this tenant.
Run a comprehensive set of API calls relevant to the specific tenant. Every API surface the tenant uses must behave identically:
- Item CRUD (PATCH/POST/DELETE items, item results)
- Link operations (create, update, unlink)
- Flash / display page changes
- Bulk operations (if the tenant uses them)
- Store configuration reads
Compare responses from R3Server (source of truth) vs. DTOflow (cloud). Any discrepancy is a migration blocker.
Scope is tenant-specific. A tenant that doesn't use ECC linking doesn't need ECC API parity — validate only what that tenant actually uses.
4. Switch Procedure¶
The switch from Shadow Mode to live DTOflow is a controlled, sequenced cutover. The goal is to minimize the window where labels can't be updated while ensuring data consistency.
Step-by-step¶
---
config:
layout: elk
---
sequenceDiagram
participant RT as Router/Ingress
participant R3 as R3Server
participant CQS as CQS/Pub/Sub
participant DT as DTOflow
Note over R3,DT: Currently: Shadow Mode — R3Server drives labels, DTOflow runs in parallel
RT->>R3: Normal operation (items, links, flash...)
rect rgba(255, 0, 0, 0.1)
Note over R3,DT: === SWITCH WINDOW ===
Note over RT: Step 1: Bring store down
RT-->>R3: All API calls blocked
Note over R3: No calls reach R3Server
Note over CQS: Step 2: Pause event processing
CQS-->>CQS: CQS subscriptions paused
Note over CQS: No new events flowing to DTOflow
Note over R3: Step 3: Reconfigure R3Server
R3->>R3: Backup items + link tables
R3->>R3: Disable Shadow Mode
R3->>R3: Drop items table<br/>(R3Server no longer authoritative)
R3->>R3: Drop link table
Note over R3: Step 4: Shut down R3Server
R3-->>R3: R3Server stopped
Note over R3: All local processing halted
Note over RT: Step 5: Switch Router
RT->>DT: Re-route API calls to DTOflow
Note over CQS: Step 6: Resume event processing
CQS-->>DT: Subscriptions resumed
Note over R3: Step 7: Restart R3Server (thin edge)
R3->>R3: R3Server restarted<br/>(transmission only)
end
RT->>DT: Store is live on DTOflow
DT->>R3: Rendered images (transmission only)
| Step | Action | Duration | Impact |
|---|---|---|---|
| 1. Bring store down | All API calls interrupted at router level. No calls reach R3Server. | Seconds | Store is "offline" — no updates possible |
| 2. Pause CQS | Pause CQS subscriptions for this store's DTO types. Prevents DTOflow from processing events during reconfiguration. | Seconds | DTOflow pipeline paused |
| 3. Reconfigure R3Server | While R3Server is still running (API calls blocked): backup items + link tables, disable Shadow Mode, drop the items and link tables. | Minutes | R3Server no longer authoritative for data |
| 4. Shut down R3Server | Stop R3Server process. Guards against internal timers/background jobs. | Seconds | All local processing halted |
| 5. Switch Router | Re-point API routes from R3Server to DTOflow Cloud Run services. | Seconds | API traffic now reaches the cloud |
| 6. Resume CQS | Resume CQS subscriptions. DTOflow is now the authoritative source. | Seconds | DTOflow pipeline resumes; images flow to labels |
| 7. Restart R3Server | Restart R3Server in thin-edge mode (transmission engine only, no MySQL data). | Seconds | Store is fully live on DTOflow |
Router level: "Bring store down" should be implemented at the ingress/routing layer (Apigee or ingress-nginx), not at R3Server itself. The goal is to prevent any API call from reaching R3Server during the switch, without R3Server needing to be aware of the cutover.
Rollback¶
If the switch fails, reverse the procedure: 1. Pause CQS subscriptions 2. Re-point router back to R3Server 3. Restore R3Server items/link tables from backup (taken in Step 4) 4. Re-enable Shadow Mode on R3Server 5. Restart R3Server 6. Resume CQS subscriptions
5. Migration Candidates¶
Phase 0 — Internal Validation¶
| Store / Tenant | Description | Why First |
|---|---|---|
| Replatforming-Dev | Internal dev environment, not used by anyone | Zero impact if anything breaks; validates the migration procedure itself |
| Evo-Se | Main environment used by the dev team | Real-world usage patterns but still internal; low-risk validation |
| Application-Stage | Product validation environment | Last pre-prod check; validates against staging data |
Phase 1 — Production¶
| Store / Tenant | Description | Risk Profile |
|---|---|---|
| byPricer | Demo environment | Low risk — demo data, not production; good first real-world test |
| Landwaart AGF B.V | Many updates, no PCS, EVO tokens only | Medium risk — simpler stack (no PCS dependency), active update patterns |
| Spar-be | ~13,000 ESLs, large stores | Higher risk — large scale validates performance; run after smaller stores succeed |
Selection principle: Start with smallest, simplest stores (fewest features, lowest ESL count) and progress to larger ones. Each successful migration derisks the next.
6. Dependencies¶
Before any store can be migrated, these must be in place:
| Dependency | What | Status | Assignee |
|---|---|---|---|
| storeitemvalues export | PLT-2483 — Shadow Mode data pipe | 🟡 Ready for Deploy | Johan Ekman |
| CQS client in R3Server | PLT-1870 — exports item data to DTOflow | 🟡 Test | Daniel Pettersson |
| Item property validation | PLT-2651 — validates item properties end-to-end | 🔴 Blocked | Unassigned |
| Item Patch APIs | PLT-2378 — cloud APIs for item CRUD | 🔴 Blocked | Unassigned |
| Shadow Mode infrastructure | PLT-2354 — end-to-end shadow pipeline | 🟡 In Progress | Daniel Pettersson |
| API routing | PLT-2101 — per-API-path routing at ingress | 🟡 Selected for Dev | Saikiran Katta |
| Prod-ready DTOflow | PLT-2118 — production-grade DTOflow platform | 🟡 Test | Bart De Boer |
PLT-2651 and PLT-2378 are the critical blockers. PLT-2651 gates the item pipeline itself (4 of 5 services built but can't validate properties). PLT-2378 gates the consumer API cutover — without cloud Item Patch APIs, the router has nowhere to re-route item traffic.
7. Migration Checklist (per store)¶
- [ ] Config DTOs (
store,storeesl) pushed to DTOflow - [ ] All items and links exported to DTOflow
- [ ] Shadow Mode enabled on R3Server
- [ ] Image comparison: 100% match for 24+ hours
- [ ] API parity: all tenant-relevant APIs validated
- [ ] Switch window scheduled (low-traffic period)
- [ ] Rollback plan documented and tested
- [ ] Step 1: Router blocks API calls to R3Server
- [ ] Step 2: CQS subscriptions paused
- [ ] Step 3: R3Server reconfigured (backup taken, Shadow Mode off, tables dropped)
- [ ] Step 4: R3Server shut down
- [ ] Step 5: Router re-pointed to DTOflow
- [ ] Step 6: CQS subscriptions resumed
- [ ] Step 7: R3Server restarted (thin edge, transmission only)
- [ ] Labels verified updating correctly post-switch