Volver a la wiki

Valkey Persistence Patterns - CreaRack Pro

Valkey Persistence Patterns - CreaRack Pro

Fecha: 06-02-2026 Versión: v6.5.12 Estado: En uso (producción)


1. Contexto

CreaRack Pro usa Valkey (fork open-source de Redis, licencia BSD-3) como cache server. Además del caching tradicional (TTL cortos para MetricsReader), Valkey se utiliza como almacén de configuraciones de usuario que necesitan:


2. Casos de Uso Actuales

2.1 Overview Layout (GridStack)

Estado a 15-09-2026 (v1.133.0 / v1.134.0) y 16-09-2026 (v1.135.0): este caso ya NO vive en Valkey ni en localStorage. El layout de los cuatro dashboards (Observatory, Wireless, UPS, Signage) se guarda en PostgreSQL vía core/services/ui_prefs.py (OrgUIPreference, con Valkey solo como caché L1 write-through) y por organización Y usuario (user_id). El navegador no guarda nada: el modo Customize (static/js/pages/monitoring/DashboardCustomize.js) hace un único POST al pulsar Done; los widgets ocultos van en la misma lista como {id, hidden: true}. Migración: si el servidor no tiene layout y el navegador conserva uno antiguo (*_gridstack_layout_v*), se sube una vez y se borra la clave. Desde el 16-09-2026 (v1.135.0, PR #548) los cuatro leen igual, vía el helper monitoring/api/_dashboard_layout.py (read_layout): layout del usuario → diseño por defecto de la organización (la fila de OrgUIPreference SIN user_id) → vacío (= layout de fábrica del descriptor JS); el GET devuelve además org_default para que Restore default lo aplique sin segunda llamada. Un admin (require_perm(users, admin)) fija ese diseño con Set as organization default → POST .../dashboard-layout/org-default (save_org_default), sin tocar los layouts personales ya guardados. Corrección: hasta v1.135.0 Signage era el único de los cuatro SIN user_id (compartido por toda la organización); su valor antiguo se lee ahora como diseño de la organización. Lo que sigue es el diseño original de feb-2026, mantenido como historia.

AspectoDetalle (histórico, feb-2026)
PropósitoGuardar posición/tamaño de widgets del Observatory Overview
Cache Keyoverview_layout_{org_id}
TTLNone (sin expiración)
EndpointsGET/POST/DELETE /api/monitoring/overview-layout
FallbacklocalStorage (sesión) → Valkey (persistente) → DEFAULT_LAYOUT (código)
FormatoJSON array: [{id, x, y, w, h}, ...]

Flujo de prioridad (lectura) (histórico):

1. localStorage (ediciones de sesión activa)
2. Valkey backend (layout guardado explícitamente por el usuario)
3. DEFAULT_LAYOUT (constante en JS)

Flujo de escritura (histórico):

2.2 Quick Notes

AspectoDetalle
PropósitoBloc de notas del usuario para observaciones de red
Cache Keyoverview_notes_{org_id}
TTLNone (sin expiración)
EndpointsGET/PUT /api/monitoring/overview-notes
FallbackString vacío si no existe
FormatoString plano (texto del textarea)

Flujo:

2.3 MetricsReader Cache (TTL cortos)

AspectoDetalle
PropósitoCache de consultas a VictoriaMetrics
Cache Keymetrics_{tenant_id}_{endpoint}_{params_hash}
TTL5s-30s (adaptativo según rango temporal)
Invalidacióninvalidate_target_cache() tras cada escritura

Este caso es caching tradicional, no persistencia de configuración.


3. Patrón de Implementación

3.1 Backend (Django Ninja)

# Schema para datos de entrada
class MySettingIn(Schema):
    value: dict  # o str, list, etc.

# GET - Leer configuración
@router.get("/my-setting", response={200: dict})
def get_my_setting(request):
    from django.core.cache import cache
    org = get_current_org(request)
    key = f"my_setting_{org.id}"
    data = cache.get(key)
    return {"value": data or default_value}

# PUT/POST - Guardar configuración
@router.put("/my-setting", response={200: dict})
def save_my_setting(request, data: MySettingIn):
    from django.core.cache import cache
    org = get_current_org(request)
    key = f"my_setting_{org.id}"
    cache.set(key, data.value, timeout=None)  # Sin expiración
    return {"status": "saved"}

# DELETE - Resetear a defaults (opcional)
@router.delete("/my-setting", response={204: None})
def delete_my_setting(request):
    from django.core.cache import cache
    org = get_current_org(request)
    key = f"my_setting_{org.id}"
    cache.delete(key)
    return 204, None

3.2 Frontend (JavaScript)

// Leer al inicializar
async function loadSetting() {
    try {
        const data = await window.ApiService.get('/api/path/my-setting');
        applySetting(data.value);
    } catch {
        applySetting(DEFAULT_VALUE);
    }
}

// Guardar con debounce (para inputs frecuentes)
let _saveTimer = null;
function onSettingChange(newValue) {
    clearTimeout(_saveTimer);
    _saveTimer = setTimeout(async () => {
        await window.ApiService.put('/api/path/my-setting', { value: newValue });
    }, 1000);
}

3.3 Convención de Cache Keys

{feature}_{org_id}           → Configuración por organización
{feature}_{org_id}_{user_id} → Configuración por usuario (futuro)

Prefijos reservados:

PrefijoUso
overview_layout_Layout GridStack del Overview
overview_notes_Quick Notes del Overview
metrics_Cache de métricas (TTL corto)

4. Consideraciones Importantes

4.1 Valkey NO es una base de datos

AspectoRecomendadoNo recomendado
Preferencias UILayout, tema, notas-
Cache de queriesMétricas, rankings-
Datos críticos-Usuarios, credenciales, logs
Datos con relaciones-Cualquier FK/M2M
Datos auditables-Cambios que necesiten historial

Regla: Si perder el dato causa un bug funcional → PostgreSQL. Si perder el dato solo causa un “reset a defaults” → Valkey.

4.2 Persistencia de Valkey

Valkey en Docker usa volumen persistente (valkey_data), pero:

4.3 Migración de Widget IDs (Lección Aprendida)

Cuando se renombra un widget (ej: vm → notes), los layouts guardados tienen el ID viejo. Solución aplicada:

  1. Migración en JS: Renombrar IDs al cargar el layout guardado

    layout.forEach(item => { if (item.id === 'vm') item.id = 'notes'; });
  2. Versionado del localStorage key: Incrementar versión (v2 → v3) para que el layout viejo se ignore

    const LAYOUT_STORAGE_KEY = 'observatory_gridstack_layout_v3';
  3. Limpieza de keys viejos: Añadir a la lista de limpieza legacy

    legacyKeys.push('observatory_gridstack_layout_v2');

Consejo: Al cambiar la estructura de un widget GridStack, SIEMPRE incrementar la versión del LAYOUT_STORAGE_KEY.

4.4 Multi-Tenant

Todas las keys incluyen org_id para aislamiento entre organizaciones. Esto es obligatorio en contexto SaaS — un usuario nunca debe ver configuraciones de otra organización.


5. Casos de Uso Futuros

Escenarios donde Valkey sería ideal como almacén de configuración:

5.1 Preferencias de Usuario

5.2 Dashboard Layout

5.3 Configuración de Admin

5.4 Notificaciones / Preferencias de Alertas

5.5 Estado de Onboarding


6. Comparativa: Valkey vs PostgreSQL vs localStorage

CriteriolocalStorageValkeyPostgreSQL
VelocidadInstantánea~1ms~5-20ms
PersistenciaBrowser onlyServer (volumen Docker)Completa (WAL)
Multi-deviceNoSiSi
Multi-tenantNo (browser)Si (key prefix)Si (FK org)
RelacionesNoNoSi
Queries complejasNoNoSi
AuditoríaNoNoSi (triggers)
Coste de perder datosReset visualReset a defaultsInaceptable
Ideal paraSession stateConfig/prefsBusiness data

7. Configuración de Valkey en Docker

# docker-compose.yml
valkey:
  image: valkey/valkey:8.0-alpine
  container_name: crearack_valkey
  volumes:
    - valkey_data:/data
  # Para mayor durabilidad en producción:
  # command: valkey-server --appendonly yes
# config/settings/base.py
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://valkey:6379/0",
    }
}

Mantenido por: Claude (Anthropic) + Equipo CreaRack Última actualización: 16-09-2026 (nota de estado en §2.1: diseño por defecto de la organización, v1.135.0; cuerpo original del 06-02-2026)

Véase también

Subir