Files
amayo/README/MIGRACION_CACHE_APPWRITE.md

6.2 KiB

Migración de Caché: Redis → Appwrite

Problema

Redis con 30MB de memoria ya estaba usando 2.4MB (8%) solo para el caché de configuración de guilds. Con el crecimiento del bot, esto podría saturar rápidamente la instancia de Redis.

Solución: Usar Appwrite Database como Caché

Appwrite Database ofrece:

  • Más espacio: Sin límites estrictos de 30MB
  • Persistencia: Los datos sobreviven reinicios
  • Gratuito: Ya lo tienes configurado en el proyecto
  • Consultas avanzadas: Permite búsquedas y filtros complejos

Configuración en Appwrite

1. Crear la Colección

En tu consola de Appwrite (console.appwrite.io o tu instancia):

  1. Ve a Databases → Selecciona tu database
  2. Crea una nueva colección llamada guild_cache
  3. Configura los siguientes atributos:
Atributo Tipo Tamaño Requerido Único Default
guildId String 32 -
name String 100 No -
prefix String 10 No No null
expiresAt DateTime - No -
  1. Crea un Índice en expiresAt (tipo: Key, ascendente) para optimizar las búsquedas de limpieza

2. Configurar Permisos

En la colección, ve a SettingsPermissions:

  • Create: API Key
  • Read: API Key
  • Update: API Key
  • Delete: API Key

3. Variables de Entorno

Agrega a tu .env:

# Appwrite
APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
APPWRITE_PROJECT_ID=tu_project_id
APPWRITE_API_KEY=tu_api_key
APPWRITE_DATABASE_ID=tu_database_id
APPWRITE_COLLECTION_GUILD_CACHE_ID=guild_cache_collection_id

Para obtener el APPWRITE_COLLECTION_GUILD_CACHE_ID:

  1. En la consola de Appwrite, abre la colección guild_cache
  2. Copia el Collection ID que aparece en la parte superior

Cambios Implementados

Archivos Modificados

  1. src/core/api/appwrite.ts

    • Agregado: APPWRITE_COLLECTION_GUILD_CACHE_ID
    • Nueva función: isGuildCacheConfigured()
  2. src/core/database/guildCache.ts (REESCRITO COMPLETAMENTE)

    • Migrado de Redis a Appwrite Database
    • getGuildConfig(): Lee desde Appwrite, valida expiración
    • invalidateGuildCache(): Elimina documento de Appwrite
    • updateGuildCache(): Actualiza o crea documento
    • cleanExpiredGuildCache(): Limpia documentos expirados (nueva función)
  3. src/main.ts

    • Agregado job de limpieza cada 10 minutos
    • Importa cleanExpiredGuildCache()
  4. .env.example

    • Documentadas todas las variables de Appwrite

Funciones

getGuildConfig(guildId, guildName, prisma)

// 1. Intenta leer desde Appwrite
// 2. Verifica si expiró (expiresAt < now)
// 3. Si expiró o no existe, hace upsert en PostgreSQL
// 4. Guarda en Appwrite con TTL de 5 minutos
// 5. Retorna la configuración

invalidateGuildCache(guildId)

// Elimina el documento de Appwrite
// Se llama cuando se actualiza: prefix, staff, AI role prompt

cleanExpiredGuildCache()

// Busca documentos con expiresAt < now
// Elimina hasta 100 documentos expirados por ejecución
// Se ejecuta cada 10 minutos automáticamente

Comparación: Redis vs Appwrite

Característica Redis (Antes) Appwrite (Ahora)
Memoria 30MB límite ~Ilimitado
Persistencia Volátil (se pierde al reiniciar) Persistente
TTL Automático (SETEX) Manual (verificación en lectura)
Costo Limitado por plan Incluido en plan gratis
Queries Básicas (key-value) Avanzadas (filtros, búsquedas)
Latencia ~1-5ms ~50-100ms

Nota sobre Latencia

Appwrite es ligeramente más lento que Redis (~50-100ms vs ~1-5ms), pero:

  • Solo se consulta cada 5 minutos por guild
  • El 99% de las consultas vienen de caché
  • La diferencia es imperceptible para el usuario final

Testing

1. Verificar que funciona

Busca en los logs:

✅ Guild config obtenida desde caché (Appwrite)
✅ Guild config guardada en caché (Appwrite)
🗑️  Caché de guild invalidada (Appwrite)
🧹 Documentos expirados eliminados de caché

2. Verificar en Appwrite Console

  1. Ve a tu colección guild_cache
  2. Deberías ver documentos con:
    • guildId: ID del servidor
    • name: Nombre del servidor
    • prefix: Prefix configurado (o vacío)
    • expiresAt: Fecha de expiración (5 minutos en el futuro)

3. Probar cambio de prefix

  1. Ejecuta !settings en Discord
  2. Cambia el prefix
  3. Verifica en los logs: 🗑️ Caché de guild invalidada
  4. El próximo comando debería usar el nuevo prefix inmediatamente

Uso de Memoria

Estimación por Guild

Cada documento en Appwrite ocupa aproximadamente:

guildId:    20 bytes
name:       50 bytes (promedio)
prefix:     3 bytes
expiresAt:  8 bytes
Metadata:   ~50 bytes (Appwrite overhead)
───────────────────────
TOTAL:      ~131 bytes

Para 1,000 guilds = ~128 KB (mucho menos que Redis)

Redis Liberado

Al migrar el caché de guilds a Appwrite:

  • Antes: ~2.4 MB en Redis
  • Después: ~0 MB en Redis (solo para otras cosas)
  • Ahorro: ~8% de la memoria de Redis

Rollback (si algo falla)

Si necesitas volver a Redis:

  1. Restaura el archivo anterior:
git checkout HEAD~1 src/core/database/guildCache.ts
  1. Comenta las líneas en main.ts:
// import { cleanExpiredGuildCache } from "./core/database/guildCache";
// setInterval(async () => { ... }, 10 * 60 * 1000);
  1. Redeploy

Próximos Pasos (Opcional)

Si quieres optimizar aún más:

  1. Migrar otros cachés a Appwrite:

    • Cooldowns de usuarios
    • Stats frecuentes
    • Inventarios activos
  2. Implementar caché híbrido:

    • Memoria local (LRU) para guilds muy activos
    • Appwrite para persistencia
  3. Agregar métricas:

    • Cache hit rate
    • Latencia promedio
    • Documentos expirados/hora

Fecha: 2025-10-07
Cambio: Migración de Redis → Appwrite para caché de guilds
Razón: Ahorrar memoria en Redis (30MB limitados)
Estado: COMPLETADO