Capacity Planning - CreaRack Pro SaaS
Guia de planificacion de capacidad para despliegue en produccion. Incluye analisis de almacenamiento VictoriaMetrics, consumo por tenant y recomendaciones por tier de servidor.
Ultima actualizacion: 06-02-2026 Version: v6.5.8
1. VictoriaMetrics - Metricas y Almacenamiento
1.1 Metricas escritas por dispositivo
Cada dispositivo monitorizado genera 7 time series en VictoriaMetrics:
| Tipo | Metrica | Descripcion |
|---|---|---|
| Ping | ping_latency_ms | RTT en milisegundos |
| Ping | ping_packet_loss_percent | Perdida de paquetes (0-100) |
| Ping | ping_reachable | 1/0 (alcanzable o no) |
| SNMP | snmp_bandwidth_in_mbps | Ancho de banda entrante |
| SNMP | snmp_bandwidth_out_mbps | Ancho de banda saliente |
| HTTP | http_response_time_ms | Tiempo de respuesta HTTP |
| HTTP | http_status_code | Codigo de estado HTTP |
Labels por metrica: tenant_id, target_id, target_ip (aislamiento multi-tenant).
Formato de escritura: JSON newline-delimited via /api/v1/import (fire-and-forget, no bloquea HTTP).
1.2 Fuentes de metricas en VM
VictoriaMetrics almacena datos de 3 fuentes distintas, configuradas en observability/victoriametrics/scrape.yml:
| Job | Series | Intervalo | Proposito |
|---|---|---|---|
victoriametrics (self) | ~1,484 | 60s | Auto-telemetria de VM (Go runtime, cache, merges) |
crearack-web (Django) | ~519 | 60s | Latencias HTTP, request counts por view/method |
crearack-health | ~7 | 30s | Health check basico |
| Monitoreo devices | 7 × N devices | on-demand | Metricas de dispositivos (ping/snmp/http) |
Optimizacion aplicada (06-02-2026): Los intervalos de scrape de VM self y Django se redujeron de 15s/10s a 60s/60s. Esto reduce el overhead de infraestructura de ~2.3 GB a ~512 MB en 180 dias, sin impacto en la operativa.
1.3 Compresion de VictoriaMetrics
VM comprime time series a ~0.5-1.5 bytes por data point (media ~1 byte). Esto es una de sus principales ventajas frente a Prometheus.
Formula de calculo:
Almacenamiento = series × (86400 / intervalo_segundos) × dias × 1 byte
1.4 Almacenamiento por dispositivo
Con refresh de 5s (el mas agresivo del Observatory):
7 metricas × 17,280 writes/dia = ~120,960 puntos/dia × 1 byte = ~120 KB/dia/device
| Dispositivos | 30 dias | 90 dias | 180 dias |
|---|---|---|---|
| 1 | ~3.5 MB | ~10.5 MB | ~21 MB |
| 50 | ~175 MB | ~525 MB | ~1 GB |
| 100 | ~350 MB | ~1 GB | ~2.1 GB |
| 200 | ~700 MB | ~2.1 GB | ~4.2 GB |
| 500 | ~1.7 GB | ~5.2 GB | ~10.5 GB |
1.5 Overhead de infraestructura (fijo por servidor)
Metricas internas de VM + Django + Health (con intervalos optimizados a 60s):
| Fuente | Series | Intervalo | 30 dias | 180 dias |
|---|---|---|---|---|
| VM self | ~1,484 | 60s | ~62 MB | ~375 MB |
| Django | ~519 | 60s | ~22 MB | ~133 MB |
| Health | ~7 | 30s | ~600 KB | ~3.5 MB |
| Total infra | ~85 MB | ~512 MB |
1.6 Retencion configurada
# compose.observability.yml
- "--retentionPeriod=180d"
VM elimina automaticamente datos mas antiguos de 180 dias. El almacenamiento se estabiliza tras los primeros 180 dias.
1.7 Step de consulta (resolucion de graficas)
El MetricsReader._calculate_step() adapta la resolucion de las queries segun el rango temporal:
| Rango | Step | Puntos aprox. | Caso de uso |
|---|---|---|---|
| < 1 hora | 5s | ~720 | Refresh en vivo (5-20s) |
| 1-6 horas | 15s | ~1,440 | Rangos horarios |
| 6-24 horas | 2m | ~720 | Vista diaria |
| 1-3 dias | 5m | ~864-2,160 | Analisis multi-dia |
| 3-7 dias | 15m | ~672-1,344 | Tendencia semanal |
| 7-30 dias | 30m | ~1,440 | Tendencia mensual |
| 30-90 dias | 2h | ~1,080 | Analisis trimestral |
| > 90 dias | 4h | ~1,080 | Tendencia largo plazo |
2. Consumo Total por Tenant
2.1 Desglose por componente
Para un tenant tipico con 100 dispositivos y 180 dias de retencion:
| Componente | Almacenamiento |
|---|---|
| VM metricas monitoreo | ~2.1 GB |
| PostgreSQL (racks, devices, blueprints, configs, backups) | ~200 MB |
| Uploads (logos, stencils, exports) | ~200 MB |
| Total por tenant | ~2.5 GB |
2.2 Overhead fijo del servidor (compartido entre todos los tenants)
| Componente | Tamano |
|---|---|
| Docker images (VM, PostgreSQL, Valkey, Django) | ~4.5 GB |
| Sistema operativo + herramientas | ~5 GB |
| VM metricas infraestructura (180d) | ~512 MB |
| Logs (con rotacion) | ~2 GB |
| Total fijo | ~12 GB |
3. Planificacion por Tier de Servidor (Hetzner)
3.1 Distribucion de RAM por servicio
| Servicio | RAM base | Por tenant activo |
|---|---|---|
| PostgreSQL (shared_buffers) | 4-16 GB | ~5 MB |
| VictoriaMetrics | 4-16 GB | ~2 MB (por 700 series) |
| Valkey (cache sesiones + metricas) | 1-4 GB | ~1 MB |
| Django/Daphne (ASGI workers) | 2-4 GB base | ~10 MB por WebSocket concurrente |
| Sistema operativo | 2 GB | — |
3.2 Capacidad por tier
| RAM servidor | Tenants | Devices totales | Usuarios concurrentes | Disco usado (180d) |
|---|---|---|---|---|
| 64 GB | 200-300 | 20-30K | ~50 | ~750 GB |
| 128 GB | 500-700 | 50-70K | ~120 | ~1.75 TB |
| 256 GB | 1,000-1,500 | 100-150K | ~250 | ~3.75 TB |
Nota: Con 20 TB de disco, incluso el tier mas grande usa menos del 20% del almacenamiento. El disco nunca sera el cuello de botella.
3.3 Cuellos de botella por recurso
| Recurso | Que lo estresa | Cuando se nota |
|---|---|---|
| RAM | Series en VM + conexiones PostgreSQL + WebSockets | Muchos tenants con Observatory abierto |
| CPU | Pings/SNMP/HTTP concurrentes, queries VM | Monitoreo desde servidor (sin Local Agent) |
| Red | WebSocket persistente por agent + scraping | Muchos Local Agents conectados |
| Disco I/O | Escrituras VM + WAL PostgreSQL | Picos de escritura concurrente |
3.4 Factores que alivian carga
- Local Agent: Ejecuta ping/SNMP/HTTP desde el cliente, no desde el servidor. El servidor solo recibe resultados via WebSocket/API (trafico ligero). Esto reduce drasticamente el consumo de CPU y red.
- Valkey cache: TTLs adaptativos (5s-30s) para queries VM repetidas. Refreshes consecutivos del Observatory no golpean a VM.
- Fire-and-forget writes: Las escrituras a VM no bloquean las respuestas HTTP al usuario.
4. Recomendacion de Arranque
Servidor inicial recomendado
Un Hetzner AX42 o similar:
- 64 GB RAM
- 2x NVMe (1-2 TB)
- CPU moderna (Ryzen/Xeon)
Capacidad: 200-300 clientes con 100 devices cada uno.
Escalado futuro
Antes de saltar a un servidor mas grande, separar servicios:
Fase 1 (monolito):
[Servidor unico] → Django + PostgreSQL + VM + Valkey
Fase 2 (separacion DB):
[Servidor App] → Django + Valkey
[Servidor DB] → PostgreSQL + VictoriaMetrics
Fase 3 (microservicios):
[App Server(s)] → Django + Daphne (escalado horizontal)
[DB Server] → PostgreSQL (replicacion read-replica)
[Metrics Server] → VictoriaMetrics cluster mode
[Cache Server] → Valkey Cluster
Cada fase multiplica la capacidad sin reescribir codigo, solo reconfigurando docker-compose y variables de entorno.
5. Arquitectura Multi-Tenant
5.1 Opciones de arquitectura
Opcion A: Stack compartido (arquitectura actual)
┌─────────────────────────────────────────┐
│ 1 Servidor │
│ │
│ ┌─────────┐ ┌─────────┐ ┌────────┐ │
│ │ Django │ │ Postgre │ │ Victor │ │
│ │ Daphne │ │ SQL │ │ Metrics│ │
│ │ (1 inst)│ │(1 inst) │ │(1 inst)│ │
│ └─────────┘ └─────────┘ └────────┘ │
│ ↕ ↕ ↕ │
│ N tenants comparten todo │
│ Aislamiento: organization FK │
│ + tenant_id label (VM) │
└─────────────────────────────────────────┘
| Aspecto | Valoracion |
|---|---|
| A favor | Simple, barato, ya implementado. Una sola actualizacion afecta a todos. Maxima eficiencia de recursos. |
| En contra | Noisy neighbor (un tenant pesado afecta a todos). Un bug podria filtrar datos entre tenants. Migraciones de DB afectan a todos simultaneamente. |
| Ideal para | SaaS joven, <100 tenants, presupuesto ajustado |
Opcion B: Stack compartido + Schema por tenant
┌─────────────────────────────────────────┐
│ 1 Servidor │
│ │
│ ┌─────────┐ ┌─────────────────────┐ │
│ │ Django │ │ PostgreSQL │ │
│ │ Daphne │ │ ├─ schema_tenant1 │ │
│ │ 1 inst) │ │ ├─ schema_tenant2 │ │
│ │ +routing│ │ ├─ schema_tenant3 │ │
│ └─────────┘ │ └─ ...×N │ │
│ ↕ └─────────────────────┘ │
│ ┌─────────┐ ┌────────┐ │
│ │ Valkey │ │ Victor │ │
│ │(1 inst) │ │ Metrics│ │
│ └─────────┘ └────────┘ │
└─────────────────────────────────────────┘
| Aspecto | Valoracion |
|---|---|
| A favor | Aislamiento real a nivel DB. Backup/restore individual por tenant. Posibilidad de migrar un tenant a otro servidor sin tocar los demas. |
| En contra | Requiere django-tenants. Mas conexiones DB (1 pool por schema). Mas complejidad operativa. |
| Ideal para | SaaS medio, clientes enterprise que exigen aislamiento contractual de datos |
Opcion C: Stack completo por tenant
┌──────────────────────────────────────────┐
│ 1 Servidor │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Tenant 1 │ │ Tenant 2 │ │
│ │ Django + DB │ │ Django + DB │ │
│ │ + VM + Valkey │ │ + VM + Valkey │ │
│ │ (port 8001) │ │ (port 8002) │ │
│ └─────────────────┘ └─────────────────┘ │
│ ... × N stacks completos │
│ RAM: N × ~1.2 GB base = overhead masivo │
└──────────────────────────────────────────┘
| Aspecto | Valoracion |
|---|---|
| A favor | Aislamiento total. Cada cliente es independiente. Puedes dar versiones diferentes a cada uno. |
| En contra | N instancias de PostgreSQL + VM + Valkey + Django = desperdicio masivo de RAM. Pesadilla de deployments. Costosisimo. |
| Ideal para | On-premise / enterprise dedicado, no SaaS |
5.2 Arquitectura recomendada
Opcion A reforzada es la correcta para CreaRack Pro porque:
- Ya implementado:
organizationFK en los 12 modelos,tenant_iden VM - 50 tenants es escala pequena-media: PostgreSQL maneja esto sin problemas
- 25,000 devices × 7 metricas = 175,000 series: carga moderada para VM
- Coste: un solo servidor vs N stacks
Estado actual del aislamiento en el codigo:
| Mecanismo | Donde | Cantidad |
|---|---|---|
organization FK en modelos | */models.py | 12 modelos |
get_current_org() en views/APIs | */api.py, */views.py | 210 usos en 18 archivos |
tenant_id label en VM | MetricsWriter/Reader | Todas las metricas |
5.3 Refuerzos recomendados para Opcion A
| Refuerzo | Que hace | Prioridad |
|---|---|---|
| PostgreSQL RLS | Row-Level Security: el propio DB impide que un tenant vea datos de otro, incluso si hay bug en Django | Alta |
| PgBouncer | Connection pooling: evita que muchos tenants concurrentes agoten conexiones | Media |
| Rate limiting per-tenant | Ya existe en middleware, ajustar limites por organizacion | Ya existe |
| Statement timeout | statement_timeout en PostgreSQL para evitar queries destructivas | Media |
| Backup per-tenant | Export JSON ya existe (backup_full), programar via cron | Baja |
5.4 Cuando cambiar de arquitectura
| Senal | Accion |
|---|---|
| >200 tenants | Anadir read-replica PostgreSQL |
| >500 tenants o cliente enterprise exige aislamiento | Migrar a Opcion B (django-tenants) |
| Cliente on-premise con requisitos regulatorios | Opcion C solo para ese cliente |
| Escalado horizontal del app server | Kubernetes + multiples pods Django |
5.5 Costes por arquitectura (Hetzner, 50 tenants × 500 devices)
| Arquitectura | RAM necesaria | Servidores | Coste aprox/mes |
|---|---|---|---|
| A (compartido) | 64 GB | 1 | ~40-60 EUR |
| B (schema/tenant) | 64-128 GB | 1 | ~40-80 EUR |
| C (stack/tenant) | 256+ GB | 2-3 | ~200-400 EUR |
6. Migracion A → B (django-tenants)
6.1 Que es django-tenants
django-tenants usa PostgreSQL schemas para aislar datos por tenant. Cada tenant tiene su propio schema con todas las tablas replicadas. Un schema public compartido aloja datos comunes (planes, dominios, configuracion global).
PostgreSQL
├── public (schema compartido)
│ ├── tenants_tenant ← registro de cada cliente
│ ├── tenants_domain ← dominios/subdominios
│ └── (planes, pricing, etc.)
├── tenant_acme (schema tenant 1)
│ ├── racks_rack
│ ├── racks_device
│ ├── monitoring_monitoringtarget
│ └── ... (todas las tablas del tenant)
├── tenant_globex (schema tenant 2)
│ ├── racks_rack
│ └── ...
└── ...
El middleware de django-tenants lee el subdominio (acme.crearack.pro) y ejecuta SET search_path TO tenant_acme en cada request. Todas las queries van automaticamente al schema correcto sin cambiar codigo.
6.2 Esfuerzo de migracion
La migracion no es una reescritura. El FK organization ya garantiza que los datos estan separados logicamente — solo falta aislarlos fisicamente en schemas.
Cambios necesarios
| Area | Cambio | Esfuerzo |
|---|---|---|
| Dependencia | pip install django-tenants (MIT) | 5 min |
| Settings | DATABASE_ENGINE → django_tenants.postgresql_backend | 10 min |
| Settings | Definir SHARED_APPS y TENANT_APPS | 30 min |
| Modelo Tenant | Crear modelo Tenant + Domain (hereda de TenantMixin) | 1 hora |
| Middleware | Anadir django_tenants.middleware.TenantMainMiddleware | 10 min |
| URLs | Configurar PUBLIC_SCHEMA_URLCONF para paginas publicas | 30 min |
| DNS | Wildcard *.crearack.pro apuntando al servidor | 10 min |
| Migracion de datos | Script para: crear schemas, mover datos por organization | 2-3 horas |
| Tests | Verificar que cada tenant ve solo sus datos | 1-2 horas |
Estimacion total: 1-2 dias de trabajo.
Lo que NO cambia
| Componente | Por que no cambia |
|---|---|
| Modelos | Se mantienen exactamente igual. El FK organization pasa a ser defense-in-depth (redundante pero no estorba) |
| Views/APIs | get_current_org() sigue funcionando. El schema ya filtra, pero el FK doble-valida |
| VictoriaMetrics | tenant_id label ya aísla. Sin cambios |
| Valkey | Solo anadir prefijo de tenant al cache key (1 linea en settings) |
| Frontend | Zero cambios. Las URLs de API son identicas |
| Templates | Zero cambios |
| ObservatoryCharts.js | Zero cambios |
Script de migracion de datos (pseudocodigo)
from django_tenants.utils import schema_context
for org in Organization.objects.all():
# 1. Crear tenant + schema
tenant = Tenant(schema_name=f"tenant_{org.id}", name=org.name)
tenant.save() # django-tenants crea el schema y ejecuta migrate
# 2. Mover datos al nuevo schema
with schema_context(tenant.schema_name):
for rack in Rack.objects.using('default').filter(organization=org):
rack.pk = None # forzar INSERT
rack.save()
# Repetir para Device, Blueprint, MonitoringTarget, etc.
# 3. Crear dominio
Domain(domain=f"{org.slug}.crearack.pro", tenant=tenant, is_primary=True).save()
6.3 Ventajas post-migracion
| Ventaja | Descripcion |
|---|---|
| Backup granular | pg_dump --schema=tenant_acme → backup de un solo cliente |
| Restore individual | Restaurar un tenant sin afectar a los demas |
| Migracion de tenant | Mover un cliente a otro servidor: dump schema + restore |
| Borrado limpio | DROP SCHEMA tenant_acme CASCADE elimina todo el cliente |
| Seguridad | Incluso un SQL injection no puede cruzar schemas |
| Performance | Tablas mas pequenas por schema = indices mas rapidos |
6.4 Consideraciones
| Aspecto | Detalle |
|---|---|
| Conexiones DB | Cada schema usa el mismo pool de conexiones (PgBouncer recomendado a partir de ~100 tenants) |
| Migraciones Django | python manage.py migrate_schemas aplica migraciones a todos los schemas |
| Admin Django | Funciona, pero solo ve datos del tenant del subdominio actual |
| Superadmin | Accede via schema public para gestion cross-tenant |
6.5 Ruta de migracion recomendada
Fase actual (Opcion A):
→ Lanzar SaaS, captar primeros 50-100 clientes
→ Validar producto-mercado
→ organization FK es suficiente
Fase 2 (~100-200 clientes o primer enterprise):
→ Instalar django-tenants
→ Migrar datos existentes a schemas
→ 1-2 dias de trabajo, zero downtime posible
→ El codigo de la app NO cambia
Fase 3 (~500+ clientes):
→ Separar PostgreSQL a servidor dedicado
→ PgBouncer obligatorio
→ Read-replicas para queries pesadas
Conclusion: La migracion A→B es de bajo coste y bajo riesgo porque el codigo actual ya esta preparado (organization FK en los 12 modelos, get_current_org en 210 puntos). Solo se anade la capa de routing de schemas — no hay que reescribir logica de negocio.
7. Migracion entre Servidores
7.1 Que se migra
Todo el stack corre en Docker con volumenes nombrados. Migrar a un servidor mas capaz es copiar datos y levantar contenedores.
| Componente | Como se migra | Tamano tipico |
|---|---|---|
| Codigo Django | git clone en el nuevo servidor | ~50 MB |
| PostgreSQL | pg_dump → pg_restore (o copiar volumen Docker) | 1-10 GB |
| VictoriaMetrics | Copiar volumen Docker via rsync (archivos planos) | 1-15 GB |
| Valkey | No se migra — es cache, se regenera automaticamente | 0 |
| Uploads | Copiar directorio static/uploads/ | Variable |
.env | Copiar manualmente | 1 KB |
7.2 Procedimiento (near-zero downtime)
1. Levantar nuevo servidor Hetzner (30 min)
2. Instalar Docker + git clone del repo (10 min)
3. Copiar .env + static/uploads/ (5 min)
4. pg_dump en origen → pg_restore en destino (10-30 min)
5. Copiar volumen VictoriaMetrics via rsync (10-60 min)
6. docker compose up -d en nuevo servidor (2 min)
7. Verificar que todo funciona (10 min)
8. Cambiar DNS al nuevo IP (propagacion: 5-60 min)
9. Apagar servidor antiguo
Tiempo total: 1-2 horas. Downtime real: solo la propagacion DNS (minutos si se configura TTL bajo previamente).
7.3 Comandos clave
# En servidor ORIGEN — exportar datos
pg_dump -U crearack -h localhost -F c crearack_db > /tmp/db_backup.dump
rsync -avz /var/lib/docker/volumes/victoriametrics_data/ nuevo-server:/tmp/vm_data/
# En servidor DESTINO — importar datos
pg_restore -U crearack -h localhost -d crearack_db /tmp/db_backup.dump
docker volume create victoriametrics_data
rsync -avz /tmp/vm_data/ /var/lib/docker/volumes/victoriametrics_data/_data/
docker compose up -d
7.4 Hetzner: upgrade in-place
Hetzner ofrece upgrade de hardware sin cambiar servidor en algunos planes dedicados (mas RAM, mejor CPU). En ese caso:
- Se solicita via panel de Hetzner
- Downtime: solo el reinicio del servidor (~5 minutos)
- No hay migracion de datos (los discos no cambian)
- Los contenedores Docker arrancan automaticamente (
restart: unless-stopped)
7.5 Estrategia de TTL para migraciones con DNS
Para minimizar downtime durante migraciones entre servidores:
- 1 semana antes: Bajar el TTL del DNS de 3600s (1h) a 60s (1min)
- Dia de migracion: Cambiar IP en DNS. Propagacion en ~1-2 minutos
- Despues de verificar: Subir TTL de vuelta a 3600s
8. Datos Reales de Referencia (06-02-2026)
Medido en entorno de desarrollo con 3 targets activos y ~2 dias de datos:
| Metrica | Valor |
|---|---|
| Almacenamiento VM total | 100.6 MB |
| Series totales | 2,746 |
| Series monitoreo CreaRack | 14 (3 targets × 4-5 metricas) |
| Series infra (VM + Django) | ~2,732 |
| Directorio data VM | 9.2 MB |
Mantenido por: Equipo CreaRack
Véase también
- [[crearack-tech—backend—network-observatory]] — backend del Network Observatory
- [[crearack-tech—architecture—realtime-monitoring-plan]] — plan de monitoring en tiempo real
- [[crearack-tech—architecture—saas-metrics-architecture]] — arquitectura de métricas SaaS
- [[crearack-tech—guides—production-deployment]] — deploy en producción Hetzner
- [[crearack-tech—architecture—valkey-persistence]] — persistencia de Valkey
- [[crearack-tech—reports—infrastructure-robustness-audit-04-04-2026]] — auditoría de robustez infra