Drastisch minder resources
Van ~40 containers naar ~4. Significante besparing op RAM en CPU, vooral relevant bij groei naar 20+ tenants.
+======================================================================+| || ███╗ ███╗██╗ ██╗██╗ ████████╗██╗ || ████╗ ████║██║ ██║██║ ╚══██╔══╝██║ || ██╔████╔██║██║ ██║██║ ██║ ██║ || ██║╚██╔╝██║██║ ██║██║ ██║ ██║ || ██║ ╚═╝ ██║╚██████╔╝███████╗██║ ██║ || ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ || || T E N A N T C O N S O L I D A T I O N P L A N || || Magic e-VERSE Commerce Platform || PostgreSQL RLS + Single Medusa Instance || || Status: EVALUATIE || Prioriteit: Middel || Impact: Alle commerce tenants || |+======================================================================+Dit plan evalueert de haalbaarheid van het consolideren van de huidige 10 afzonderlijke Medusa-containers naar een enkele Medusa-instance met PostgreSQL Row Level Security (RLS), gebaseerd op het open-source project rigby-sh/medusajs-multi-tenancy-ecommerce.
┌──────────────────────────────────────────────────────────────────┐│ HUIDIGE ARCHITECTUUR ││ Container-per-Tenant │├──────────────────────────────────────────────────────────────────┤│ ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │ brinxx │ │ bovisales │ │ jodasign │ ... x10 ││ │ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ │ ││ │ │ Backend │ │ │ │ Backend │ │ │ │ Backend │ │ ││ │ │ :4040 │ │ │ │ :4050 │ │ │ │ :4070 │ │ ││ │ ├─────────┤ │ │ ├─────────┤ │ │ ├─────────┤ │ ││ │ │Storefront│ │ │ │Storefront│ │ │ │Storefront│ │ ││ │ │ :10040 │ │ │ │ :10050 │ │ │ │ :10070 │ │ ││ │ ├─────────┤ │ │ ├─────────┤ │ │ ├─────────┤ │ ││ │ │ Redis │ │ │ │ Redis │ │ │ │ Redis │ │ ││ │ │ :6340 │ │ │ │ :6350 │ │ │ │ :6370 │ │ ││ │ ├─────────┤ │ │ ├─────────┤ │ │ ├─────────┤ │ ││ │ │Meili │ │ │ │Meili │ │ │ │Meili │ │ ││ │ │ :7740 │ │ │ │ :7750 │ │ │ │ :7770 │ │ ││ │ └─────────┘ │ │ └─────────┘ │ │ └─────────┘ │ ││ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ ││ │ │ │ ││ ▼ ▼ ▼ ││ ┌──────────────────────────────────────────────┐ ││ │ PostgreSQL 16 (enkele instance) │ ││ │ magic_b2b_brinxx │ magic_b2b_bovisales │ ... │ ││ │ Aparte database per tenant │ ││ └──────────────────────────────────────────────┘ ││ │└──────────────────────────────────────────────────────────────────┘| # | Tenant | Backend | Storefront | Redis | Meili | Database |
|---|---|---|---|---|---|---|
| 1 | development | :4010 | :10010 | :6310 | :7715 | magic_b2b_development |
| 2 | demo | :4020 | :10020 | :6320 | :7720 | magic_b2b_demo |
| 3 | default | :4030 | :10030 | :6330 | :7730 | magic_b2b_default |
| 4 | brinxx | :4040 | :10040 | :6340 | :7740 | magic_b2b_brinxx |
| 5 | bovisales | :4050 | :10050 | :6350 | :7750 | magic_b2b_bovisales |
| 6 | master_magic | :4059 | — | :6390 | — | master_magic |
| 7 | desluis | :4060 | :10060 | :6360 | :7760 | magic_b2b_desluis |
| 8 | jodasign | :4070 | :10070 | :6370 | :7770 | magic_b2b_jodasign |
| 9 | logohorloge | :4080 | :10080 | :6381 | :7780 | magic_b2b_logohorloge |
| 10 | spranz | :4091 | :10090 | :6391 | :7710 | magic_b2b_spranz |
Totaal resources per tenant: 4 containers (backend, storefront, Redis, Meilisearch) Totaal draaiende containers: ~40 containers voor 10 tenants
┌──────────────────────────────────────────────────────────────────┐│ RLS ARCHITECTUUR ││ Single Instance + RLS │├──────────────────────────────────────────────────────────────────┤│ ││ ┌──────────────────────────────────────────────────┐ ││ │ Nginx / Reverse Proxy │ ││ │ brinxx.magiceverse.online ─────┐ │ ││ │ bovisales.magiceverse.online ──┤ x-tenant-id │ ││ │ jodasign.magiceverse.online ───┘ header inject │ ││ └──────────────────┬───────────────────────────────┘ ││ │ ││ ▼ ││ ┌──────────────────────────────────────────────────┐ ││ │ Medusa Backend (single instance) │ ││ │ │ ││ │ ┌─────────────┐ ┌──────────────────────────┐ │ ││ │ │ Middleware │ │ AsyncLocalStorage │ │ ││ │ │ x-tenant-id │──│ tenant_id per request │ │ ││ │ └─────────────┘ └──────────┬───────────────┘ │ ││ │ │ │ ││ │ ┌──────────────────────────┐│ │ ││ │ │ Knex Patch ││ │ ││ │ │ SET app.current_tenant │◄ │ ││ │ │ before every query │ │ ││ │ └──────────────────────────┘ │ ││ └──────────────────┬───────────────────────────────┘ ││ │ ││ ┌──────────────────▼───────────────────────────────┐ ││ │ PostgreSQL 16 (single database) │ ││ │ │ ││ │ ┌─────────────────────────────────────────────┐ │ ││ │ │ Elke tabel: + tenant_id kolom │ │ ││ │ │ RLS Policy: WHERE tenant_id = current_tenant│ │ ││ │ │ 44+ tabellen met RLS │ │ ││ │ └─────────────────────────────────────────────┘ │ ││ │ │ ││ │ Admin mode: geen header = alle data zichtbaar │ ││ └──────────────────────────────────────────────────┘ ││ ││ ┌───────────┐ ┌───────────┐ ││ │ Redis │ │ Meili │ (gedeeld, met tenant prefix) ││ │ (single) │ │ (single) │ ││ └───────────┘ └───────────┘ ││ │└──────────────────────────────────────────────────────────────────┘Middleware laag — Express middleware op alle routes die x-tenant-id uit de HTTP header haalt en opslaat in Node.js AsyncLocalStorage. Tenant ID wordt gevalideerd als UUID.
Framework patch laag — Een patch-package patch op @medusajs/framework die Knex’s connection pool hookt. Vóór elke query wordt SET app.current_tenant = '<tenant-id>' uitgevoerd als PostgreSQL session variable.
Database laag — PostgreSQL Row Level Security policies op 44+ tabellen. Elke tabel krijgt een tenant_id kolom met default current_setting('app.current_tenant'). SELECT/INSERT/UPDATE/DELETE policies filteren automatisch op de actieve tenant.
| Aspect | Container-per-Tenant (huidig) | RLS Single Instance |
|---|---|---|
| Backend processes | 10 Medusa instances | 1 Medusa instance |
| Redis instances | 10 | 1 (met tenant prefix) |
| Meilisearch instances | 9 | 1 (met tenant index) |
| Storefront instances | 9 Next.js apps | 1-2 (met tenant routing) |
| Databases | 10 aparte databases | 1 gedeelde database |
| RAM schatting | ~20-30 GB | ~4-6 GB |
| Nieuwe tenant toevoegen | 4 containers + nginx + DB | 1 DB migratie + nginx |
Drastisch minder resources
Van ~40 containers naar ~4. Significante besparing op RAM en CPU, vooral relevant bij groei naar 20+ tenants.
Eén deployment
Bugfixes en features worden direct voor alle tenants beschikbaar. Geen rsync-pipeline meer nodig.
Database-level isolatie
RLS wordt door PostgreSQL zelf afgedwongen. Kan niet omzeild worden door applicatiefouten of SQL-injectie.
Eenvoudiger onboarding
Nieuwe tenant = UUID aanmaken + seed data draaien. Geen container-orchestratie nodig.
Fragiele framework patch
De Knex-patch target een specifieke Medusa versie (2.10.1). Bij elke Medusa upgrade moet de patch opnieuw gevalideerd en aangepast worden. Dit is het grootste risico.
Gedeeld risico (blast radius)
Eén crash = alle tenants down. Geen mogelijkheid om één tenant terug te rollen zonder alle tenants te raken.
APLT module compatibiliteit
Onze custom APLT B2B module (producten, technieken, prijzen) moet volledig RLS-compatible gemaakt worden. Alle custom tabellen hebben tenant_id nodig.
Superuser restrictie
PostgreSQL superusers bypassen RLS volledig. De applicatie MOET draaien als non-superuser. Migraties moeten apart als superuser draaien — dit vereist wijziging in het deploy-proces.
| Risico | Impact | Kans | Mitigatie |
|---|---|---|---|
| Medusa upgrade breekt patch | Hoog — alle tenants onbereikbaar | Hoog — bij elke upgrade | Pin Medusa versie, test patch vóór upgrade in staging |
| Data leak tussen tenants | Kritiek — privacy/compliance | Laag — RLS is robuust | Non-superuser afdwingen, integration tests per release |
| Single point of failure | Hoog — alle tenants tegelijk down | Middel | Health checks, auto-restart, DB replicatie |
| APLT module incompatibel | Hoog — custom business logic | Middel | Uitgebreide audit + test suite voor APLT tabellen |
| Performance degradatie | Middel — trage queries | Laag | tenant_id indexes, query plan monitoring |
| Tenant-specifieke customizations | Middel — per-tenant features onmogelijk | Hoog | Feature flags systeem bouwen |
| Component | Huidig | Na RLS |
|---|---|---|
| Backend code sync | rsync naar 6+ tenants | Niet meer nodig — single codebase |
| Storefront code sync | rsync naar 6+ tenants | Storefront routing per tenant nodig |
| Docker compose files | 10 aparte files | 1 docker-compose |
| Nginx configs | 20+ site configs | Vereenvoudigd, maar tenant-to-UUID mapping nodig |
| Database migraties | Per tenant apart | Eenmalig, maar complexer (RLS policies) |
De custom APLT B2B module bevat tabellen die tenant_id nodig hebben:
aplt_products → tenant_id + RLS policiesaplt_techniques → tenant_id + RLS policiesaplt_product_variants → tenant_id + RLS policiesaplt_buying_prices → tenant_id + RLS policiesaplt_product_images → tenant_id + RLS policies (of gedeeld via PIM?)De huidige storefronts zijn identieke Next.js apps per tenant met tenant-specifieke environment variables (kleuren, logo, API keys). Bij RLS consolidatie zijn er twee opties:
Optie A: Single Storefront met tenant routing
x-tenant-id header meesturen bij alle Medusa API callsOptie B: Aparte storefronts behouden
Fase 1: Consolideer gedeelde services
Fase 2: Standaardiseer code deployment
Fase 3: Evalueer RLS voor nieuwe tenants (optioneel)
Fase 4: Monitor Medusa’s eigen multi-tenancy (afwachten)
Onafhankelijk van de multi-tenancy beslissing, kunnen deze optimalisaties direct uitgevoerd worden:
# Huidige situatie: 10 Redis containers# Voorstel: 1 Redis instance met database nummering
# Per tenant een aparte Redis database (0-15 beschikbaar)# brinxx: redis://redis:6379/0# bovisales: redis://redis:6379/1# jodasign: redis://redis:6379/2# etc.# Huidige situatie: 9 Meilisearch containers# Voorstel: 1 Meilisearch instance met tenant-prefixed indexes
# brinxx: products_brinxx, categories_brinxx# bovisales: products_bovisales, categories_bovisales# etc.Pre-implementatie:[ ] Medusa versie pinnen op een specifieke release[ ] patch-package valideren tegen onze Medusa versie (2.13.1)[ ] APLT module audit: alle custom tabellen identificeren[ ] PIM sync-flow documenteren en redesign plannen[ ] Non-superuser PostgreSQL setup testen[ ] Staging omgeving inrichten met RLS
Implementatie:[ ] PostgreSQL non-superuser (medusa_app_user) aanmaken[ ] RLS migratie schrijven voor alle 44+ Medusa tabellen[ ] RLS migratie schrijven voor APLT tabellen[ ] Middleware implementeren (x-tenant-id uit domein)[ ] Knex patch toepassen en testen[ ] Data migratie script: 10 databases → 1 database met tenant_id[ ] Nginx aanpassen: domein → tenant UUID mapping[ ] Storefront routing implementeren (Optie A of B)[ ] PIM sync aanpassen voor tenant_id
Validatie:[ ] Integration tests per tenant[ ] Cross-tenant data leak test[ ] Performance benchmark (10 tenants in 1 DB)[ ] Failover en recovery test[ ] Admin dashboard test (geen tenant = alle data)Bij de hybride aanpak (Fase 1-2) is rollback eenvoudig:
Bij volledige RLS migratie is rollback complex: