Backends d’Isolation Tenant

Updo Backend supporte trois stratégies d’isolation des données tenant. Le choix du backend est configuré via MULTITENANT['ISOLATION_BACKEND'].

Module : src.share_kernel.infrastructure.isolation

Factory

src.share_kernel.infrastructure.isolation.get_isolation_backend() TenantIsolationBackend

Retourne le singleton du backend d’isolation configuré. Lit MULTITENANT['ISOLATION_BACKEND'] et instancie le backend approprié.

Raises:

ConfigurationError – Si le type de backend est inconnu.

src.share_kernel.infrastructure.isolation.reset_backend() None

Réinitialise le singleton. Pour les tests uniquement.

Schema Backend (schema)

Principe : Chaque tenant obtient un schéma PostgreSQL dédié (tenant_<slug>). Le search_path est dynamiquement configuré pour chaque requête.

Recommandé pour : Jusqu’à ~2 000 tenants.

class src.share_kernel.infrastructure.isolation.schema_backend.SchemaIsolationBackend(TenantIsolationBackend)
activate_tenant(tenant, connection) None

Configure search_path vers le schéma du tenant + public. Utilise SET LOCAL (transaction-scoped) pour la compatibilité PgBouncer. Utilise psycopg.sql.Identifier() pour prévenir l’injection SQL.

Applique aussi statement_timeout pour la protection contre les « noisy neighbors ».

deactivate_tenant(connection) None

Réinitialise search_path au schéma partagé uniquement.

create_tenant_storage(tenant) None

Crée le schéma PostgreSQL et exécute les migrations.

CREATE SCHEMA IF NOT EXISTS tenant_acme_corp;
destroy_tenant_storage(tenant) None

Supprime le schéma PostgreSQL. DESTRUCTIF.

DROP SCHEMA IF EXISTS tenant_acme_corp CASCADE;
migrate_tenant(tenant) None

Applique les migrations Django dans le schéma du tenant. Configure le flag _migrating_tenant_schema pour que le DatabaseRouter n’autorise que les modèles tenant-specific.

schema_exists(schema_name: str) bool

Vérifie si un schéma PostgreSQL existe.

list_schemas(prefix: str | None = None) list[str]

Liste tous les schémas tenant.

Exemple :

# Configuration
MULTITENANT = {
    'ISOLATION_BACKEND': 'schema',
    'SHARED_SCHEMA_NAME': 'public',
    'TENANT_SCHEMA_PREFIX': 'tenant_',
}

# Le middleware active automatiquement le schéma
# SQL généré : SET LOCAL search_path TO "tenant_acme_corp", "public"

RLS Backend (rls)

Principe : Schéma unique avec des politiques RLS PostgreSQL. Le tenant courant est défini via SET app.current_tenant.

Recommandé pour : 2 000+ tenants ou PgBouncer en mode transaction pooling.

Prérequis : Toutes les tables tenant-specific doivent avoir une colonne tenant_id.

class src.share_kernel.infrastructure.isolation.rls_backend.RLSIsolationBackend(TenantIsolationBackend)
activate_tenant(tenant, connection) None

Exécute SET LOCAL app.current_tenant = '<tenant_id>'. SET LOCAL est transaction-scoped (compatible PgBouncer).

deactivate_tenant(connection) None

Efface app.current_tenant avec SET LOCAL app.current_tenant = ''.

create_tenant_storage(tenant) None

Active RLS et crée les politiques d’isolation pour toutes les tables ayant une colonne tenant_id.

Politique créée :

ALTER TABLE <table> ENABLE ROW LEVEL SECURITY;
ALTER TABLE <table> FORCE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation_<table> ON <table>
USING (tenant_id = current_setting('app.current_tenant', true)::uuid)
WITH CHECK (tenant_id = current_setting('app.current_tenant', true)::uuid);
destroy_tenant_storage(tenant) None

Supprime toutes les lignes du tenant (DELETE WHERE tenant_id = ?). Utilise SET CONSTRAINTS ALL DEFERRED pour éviter les problèmes d’ordre FK.

suspend_isolation(connection) None

Désactive temporairement RLS pour les requêtes cross-tenant. Nécessite le rôle BYPASSRLS.

resume_isolation(connection) None

Réactive RLS après une opération cross-tenant.

migrate_tenant(tenant) None

No-op : en mode RLS, les migrations sont partagées.

Shared FK Backend (shared_fk)

Principe : Schéma unique, filtrage au niveau applicatif via tenant_id FK. Aucune garantie au niveau base de données.

Recommandé pour : Prototypage et petits déploiements.

class src.share_kernel.infrastructure.isolation.shared_fk_backend.SharedFKIsolationBackend(TenantIsolationBackend)
activate_tenant(tenant, connection) None

No-op. Le filtrage est géré par les managers et querysets Django.

deactivate_tenant(connection) None

No-op.

create_tenant_storage(tenant) None

No-op. Pas de stockage séparé à créer.

destroy_tenant_storage(tenant) None

Supprime toutes les lignes du tenant de toutes les tables tenant-specific.

migrate_tenant(tenant) None

No-op. Les migrations sont partagées.

Comparaison des backends

Comparaison des backends d’isolation

Critère

Schema

RLS

Shared FK

Isolation DB

Forte (schéma dédié)

Forte (politique PostgreSQL)

Faible (applicatif)

Tenants max

~2 000

Illimité

Illimité

PgBouncer

Oui (SET LOCAL)

Oui (SET LOCAL)

Oui

Migrations

Par tenant

Partagées

Partagées

Performance

Haute (index séparés)

Haute (RLS natif)

Moyenne

Complexité

Élevée

Moyenne

Faible