Overview
VIGIA implements a database-per-tenant architecture where each client gets a completely isolated PostgreSQL database. This provides the strongest level of data isolation while maintaining a centralized master database for tenant management.Architecture Components
Master Database
The master database (MASTER_DATABASE_URL) stores:
- Tenant registry (
clientes_saastable) - SaaS configuration
- Cross-tenant analytics (optional)
- License management
Tenant Databases
Each tenant gets a dedicated database with:- Complete VIGIA schema (all tables)
- Isolated data (users, cases, products, etc.)
- Independent backups
- Separate connection pools
Architecture Diagram
Tenant Model
Tenants are represented by theClienteSaaS model (backend/app/models/cliente_saas.py:23-124):
Core Fields
| Field | Type | Description |
|---|---|---|
id | Integer | Primary key |
ruc | String(11) | Tax ID (unique) |
razon_social | String(255) | Legal business name |
subdominio | String(63) | Tenant subdomain (unique) |
correo_acceso | String(255) | Admin email for initial user |
contacto_nombre | String(255) | Contact person name |
plan | String(50) | Subscription plan |
entorno | String(20) | Environment: demo/produccion |
is_active | Boolean | Tenant activation status |
config | JSONB | Custom configuration |
Plan and Limits
| Field | Type | Description |
|---|---|---|
limitar_emision | Boolean | Enable emission limits |
max_comprobantes | Integer | Max invoices/documents |
max_usuarios | Integer | Max user accounts |
max_establecimientos | Integer | Max establishment locations |
Computed Properties
TheClienteSaaS model includes helpful computed properties:
Example Record
Configuration
Environment Variables
Configure multi-tenancy in.env (backend/app/core/config.py:62-68):
Template Validation
TheTENANT_DB_TEMPLATE is validated at startup (backend/app/core/config.py:278-294):
Tenant Provisioning
Provisioning creates a new tenant database and admin user in three steps.Step 1: Create Physical Database
Function:_create_database_if_not_exists() at backend/app/services/tenants.py:58-77
AUTOCOMMIT isolation level for DDL operations.
Step 2: Initialize Schema
Function:_init_tenant_schema() at backend/app/services/tenants.py:82-93
Base.metadata, including:
users,roles,user_rolesicsr,icsr_products,icsr_eventsproducts,clients,surveillance_alerts- All other VIGIA tables
Step 3: Create Admin User
Function:_create_tenant_admin_user() at backend/app/services/tenants.py:98-153
Complete Provisioning Flow
Orchestration function:provisionar_tenant() at backend/app/services/tenants.py:196-206
Tenant Management API
The admin API provides complete tenant lifecycle management atbackend/app/routers/admin_clientes.py.
List Tenants
Create Tenant
backend/app/routers/admin_clientes.py:43-79:
Update Tenant
Toggle Activation
is_active status. Inactive tenants cannot authenticate.
Delete Tenant
Soft delete (default):is_active = false without removing data.
Hard delete (permanent):
backend/app/routers/admin_clientes.py:114-143:
Database Deletion
Hard deletion requires terminating active connections before dropping the database.Drop Database Function
drop_database_if_exists() at backend/app/services/tenants.py:158-185:
Connection Termination
_terminate_db_connections() at backend/app/services/tenants.py:35-53:
Tenant Resolution
VIGIA resolves the active tenant using multiple strategies.Tenant Session Helper
get_tenant_session() at backend/app/core/tenants.py:87-94:
Engine Caching
get_tenant_engine() at backend/app/core/tenants.py:75-84:
Request-Level Tenant Resolution
Tenants are identified via theX-Tenant header in HTTP requests:
Migration from Single-Tenant
Backward Compatibility
VIGIA maintains backward compatibility with single-tenant deployments:Migration Steps
-
Set up master database:
-
Update environment:
-
Create
clientes_saastable in master: -
Migrate existing database as first tenant:
-
Update connection routing to use
X-Tenantheader
Best Practices
Tenant Naming
- Subdomains: Use lowercase, alphanumeric, hyphens only
- Database names: Auto-generated as
vigia_{subdominio} - Uniqueness: Enforce unique RUC and subdomain
Security
- Isolation: Each tenant has separate database
- Authentication: JWT tokens include tenant claim
- Validation: Always validate
X-Tenantheader - Hard deletes: Require explicit confirmation
Performance
- Engine caching: Cache database engines per tenant
- Connection pooling: Use
pool_pre_ping=True - Lazy provisioning: Create databases only when needed
Monitoring
- Logging: All tenant operations are logged with tenant ID
- Trace IDs: Track provisioning across steps
- Error handling: Graceful degradation for missing databases