CreaRack-SL

Base de datos CreaRack Pro · arquitectura técnica

Base de datos CreaRack Pro · arquitectura técnica

Referencia técnica del esquema PostgreSQL. Para una explicación accesible ve a [[crearack-tech—bd—coloquial]].


1. Stack de persistencia

ComponenteVersiónRolNotas
PostgreSQL18BD principal, fuente de verdadContenedor crearack-pro-zcmvsl-db-1
pgbouncerlatestConnection pooling (transaction mode)Contenedor crearack-pro-zcmvsl-pgbouncer-1. Necesario por Daphne ASGI + workers Huey
Valkey7.2Cache + broker Huey + Channels backendNO persiste datos críticos
VictoriaMetrics1.106.1Métricas time-series (180 días)Fuera de PG: separación operativa de datos cuantitativos
WhiteNoise + Brotli1.1.0Servir media/static (no toca BD)—

Conexiones

  • CONN_MAX_AGE = 0 (obligatorio con Daphne ASGI — footgun revertido 4 veces, causa saturación si se cambia)
  • DATABASES['default']['HOST'] apunta a pgbouncer:6432 en PROD/STAGE
  • Pool default por worker. PG max_connections=500, pgbouncer pool default_pool_size=20
  • Desde s215 la app conecta con el rol crearack_app (NOSUPERUSER) — condición necesaria para que RLS muerda de verdad (ver 2.2)
  • ⚠️ Los tests del gate pre-push usan config.settings.test con BD directa a db:5432 sin pgbouncer (s233 — mata los falsos rojos del teardown paralelo)

2. Multi-tenancy · arquitectura de dos capas

2.1 Capa ORM (Django)

Patrón obligatorio en todos los queries:

device = get_object_or_404(Device, id=device_id, rack__organization=org)

get_current_org(request) retorna None si el user no tiene org. No hay fallback — cada endpoint debe verificar.

2.2 Capa DB (PostgreSQL RLS)

Implementado en v1.0.50 (03-04-2026), migration core/0017_rls_policies. ACTIVO DE VERDAD desde s215 (v1.48.1, 09-07-2026) en PROD y desde s224 en STAGE: la app conecta como crearack_app NOSUPERUSER (antes conectaba como superuser y las 38 políticas eran inertes — footgun footguns_rls_inerte_superuser_prod).

Mecanismo:

  1. TenantRLSMiddleware (después de ModuleGatingMiddleware) ejecuta SET app.current_org_id = '{org_id}' al inicio del request
  2. PG aplica política USING (organization_id = current_setting('app.current_org_id')::int) en cada SELECT/UPDATE/DELETE
  3. Al terminar, RESET app.current_org_id previene leakage en conexiones pooled

Bypass (estado post-s215):

Condiciónapp.current_org_idAcceso
Superuser de la app0Todas las filas
Anónimo / sin org0Bypass (páginas públicas)
Usuario con org<org_id>Solo filas de su org
crearack_app sin SET explícito0 (default de rol, intencionado: modo admin/migraciones/Huey)Todas las filas
Rol sin default y GUC jamás fijadoNULL0 filas (¡no es bypass!)
postgres superuser / dani_readonly (psql diagnóstico)—Ve todo (RLS no aplica a superuser/roles con BYPASSRLS)

⚠️ Trampa de verificación (cazada s224): un test crudo por psql con crearack_app hereda el default '0' y ve TODO — engaña. Para probar el aislamiento hay que fijar el GUC a un org real: SET app.current_org_id = '1' → 1 fila propia · '999999' → 0 filas. Pendiente aparte: endurecer el bypass de Huey (fijar GUC por task y retirar el default de rol — ciclo propio, necesita test de task real).

2.3 Cobertura

29 tablas con organization_id NOT NULL (aislamiento estricto):

AppTablas
racksrack, rackgroup
blueprintsblueprint, aiprompt
networkdevice_profiles, custom_mibs, port_connections
monitoringmonitoringtarget, aiinsight, slapolicy, notificationchannel, escalationpolicy, incidentgroup, maintenancewindow, knownissue, runbook, recurringpattern
signagesignageplayer, signageoperation, mediaasset, playlist, schedule, clientproject, playbacklog, contentdeployment, clientsharelink
terminalscript, agentinstance
corestoredcredential

6 tablas con organization_id NULLABLE (filas propias + globales):

TablaPor qué nullable
core_systemlogLogs visibles por todos
core_scripttemplateTemplates globales compartidos
racks_boxcategoryCategorías base pre-instaladas
racks_stencilStencils de fabricantes compartidos
monitoring_monitoringalertAlertas globales
core_userDjango AuthenticationMiddleware consulta usuarios antes del RLS middleware

Tablas con FK heredada (Device→Rack→Org, ConfigBackup→Device→Rack→Org, etc.) no necesitan política propia: la integridad referencial protege automáticamente vía CASCADE.


3. Mapa ER global

erDiagram
    Organization ||--o{ User : "tiene"
    Organization ||--o{ Rack : "es dueña de"
    Organization ||--o{ Blueprint : "es dueña de"
    Organization ||--o{ MonitoringTarget : "monitoriza"
    Organization ||--o{ SignagePlayer : "controla"
    Organization ||--o{ AgentInstance : "tiene agents"
    Organization ||--o{ StoredCredential : "almacena"
    Organization }o--|| Plan : "suscrita a"
    Plan }o--o{ SaaSModule : "incluye"
    Organization }o--o{ SaaSModule : "extra a la carta"
    User ||--o{ LoginLog : "genera"
    User ||--o{ SystemLog : "audita"
    User ||--o{ ImpersonationLog : "impersona"
    User ||--o| ModulePermission : "permisos extra"
    Rack ||--o{ Device : "contiene"
    Rack ||--o{ ConfigBackup : "snapshots"
    Rack ||--o{ BlueprintPlacement : "ubicado en"
    Rack }o--o{ RackGroup : "agrupado en"
    Blueprint ||--o{ BlueprintPlacement : "ubica"
    Blueprint ||--o{ MapAnnotation : "anotaciones"
    Device ||--o{ PortConnection : "conexiones"
    Device }o--|| DeviceProfile : "perfil"
    DeviceProfile }o--|| VendorProfile : "vendor"
    SignagePlayer }o--|| SignageVendorAdapter : "adapter"
    SignagePlayer ||--o{ PlaybackLog : "reproduce"
    Playlist ||--o{ MediaAsset : "contiene"
    Schedule }o--|| Playlist : "programa"
    MonitoringTarget ||--o{ AIInsight : "genera"
    AIInsight }o--o{ IncidentGroup : "agrupado en"
    AIInsight ||--o{ AIInsightAuditLog : "auditoría"
    AIInsight ||--o{ InsightConversation : "Q&A"
    IncidentGroup }o--|| SLAPolicy : "SLA"
    IncidentGroup }o--|| EscalationPolicy : "escalation"

4. Apps y modelos · referencia por archivo

4.1 core/ · multi-tenancy, auth, auditoría (11 modelos)

ModeloPropósitoFKs claveNotas
SaaSModuleRegistry de módulos gated (Rack Editor, Observatory, Signage…)—is_core = siempre activo
PlanTier de suscripción (Starter, Pro, Custom)modules M2M SaaSModule—
OrganizationTenant SaaS (entidad raíz)plan FK Plan, extra_modules M2M SaaSModuleSoft-delete vía deleted_at. Purga programada 90d (Huey purge_deleted_organizations)
UserAbstractUser extendidoorganization FK OrganizationRoles: admin / operator / readonly. UniqueConstraint email (cuando no vacío)
SystemLogAuditoría de accionesuser FK User, organization FK Organization (nullable)Indices (org, -ts), (user, -ts), (level, -ts)
ScriptTemplateTemplates SSH reutilizables (Cisco, Juniper, Aruba, bash, python)organization FK (nullable = global), created_by FK Userunique_together(name, organization)
ImpersonationLogAudit de superuser impersonationadmin FK User, target FK UserSólo superuser
ModulePermissionPermisos granulares per-user (JSONField)user OneToOne User, updated_by FK UserOpt-in layer sobre rol
TemporaryAccessElevación temporal de permisosuser FK User, granted_by FK UserIndex (user, expires_at)
StoredCredentialCredenciales cifradas (SNMP/SSH/HTTP)organization FK Organization, created_by FK Userencrypted_data JSONField con Fernet. unique_together(name, organization)
LoginLogAuditoría de logins (success/fail)user FK User (nullable para fails)Métodos: password, passkey, social, token

4.2 racks/ · Rack Editor (6 modelos)

ModeloPropósitoFKs claveNotas
BoxCategoryCategorías de cajas (Server, Switch, PDU, KVM, etc.)organization FK (nullable = global)unique_together(name, organization)
RackGroupAgrupación lógica (Data Center A, Row 1)organization FK Organizationcolor para UI
StencilTemplates de dispositivos (image, U-height, manufacturer)organization FK (nullable = system stencil)Index (org, category). extra_data JSON con specs
RackServer rack (height_u, location, status)organization FK Organization, parent_rack FK self (children), groups M2M RackGroupSoft-delete deleted_at. is_template para sistema de templates
DeviceDispositivo dentro de rackrack FK Rack, groups M2M RackGroupu_position, u_height. management_config JSON con IP/credentials cifrados Fernet. Status: online/offline/warning/unknown
ConfigBackupSnapshots de running-configdevice FK Device, rack FK Rack, created_by FK Userconfig_hash SHA256 detecta cambios. Tipos: auto / manual / pre_change

4.3 blueprints/ · Map Editor + Auto-Plan AI (4 modelos)

ModeloPropósitoFKs claveNotas
BlueprintMapa infinito (floorplan / topología)organization FK OrganizationSoft-delete. Settings de canvas (scale, dark_mode, cable_curvature)
BlueprintPlacementPosición de un rack en un blueprintblueprint FK Blueprint, rack FK Rackunique_together(blueprint, rack). style_props JSON
MapAnnotationWalls, text, zonesblueprint FK Blueprintdata JSON con coordenadas
AIPromptPrompts Auto-Plan guardadosorganization FK Organizationunique_together(organization, name). is_default

4.4 monitoring/ · Observatory + ITSM + CNS (19 modelos)

Repartido en 3 archivos: models.py (5), models_insight.py (3 · CNS/AI Brain), models_itsm.py (11 · ITSM).

models.py — Network monitoring core:

ModeloPropósitoFKs clave
MonitoringTargetDispositivo o IP a monitorear (SNMP/ping/TCP probe desde v1.63.x/#205/HTTP)organization FK Organization
MetricSampleMuestra individual (histórico corto)target FK MonitoringTarget
MonitoringAlertConfig de alertas (per-device o global)organization FK (nullable = global)
AlertEventHistorial de alertas disparadasalert FK MonitoringAlert
AggregatedMetricMétricas agregadas para queries históricas eficientestarget FK MonitoringTarget

models_insight.py — Network Sentinel AI (CNS):

ModeloPropósitoFKs clave
AIInsightDiagnóstico generado por AI sobre incidente de redorganization FK Organization, target FK MonitoringTarget
AIInsightAuditLogAuditoría de toda acción sobre un insightinsight FK AIInsight
InsightConversationQ&A iterativa sobre un insight (Explain)insight FK AIInsight

models_itsm.py — ITSM operacional:

ModeloPropósitoFKs clave
SLAPolicySLA timers configurables por nivel de riesgoorganization FK Organization
NotificationChannelWebhook / email / Slack / Teamsorganization FK Organization
NotificationLogLog de notificaciones enviadaschannel FK NotificationChannel
EscalationPolicyPolítica multi-nivelorganization FK Organization
EscalationLevelStep individual dentro de policypolicy FK EscalationPolicy
IncidentGroupAgrupa insights con misma root causeorganization FK, sla_policy FK, escalation_policy FK
MaintenanceWindowSuprime creación de insights durante mantenimientoorganization FK Organization
KnownIssueIssue documentado con resoluciónorganization FK Organization
InsightLinkLink manual entre insightsinsight_a FK AIInsight, insight_b FK AIInsight
RunbookProcedimiento de remediación reusableorganization FK Organization
RecurringPatternPatrón recurrente detectado proactivamenteorganization FK Organization

4.5 network/ · Vendor intelligence + Auto-Provision (4 modelos)

ModeloPropósitoFKs claveNotas
VendorProfileCatálogo global de vendors (Cisco, Juniper, Aruba, Xirrus…)— (scope global)Contiene SNMP communities, sysDescr patterns, MIB modules, deep discovery OIDs, monitoring OIDs, fast_poll OIDs, comandos SSH, fingerprinting HTTP. Extensible vía admin/API
CustomMibMIB ASN.1 subido por tenantorganization FK OrganizationCompilado a pysnmp con pysmi-lextudio
DeviceProfilePerfil de dispositivo descubiertoorganization FK Organization, vendor FK VendorProfileOIDs/credenciales aprendidas via auto-provision
PortConnectionConexión cableada entre puertos de devicesorganization FK Organization, device_a FK Device, device_b FK DeviceTipo de cable, longitud, label

4.6 terminal/ · SSH + Local Agent (2 modelos)

ModeloPropósitoFKs claveNotas
ScriptScript SSH guardadoorganization FK Organization, created_by FK Usercommands JSONField (lista). is_safe flag. Lenguaje: cisco_ios, junos, aruba, bash, python
AgentInstanceInstalación de Local Agent (Sentinel failover)organization FK OrganizationRoles: primary / secondary. Solo el primary corre Sentinel monitoring. agent_id UNIQUE indexed

4.7 signage/ · Digital Signage CMS (9 modelos)

ModeloPropósitoFKs claveNotas
SignageVendorAdapterAdapter vendor-specific (Samsung MDC, LG, PJLink, Crestron, Philips SICP)vendor_profile FK VendorProfileScope global. Tipos: json_rpc, rest_api, snmp_only
SignagePlayerPlayer físico de signageorganization FK Organization, adapter FK SignageVendorAdapterStatus: online/offline/error/maintenance
SignageOperationOperación enviada a un player (push content, reboot, etc.)player FK SignagePlayerAudit trail
MediaAssetImagen/vídeo subidoorganization FK OrganizationStorage R2
PlaylistLista ordenada de assetsorganization FK OrganizationItems M2M MediaAsset con orden
ScheduleProgramación temporal de playlistsorganization FK Organization, playlist FK PlaylistCron-like
ClientProjectProyecto de cliente (cartelería)organization FK OrganizationAgrupa playlists + share links
PlaybackLogProof-of-play (qué reprodujo cada player y cuándo)player FK SignagePlayer, asset FK MediaAssetCompliance reporting
ContentDeploymentDespliegue de contenido a un grupo de playersplaylist FK Playlist, players M2M SignagePlayerEstado de deployment

5. Índices clave (no exhaustivo)

TablaÍndiceRazón
racks_rack(organization, deleted_at)Filtro tenant + trash
racks_device(rack, u_position)Render del rack por posición
racks_stencil(organization, category)Picker de stencils
blueprints_blueprint(organization, deleted_at)Lista + trash
core_systemlog(organization, -timestamp), (user, -timestamp), (level, -timestamp)Filtros típicos del Activity
core_loginlog(user, -timestamp), (success, -timestamp), (ip_address, -timestamp), (username_attempted, -timestamp)Seguridad / detección brute-force
core_temporaryaccess(user, expires_at)Lookup permisos vigentes
terminal_agentinstance(organization, role), (organization, status)Sentinel failover lookup
core_storedcredentialunique_together(name, organization)Evitar duplicados nombre
racks_configbackup(device, -created_at), (rack, -created_at)History view

6. Migrations y mantenimiento

6.1 Crear migrations

docker compose exec web python manage.py makemigrations
docker compose exec web python manage.py migrate

Footgun: las migraciones numéricas Django son inmutables una vez aplicadas en PROD. Si haces edit in-place y vuelves a migrate, no se re-ejecuta (Django ya tiene la row en django_migrations). Crear nueva migration siempre.

Nota operativa: Dokploy auto-aplica migraciones en cada deploy (STAGE y PROD migran al mergear — memoria reference_dokploy_auto_migrate).

6.2 Backup pg_dump diario (PROD)

Cron en server crearack-prod:

# /etc/cron.d/pg_backup
0 3 * * * root /opt/crearack/scripts/pg_dump_daily.sh

Output a /opt/pg-backups/crearack-YYYY-MM-DD.dump. Retención 7 días local + sync semanal a DR backup workspace. Además: WAL archiving continuo + pg_basebackup semanal (dom 04:00) a Object Storage — cadena PITR verificada E2E (21-07-2026).

6.3 Backup per-tenant (Huey)

# Huey task agendada
@huey.periodic_task(crontab(hour=3, minute=30))
def run_org_backups():
    ...

Output: MEDIA_ROOT/backups/{org_id}/backup_YYYY-MM-DD.zip con backup_data.json (14 entity types) + media/uploads/. Retención: Organization.backup_retention_days (default plan: Starter 7d, Pro 30d).

6.4 Integridad cross-tenant (Huey)

@huey.periodic_task(crontab(hour=4, minute=0))
def check_tenant_integrity():
    ...

Detecta anomalías tipo Device.org_id != Rack.org_id y registra en SystemLog.


7. Operaciones comunes (psql)

Verificar RLS activo en una tabla

SELECT relname, relrowsecurity, relforcerowsecurity
FROM pg_class WHERE relname = 'racks_rack';

SELECT * FROM pg_policies WHERE tablename = 'racks_rack';

Listar tablas con RLS habilitado

SELECT tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public' AND rowsecurity = true;

Probar el aislamiento RLS de verdad (post-s215)

-- Como crearack_app, fijar un org REAL (el default de rol '0' es bypass y engaña):
SET app.current_org_id = '1';       -- debe devolver solo filas del org 1
SELECT count(*) FROM racks_rack;
SET app.current_org_id = '999999';  -- debe devolver 0 filas
SELECT count(*) FROM racks_rack;

Matar conexiones idle (emergencia)

SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle' AND pid != pg_backend_pid();

Detectar integridad cross-tenant manualmente

SELECT d.id, d.name, d.organization_id AS device_org, r.organization_id AS rack_org
FROM racks_device d
JOIN racks_rack r ON d.rack_id = r.id
WHERE d.organization_id != r.organization_id;

8. Footguns documentados

FootgunSíntomaFix
CONN_MAX_AGE != 0Saturación conexiones BD bajo cargaMantener CONN_MAX_AGE = 0 con Daphne ASGI (revertido 4 veces)
Tuple returns Ninja (status, body)Errores silenciosos en endpointsValidar siempre el tuple, no asumir 200
Migrations numéricasEdit in-place no re-ejecutaNueva migration siempre
dani_readonly bypasea RLSVes datos de TODOS los tenants en psqlIntencional, pero recordarlo
GUC RLS jamás fijado = NULL0 filas (parece BD vacía, no es bypass)Fijar app.current_org_id a un org real para probar aislamiento (s215/s224)
Stencils system + tenantQuery falla si solo filtras por orgUsar `Q(organization=org)

9. Acceso operativo

CómoComando
Shell Djangodocker compose exec web python manage.py shell
psql directo (vía NetBird — proxy socat :5432 sobre wt0)psql postgres://USER:PASS@100.96.156.31:5432/crearack
Ver contenedor BDssh root@100.96.156.31 "docker logs crearack-pro-zcmvsl-db-1 --tail 50" (SSH solo-NetBird desde s222)
Tamaño tablasSELECT relname, pg_size_pretty(pg_total_relation_size(oid)) FROM pg_class WHERE relkind='r' ORDER BY pg_total_relation_size(oid) DESC LIMIT 20;

Véase también

  • [[crearack-tech—bd—coloquial]] — Versión accesible para entrada al proyecto
  • [[workspace-tech—bd—tecnico]] — BD del workspace (Cloudflare D1)
  • [[crearack-tech—agents—dev-core]] — Briefing técnico del módulo core
  • Documentation/guides/DATABASE_ADMIN_GUIDE.md — Manual operativo completo
  • Documentation/guides/SECURITY_GUIDE.md — Multi-tenancy security detail
  • Documentation/guides/CAPACITY_PLANNING.md — Plan de escalado (>200 tenants → read-replica)