BookinglyTech News
Infraestructura

Bloqueos distribuidos en Go: los errores que tu mutex no te perdonará

La diferencia entre un sync.Mutex local y un lock en Redis no es de sintaxis, sino de resolución de conflictos y expiración.

2 min de lecturaDev.to0 vistas

Implementar un lock distribuido en Go parece sencillo hasta que un proceso se cae a medio trabajo. El problema no es la adquisición inicial, sino qué ocurre cuando la conexión se corta, el TTL expira mientras el worker sigue ejecutándose o otro nodo toma el relevo por error. En un entorno con múltiples instancias sin memoria compartida, estos escenarios son la norma, no el caso de extremo.

La trampa del DEL a ciegas

El patrón básico consiste en usar el comando SET de Redis con los modificadores NX (no existe) y PX (expiración), almacenando un token único como valor de identidad. Redis recomienda este enfoque atómico frente al antiguo SETNX, que ya se considera obsoleto para nuevos patrones de bloqueo. En Go, utilizando go-redis, la adquisición es directa, pero la liberación es donde se rompe la lógica si se hace mal.

Si un worker espera más de lo que dura el lease, el lock expira y otro worker puede adquirirlo. Si el primero termina su tarea y ejecuta un DEL simple, borrará el lock del segundo worker. El error clásico es pensar que basta con leer la clave antes de borrarla, pero entre el GET y el DEL puede haber una ventana donde otro proceso tome posesión. La solución exige atomicidad en la verificación y eliminación, algo que en Go se resuelve típicamente con un script Lua que comprueba si el valor coincide con el token del cliente antes de ejecutar el borrado.

Existe además el problema del resultado ambiguo: si el comando SET se ejecuta en Redis pero la respuesta se pierde por una partición de red, el cliente no sabe si el lock existe. Reintentar ciegamente con un nuevo token puede dejar el primer lease retenido hasta su expiración. La recomendación es generar el token antes del comando y, ante la duda, tratar el estado como desconocido, dependiendo de la expiración natural para la recuperación.

Operativamente, esto cambia cómo abordas la concurrencia entre microservicios. No basta con envolver la lógica en un defer Release si no has validado la propiedad atómicamente. Los administradores de sistemas que supervisan clústeres de Redis deben estar atentos a la latencia de los scripts Lua en momentos de alta carga, ya que una pausa prolongada en la liberación puede bloquear flujos críticos como procesadores de facturas o colas de tareas. La diferencia fundamental con un mutex de proceso es que aquí no hay memoria compartida: cada fallo de red o de reloj introduce un estado de incertidumbre que el código debe gestionar explícitamente.

Este es el tipo de detalles que suelen pasar desapercibidos hasta que una caída de red deja un trabajo duplicado o un recurso corrupto en producción.