Skip to content
GitHub

ADR-001: Multi-Tenant Strategy

Aceptada - 2024 (fecha estimada basada en implementación)


El sistema Nostromo debe soportar múltiples empresas clientes (tenants) con requisitos estrictos de:

  • Aislamiento de datos: Datos de un cliente NO deben ser accesibles por otro cliente bajo ninguna circunstancia
  • Escalabilidad: Sistema debe soportar decenas o cientos de clientes simultáneamente
  • Compliance: Cumplimiento de normativas de protección de datos (ej: GDPR, ley chilena de protección de datos)
  • Independencia operacional: Cada cliente debe poder tener versiones de schema diferentes si es necesario (migraciones graduales)
  • Presupuesto limitado: No es viable mantener bases de datos separadas para cada cliente
  • Complejidad operacional: El equipo es pequeño, se necesita solución mantenible
  • Rendimiento: La solución debe ser eficiente (latencia baja, throughput alto)

Implementar schema-per-tenant en PostgreSQL:

  • Cada cliente (tenant) tiene su propio schema dentro de una única base de datos PostgreSQL
  • El schema contiene todas las tablas de negocio del cliente (employees, contracts, payroll, etc.)
  • El tenant ID se resuelve al inicio de cada request y se asigna el schema correspondiente vía SET search_path
  • Un schema especial central contiene metadatos de tenants, usuarios, y configuración global
-- Ejemplo de estructura
CREATE SCHEMA tenant_empresa_123; -- Schema para empresa ABC
CREATE SCHEMA tenant_empresa_456; -- Schema para empresa XYZ
CREATE SCHEMA central; -- Metadatos globales
-- Cada request ejecuta:
SET search_path TO tenant_empresa_123;
-- Ahora todas las queries usan automáticamente ese schema

Componentes clave:

  • Mother container: Base de datos PostgreSQL 16 multi-tenant
  • tenantResolver.ts: Resuelve tenant desde JWT/request → nombre de schema
  • getTenantPool(): Obtiene pool de conexiones y configura schema

Aislamiento completo de datos

  • Separación a nivel de schema garantiza que SELECT * FROM employees de la empresa A nunca retorna datos de empresa B
  • No hay riesgo de “data leakage” por olvidar WHERE tenant_id = ? en queries

Escalabilidad horizontal

  • Posibilidad futura de distribuir schemas a diferentes servidores PostgreSQL usando Foreign Data Wrappers (FDW)
  • Si tenant_empresa_123 crece mucho, se puede mover a servidor dedicado sin cambiar código

Migrations independientes

  • Cada tenant puede estar en versión diferente de schema
  • Permite migraciones graduales por cliente (importante para sistemas críticos)
  • Rollback de un tenant no afecta a otros

Simplicidad operacional vs database-per-tenant

  • Un único servidor PostgreSQL para gestionar (backups, monitoring, tuning)
  • Conexiones compartidas en pool central (más eficiente)

Performance aceptable

  • SET search_path es operación muy rápida en PostgreSQL (~0.1ms)
  • Queries ejecutan igual que en base de datos dedicada
  • Índices, statistics, query planner funcionan normalmente

Complejidad de connection pooling

  • Requiere gestión de pool con schema-switching por request
  • No se puede usar pool estándar “listo para usar” sin configuración
  • Mitigación: Implementamos pool central en Mother con lógica de schema switching

Overhead de migrations

  • Migración de schema debe ejecutarse N veces (una por tenant)
  • Si hay 50 tenants, un ALTER TABLE se ejecuta 50 veces
  • Mitigación: Scripts de migración automatizan ejecución multi-tenant

PostgreSQL dependency fuerte

  • Solución está acoplada a PostgreSQL (schemas son feature de Postgres)
  • Migrar a MySQL/MongoDB requeriría rediseño completo
  • Mitigación: PostgreSQL es estable, maduro, open-source - riesgo aceptable

Límite teórico de escalabilidad

  • PostgreSQL tiene límite práctico de ~1000 schemas por DB
  • Si se superan 1000 clients, se necesita múltiples DBs
  • Mitigación: Estrategia de sharding cross-database vía FDW está documentada

Backup complejo

  • Un backup contiene todos los tenants (no aislados)
  • Restaurar UN solo tenant requiere extracción selectiva
  • Mitigación: pg_dump --schema=tenant_X permite dumps por tenant

Descripción: Cada cliente tiene su propia base de datos PostgreSQL completa.

Rechazada porque:

  • Costos de infraestructura: 100 clientes = 100 bases de datos = recursos x100
  • Complejidad operacional: Gestionar 100 servidores DB (backups, monitoring, tuning, upgrades)
  • Conexiones insostenibles: Cada DB necesita su pool de conexiones → explosión de recursos
  • No escala para modelo SaaS: Agregar nuevo cliente requiere provisionar DB completa (lento)

Cuándo sería válida: Si cada cliente paga por infraestructura dedicada (no SaaS), o hay requisitos legales de aislamiento físico.


Descripción: Todas las tablas tienen columna tenant_id, se filtran queries con WHERE tenant_id = ?.

Ejemplo:

SELECT * FROM employees WHERE tenant_id = 123;

Rechazada porque:

  • Riesgo CRÍTICO de data leakage: Un WHERE tenant_id olvidado expone datos de todos los clientes
    • En JOIN complejos, fácil olvidar filtro en UNA tabla
    • Bug en código → brecha de seguridad catastrófica
  • Migrations difíciles: No puedes migrar UN tenant sin afectar a todos
  • Performance degradado: Índices contienen datos de TODOS los tenants, menos eficientes
  • Compliance problemático: Auditores prefieren aislamiento físico/lógico claro

Cuándo sería válida: Si requisitos de aislamiento son bajos (ej: multi-tenant “soft” donde clientes comparten ciertos datos).


Opción C: Híbrido (schema-per-tenant + shared tables)

Section titled “Opción C: Híbrido (schema-per-tenant + shared tables)”

Descripción: Tablas de negocio en schemas separados, tablas comunes (parámetros SII, normativa) en schema shared.

Considerada pero no implementada inicialmente:

  • Añade complejidad sin beneficio claro para v1
  • Parámetros SII cambian poco, duplicar en cada schema es aceptable
  • Reservado para futuro: Si storage de parámetros se vuelve problemático

Implementación:

Decisiones relacionadas:

Referencias externas:


Durante la implementación descubrimos:

  1. Schema switching es rápido: Temor inicial de overhead de SET search_path resultó infundado - mediciones muestran ~0.1ms
  2. Migrations requieren scripting robusto: Primera migración multi-tenant fue manual y propensa a errores - ahora automatizada
  3. Monitoring por tenant es valioso: Añadimos métricas por schema (storage, query time) para detectar clientes “pesados”

Si se supera escala actual (~100-200 tenants):

  1. Sharding cross-database: Usar FDW para distribuir schemas a múltiples servidores PostgreSQL
  2. Read replicas por tenant: Clientes grandes pueden tener read replica dedicada
  3. Migración a Citus: Extensión PostgreSQL para sharding nativo multi-tenant

Próxima revisión: Cuando se alcance 80% del límite práctico de schemas (~800 clientes).