Saltar al contenido
Insights
Ingeniería/5 min/27 de febrero de 2026

Por qué cada cliente tiene su propia base de datos

La separación de datos no es un detalle técnico. Es una decisión de arquitectura con consecuencias reales para la privacidad, la seguridad y la operación.

Cuando diseñamos la arquitectura de Pegasuz Core, tuvimos que tomar una decisión que parece técnica pero que tiene consecuencias en la privacidad, la seguridad y la forma en que operamos a largo plazo: ¿cómo separamos los datos entre clientes?

La respuesta más común en sistemas multi-tenant es el modelo de tenant_id: todos los datos van a las mismas tablas, cada fila tiene una columna que indica a qué cliente pertenece, y cada query filtra por ese campo. Es más fácil de implementar, es más fácil de escalar horizontalmente, y es lo que usa la mayoría del software SaaS conocido.

Nosotros elegimos el camino diferente: base de datos separada por cliente.

01

El problema real del tenant_id

El modelo de tenant_id tiene una vulnerabilidad sistémica que no se resuelve con más cuidado o con mejores tests: la separación depende de que cada query filtre correctamente.

En un sistema con decenas de endpoints, cientos de queries y múltiples desarrolladores trabajando en el tiempo, la probabilidad de que en algún momento alguien olvide el filtro de tenant_id es real. No es un problema hipotético — es un patrón de bug conocido en sistemas multi-tenant que ha causado incidentes de privacidad en productos conocidos.

Cuando ese bug ocurre en el modelo tenant_id, el daño puede ser masivo: datos de todos los tenants expuestos en una query que debería devolver solo datos de uno.

Con bases de datos separadas, el aislamiento es estructural. Un error en el código puede romper funcionalidad — un endpoint que devuelve un error, una query que retorna un resultado vacío. Pero no puede devolver datos del cliente equivocado, porque la conexión de base de datos está apuntando a la base del cliente correcto desde el middleware.

02

Las ventajas operativas que subestimamos al principio

Cuando diseñamos el sistema, pensamos principalmente en seguridad. Lo que subestimamos fue el valor operativo de la separación.

**Backups independientes**: podemos hacer backup de un cliente específico sin incluir datos de los demás. Podemos restaurar a un punto específico en el tiempo para un cliente sin afectar a los otros.

**Migraciones graduales**: cuando necesitamos actualizar el schema, podemos hacerlo cliente por cliente. Si la migración tiene un problema, lo encontramos en el primero antes de aplicarlo a todos.

**Análisis de datos**: podemos correr queries de análisis o mantenimiento sobre la base de un cliente específico sin riesgo de afectar a otros.

**Movimiento de datos**: si un cliente necesita más recursos o quiere un servidor dedicado, podemos moverlo sin tocar a los demás.

Ninguna de estas ventajas es posible en el modelo tenant_id sin complejidad adicional significativa.

03

El costo real que no queremos ocultar

La separación por base de datos tiene costos reales que vale la pena nombrar.

**Overhead de conexiones**: cada request abre una conexión a la base del tenant correspondiente. Con pooling de conexiones, el overhead es manejable, pero existe.

**Complejidad de migraciones**: cuando actualizamos el schema, necesitamos correr la migración en cada base de datos de cliente. Tenemos scripts que lo automatizan, pero es operación adicional que puede fallar.

**Queries cross-tenant**: en el modelo tenant_id, una query que necesita comparar datos de múltiples tenants es directa. Con bases separadas, es inherentemente más compleja.

Para el volumen y la naturaleza de nuestros clientes actuales, estos costos son aceptables. Si el sistema creciera a miles de tenants o a millones de registros, probablemente reconsideraríamos algunas partes de la arquitectura.

04

Cómo funciona en la práctica

El middleware `clientResolver` intercepta cada request, lee el header `x-client`, busca el tenant en la base de datos central, y resuelve la URL de conexión a la base del cliente. Esa URL se usa para instanciar un cliente Prisma que se inyecta en el request.

Todo el código de negocio trabaja con ese cliente Prisma. El controller de propiedades hace `req.prisma.property.findMany()` sin saber a qué base de datos está conectado. La separación es transparente para el código de negocio.

La configuración de qué base de datos usa cada cliente vive en la base de datos central, no en archivos de configuración. Eso significa que agregar un cliente nuevo no requiere cambios de configuración en el código — solo un registro en la base central.

05

La privacidad como punto de diseño

La decisión de separación por base de datos es también una decisión de privacidad.

Cuando un cliente nos confía datos de sus usuarios — inquilinos, compradores, contactos — tiene una expectativa razonable de que esos datos no se mezclan con los de otro negocio. La separación física de bases de datos hace que esa expectativa sea técnicamente verdadera, no solo contractualmente prometida.

En un contexto donde la privacidad de datos se regula cada vez más, esa separación estructural es un argumento concreto, no solo una promesa.

Takeaways
01

La capa editorial necesita estructura, no solo publicacion.

02

Metadata, taxonomias y locale afectan el valor futuro del contenido.