0008 — La memoria de los bots vive en Postgres
Estado: aceptada · Fecha: 2026-09-19
Contexto
La memoria de cada conversación con un bot vivía en MemorySaver, en la RAM del proceso: el
historial, las variables, por dónde va el flujo y un "Esperar respuesta" pendiente. La
documentación de LangGraph lo presenta como solo para desarrollo, y en la práctica:
- cada deploy o reinicio la borraba, así que todas las conversaciones volvían a empezar desde Inicio y el bot saludaba otra vez;
- no se compartía entre instancias, con lo que dos réplicas del backend no sabrían la una de la otra;
- crecía sin límite en RAM, porque guarda una copia por paso del grafo.
Con la reanudación (ADR 0006) esto pasó a ser grave: un "Esperar respuesta" puede estar pendiente durante horas.
Decisión
-
@langchain/langgraph-checkpoint-postgrescon un único checkpointer compartido (BotCheckpointerService). Los hilos van por<botId>:<conversationId>. -
Esquema propio
langgraph, cerrado (20261020000003_langgraph_checkpoints.sql):- fuera de los esquemas que expone PostgREST;
- sin
USAGEparaanon,authenticatedniservice_role; - RLS como segunda barrera.
Los checkpoints contienen el texto de las conversaciones, que es PHI. El backend entra por conexión directa (
LANGGRAPH_CHECKPOINT_DATABASE_URL). -
Las tablas las crea la migración, no el
setup()del paquete. Así nacen cerradas y revisadas.setup()solo registra sus versiones, porque todas sus sentencias sonIF NOT EXISTS. -
Un checkpoint por turno (
durability: 'exit') en lugar de uno por paso del grafo. -
Poda diaria con
langgraph.prune_checkpoints:- borra las conversaciones inactivas más de 90 días;
- en las vivas deja los últimos 20 checkpoints y los blobs que siguen referenciados;
- no toca hilos con actividad en la última hora, para no chocar con un turno en curso.
Hace falta porque cada turno guarda el historial entero.
-
Si Postgres no está, el bot sigue funcionando. Sin la variable, o si
setup()falla, se usaMemorySavery queda registrado en el log.
Alternativas descartadas
- Redis (
@langchain/langgraph-checkpoint-redis). Exige Redis Stack (RedisJSON y RediSearch) en lugar delredis:7-alpineactual, y persistencia AOF bien configurada para no perder memoria en un reinicio de Redis. Postgres ya tiene copias de seguridad y está en el flujo de migraciones. - El esquema
public, que es el valor por defecto del paquete. Lo expondría PostgREST y dependería de que cadaGRANTestuviera bien. - Recortar el historial en el estado (
RemoveMessage) en lugar de podar checkpoints. Cambia lo que ve el modelo, y la poda de checkpoints ya contiene el crecimiento sin tocar la conversación.
Consecuencias
- Hay que poner
LANGGRAPH_CHECKPOINT_DATABASE_URLen el.envde cada servidor y aplicar antes la migración (ver Despliegue). - La prueba
bot-checkpointer.e2e.spec.tscorre elPostgresSaverreal contra un Postgres desechable (en local, PGlite por socket), conLANGGRAPH_E2E_DATABASE_URL. Comprueba que el saludo no se repite tras un reinicio, que una espera pendiente sobrevive y que podar un hilo vivo no lo rompe.