- Redlock, basado en Redis, apunta a ser un bloqueo distribuido tolerante a fallas, pero carece de seguridad para tareas donde está en juego la corrección, y es demasiado complejo para usarse como optimización de eficiencia
- En los bloqueos distribuidos primero hay que distinguir entre el objetivo de eficiencia, que reduce trabajo duplicado, y el de corrección, que protege estado compartido; el criterio es si una falla implica aumento de costos o corrupción de datos
- Incluso con un servicio de bloqueo perfecto, una pausa larga de GC, una suspensión del proceso o una demora de red pueden hacer que se ejecute una escritura obsoleta después de que expire el lease, por lo que se necesita un fencing token
- Redlock no puede crear un token monótonamente creciente en cada adquisición de bloqueo, y la expiración de claves de Redis depende del reloj del sistema basado en
gettimeofday, por lo que su seguridad puede romperse ante saltos de reloj o demoras - Para bloqueos que requieren corrección hay que usar un sistema de consenso como ZooKeeper junto con verificación de fencing tokens, y limitar los bloqueos con un solo nodo Redis a usos aproximados y no críticos
Punto de partida para revisar Redlock
- Redlock es un algoritmo que implementa un bloqueo distribuido tolerante a fallas, más precisamente un lease, sobre Redis
- Ya existen más de 10 implementaciones independientes, y como no se sabe quién depende de este algoritmo, vale la pena revisarlo públicamente
- Redis en sí encaja bien para compartir entre servidores datos temporales, aproximados y que cambian rápidamente
- Ej.: contadores de solicitudes por dirección IP, conjuntos de IP únicas por ID de usuario
- El punto preocupante es la tendencia a usar Redis cada vez más en áreas de gestión de datos donde se esperan consistencia y durabilidad más fuertes, y los bloqueos distribuidos son una de esas áreas
Objetivo del bloqueo: ¿eficiencia o corrección?
- En una aplicación distribuida, un bloqueo es un mecanismo para que, cuando varios nodos intentan realizar la misma tarea, solo uno la ejecute a la vez
- Las razones para usar bloqueos se dividen en dos grandes categorías
- Eficiencia: es una optimización para no hacer dos veces el mismo cálculo costoso; si falla, como mucho aumentan un poco los costos de AWS o se envía dos veces la misma notificación por email
- Corrección: es un mecanismo para impedir que procesos concurrentes corrompan el mismo estado; si falla, pueden aparecer problemas graves como archivos dañados, pérdida de datos, inconsistencias permanentes o administración incorrecta de medicamentos
- Para bloqueos con objetivo de eficiencia, el costo y la complejidad de Redlock, con 5 servidores Redis y verificación por mayoría, son innecesarios
- Conviene más usar una sola instancia de Redis y, si hace falta, replicación asíncrona
- En ese caso, por un corte de energía o un problema en el nodo Redis, se podrían perder algunos bloqueos, pero si se trata de una optimización no crítica es una falla aceptable
- Redlock, por sus 5 réplicas y mayoría, parece adecuado para bloqueos donde la corrección es importante, pero en la práctica no sirve para ese propósito
Un lease por sí solo no puede proteger recursos de forma segura
- Un bloqueo en sistemas distribuidos es distinto de un mutex en una aplicación multihilo, y es más complejo porque los nodos y la red pueden fallar de manera independiente
- Un flujo típico para actualizar un archivo en almacenamiento compartido es adquirir el bloqueo, leer el archivo, modificarlo, volver a escribirlo y liberar el bloqueo
- El bloqueo busca evitar que dos clientes realicen simultáneamente un read-modify-write y se pierdan actualizaciones
- Si un cliente se queda detenido por mucho tiempo mientras tiene el bloqueo, el lease puede expirar
- El GC puede intervenir y detener al cliente durante mucho tiempo
- Un lease es un buen diseño para evitar que un cliente caído retenga el bloqueo para siempre, pero si el tiempo de pausa supera el tiempo de expiración, el cliente puede ejecutar una escritura peligrosa sin saber que ya expiró
- Este problema no es un caso teórico; HBase tuvo un problema similar en el pasado
- Hubo casos en que las pausas de GC “stop-the-world” duraron varios minutos
- Incluso un GC “concurrente” como CMS de HotSpot JVM a veces debe detener la aplicación
- Comprobar justo antes de escribir si el bloqueo expiró no resuelve el problema
- El GC puede detener el hilo en ejecución en cualquier punto, incluso entre la última comprobación y la operación de escritura
Las pausas de proceso y las demoras de red son amenazas comunes
- Aunque se use un runtime sin pausas largas de GC, un proceso puede detenerse por muchas razones
- Leer una dirección que no está en memoria puede provocar un page fault
- Si el disco es EBS, la lectura de una variable puede convertirse en una solicitud síncrona a través de la red de Amazon
- La contención de CPU, demoras del scheduler o un
SIGSTOPenviado por error también pueden detener un proceso
- Las demoras de red producen el mismo problema
- Una aplicación puede haber enviado una solicitud de escritura, pero el paquete puede demorarse y llegar al servidor de almacenamiento después de que expire el lease
- En una falla de GitHub, los paquetes de red se demoraron cerca de 90 segundos
- Las redes de paquetes como Ethernet e IP pueden demorar paquetes de forma arbitraria, y eso ocurre en la práctica
- Por eso, incluso en una red bien administrada, no se pueden asumir tiempos; el código simple basado en leases es fundamentalmente inseguro sin importar qué servicio de bloqueo se use
Hay que bloquear escrituras obsoletas con fencing tokens
- La solución es incluir un fencing token en todas las solicitudes de escritura al almacenamiento
- Un fencing token es un número que aumenta cada vez que un cliente adquiere el bloqueo
- Ej.: el cliente 1 obtiene un lease con el token 33, luego se detiene durante mucho tiempo y el lease expira
- El cliente 2 obtiene un nuevo lease con el token 34 y envía una solicitud de escritura al almacenamiento
- Más tarde, el cliente 1 despierta y envía una escritura con el token 33; el almacenamiento rechaza la solicitud con token 33 porque ya procesó un token más alto, el 34
- Para que sea seguro, el servidor de almacenamiento debe verificar activamente el token y rechazar escrituras cuyo valor de token vaya hacia atrás
- Si el servicio de bloqueo genera tokens estrictamente monótonos crecientes, el bloqueo puede hacerse seguro
- Si se usa ZooKeeper como servicio de bloqueo, se puede usar el
zxido el número de versión del znode como fencing token
- Si se usa ZooKeeper como servicio de bloqueo, se puede usar el
- El gran problema de Redlock es que no tiene capacidad de generar fencing tokens
- El valor aleatorio único de Redlock no proporciona la monotonicidad necesaria
- Un contador de un solo nodo Redis no alcanza, porque ese nodo puede fallar
- Los contadores de varios nodos pueden desincronizarse entre sí
- Es probable que también se necesite un algoritmo de consenso para generar fencing tokens
Redlock basa su seguridad en supuestos de tiempo
- En algoritmos distribuidos, el modelo práctico es el modelo asíncrono con detectores de fallas no confiables
- Los procesos pueden detenerse por una duración arbitraria
- Los paquetes pueden demorarse arbitrariamente en la red
- Los relojes pueden desviarse arbitrariamente
- Aun así, el algoritmo debe tomar decisiones correctas
- Los relojes solo pueden usarse para generar timeouts y no esperar para siempre cuando un nodo cae
- Los timeouts no necesitan ser exactos, y que una solicitud agote el timeout no significa necesariamente que el otro nodo esté caído
- También puede deberse a una demora de red o a un error del reloj local
- Redis usa
gettimeofday, no un monotonic clock, para decidir la expiración de clavesgettimeofdaypermite que la hora del sistema salte de forma discontinua- Si NTP ajusta el reloj o un administrador cambia manualmente la hora, las claves de Redis pueden expirar mucho antes o mucho después de lo esperado
- Los algoritmos del modelo asíncrono normalmente mantienen la seguridad sin supuestos de timing, y los detectores de fallas como los timeouts solo afectan la vivacidad
- Si el timing es desastroso, el rendimiento puede empeorar, pero no debería tomar decisiones incorrectas
- Redlock, en cambio, hace que la seguridad dependa de varios supuestos de timing
- Todos los nodos Redis deben conservar la clave durante un tiempo aproximadamente correcto
- Las demoras de red deben ser lo bastante menores que el tiempo de expiración
- Las pausas de proceso deben ser mucho más cortas que el tiempo de expiración
Casos en los que Redlock se rompe con mal timing
- Con 5 nodos Redis A, B, C, D, E y los clientes 1 y 2, si el reloj de un nodo salta hacia adelante, ambos clientes pueden creer que tienen el bloqueo
- El cliente 1 obtiene el bloqueo en A, B y C, y por un problema de red no llega a D ni E
- El reloj de C salta hacia adelante y el bloqueo expira
- El cliente 2 obtiene el bloqueo en C, D y E, y por un problema de red no llega a A ni B
- Como resultado, los clientes 1 y 2 concluyen que ambos tienen el bloqueo
- Si C cae y se reinicia de inmediato antes de persistir el bloqueo en disco, puede ocurrir un problema similar
- La documentación de Redlock recomienda retrasar el reinicio de un nodo caído por al menos el TTL de bloqueo más largo
- Este retraso de reinicio también depende de una medición del tiempo razonablemente exacta, y puede fallar si el reloj salta
- Una pausa del proceso cliente también puede romper Redlock
- El cliente 1 solicita el bloqueo a A, B, C, D y E
- Mientras las respuestas están en tránsito, el cliente 1 entra en un GC stop-the-world
- Los bloqueos de todos los nodos Redis expiran
- El cliente 2 obtiene el bloqueo en A, B, C, D y E
- El cliente 1 termina el GC y recibe las respuestas exitosas que estaban en el buffer de red del kernel
- Ambos clientes creen tener el bloqueo
- Que Redis esté escrito en C y no tenga GC no ayuda
- El problema ocurre en sistemas donde el cliente puede sufrir pausas de GC
- Para que sea seguro, hay que impedir el trabajo del cliente 1 después de que el cliente 2 obtiene el bloqueo, con un mecanismo como fencing tokens
- Una demora larga de red puede tener el mismo efecto que una pausa de proceso
- Si el TCP user timeout se configura mucho más corto que el TTL de Redis, es posible que se ignoren paquetes demorados, pero habría que revisar la implementación TCP concreta para estar seguro
- Incluso en ese caso se vuelve al problema de la precisión en la medición del tiempo
Redlock requiere supuestos de sistema síncrono
- Redlock solo funciona correctamente en un modelo de sistema síncrono con estas propiedades
- Hay un límite superior garantizado para las demoras de red
- El tiempo de pausa de los procesos está limitado
- El error de reloj está limitado
- Un modelo síncrono no significa que los relojes estén exactamente sincronizados, sino que existen límites superiores fijos y conocidos para las demoras de red, las pausas y el drift de reloj
- Redlock asume que las demoras, pausas y drift son todos pequeños en comparación con el TTL del bloqueo
- Si los problemas de timing alcanzan el tamaño del TTL, el algoritmo falla
- En un entorno típico de data center, estos supuestos de timing pueden cumplirse la mayor parte del tiempo; a eso se le llama sistema parcialmente síncrono
- Si la corrección depende del bloqueo, “la mayor parte del tiempo” no basta
- En el momento en que se rompen los supuestos de timing, Redlock puede violar la seguridad, por ejemplo otorgando un lease a otro cliente antes de que expire el lease de uno
- El caso de GitHub con paquetes demorados 90 segundos es una prueba de que en entornos reales es difícil asumir un modelo de sistema síncrono
- Raft, Viewstamped Replication, Zab y Paxos pertenecen a la categoría de algoritmos de consenso diseñados para modelos de sistema parcialmente síncrono o modelos asíncronos con detectores de fallas
- Estos algoritmos deben abandonar supuestos de timing, y hay que tener cuidado de no asumir que la red, los procesos y los relojes de un sistema distribuido son más confiables de lo que son en realidad
Conclusión y opciones recomendadas
- Redlock es innecesariamente pesado y costoso para bloqueos de optimización de eficiencia, y no es lo suficientemente seguro para bloqueos donde está en juego la corrección
- En particular, en la práctica asume un sistema síncrono con límites superiores para demoras de red y tiempo de ejecución de operaciones, y si ese supuesto se rompe puede violar la seguridad
- Tampoco tiene capacidad de generar fencing tokens para proteger el sistema de demoras largas de red o procesos detenidos
- Si se necesita un bloqueo de optimización de eficiencia basado en best effort, conviene usar el algoritmo de bloqueo de un solo nodo de Redis
- Adquirir el bloqueo con un set-if-not-exists condicional
- Liberar el bloqueo eliminándolo de forma atómica solo si el valor coincide
- El código debe documentar claramente que el bloqueo es aproximado y que puede fallar de vez en cuando
- No hace falta configurar un clúster de 5 nodos Redis
- Para bloqueos que requieren corrección, no hay que usar Redlock; se debe usar un sistema de consenso como ZooKeeper
- Si es posible, se pueden usar las recetas de Curator que implementan bloqueos
- Como mínimo, se puede usar una base de datos como PostgreSQL que ofrezca garantías transaccionales razonables
- Se debe exigir la verificación de fencing tokens en todos los accesos a recursos bajo el bloqueo
- Redis es una herramienta útil cuando se usa para los fines previstos, y toda herramienta tiene límites, así que hay que conocerlos y planificar en función de ellos
- En la actualización del 9 de febrero de 2016, Salvatore, autor original de Redlock, publicó una refutación, pero la conclusión se mantiene
1 comentarios
Opiniones de Hacker News
En el trabajo usamos Temporal e implementamos un bloqueo distribuido con workflows y signals dedicados.
Hasta ahora funciona bien, y al dejarle a las funciones de Temporal la parte distribuida del lock, la implementación también es bastante simple.
Me pregunto si Temporal es único en este campo o si hay alternativas de nivel similar.
Suena bastante probado en producción, dado que se separó de Uber y lo usan proveedores importantes.
Para locks distribuidos normalmente uso PostgreSQL advisory lock.
Aunque el trabajo no tenga relación con la base de datos, si inicias una transacción y tomas un advisory lock, el lock se mantiene hasta que la app lo libera explícitamente o hasta que la transacción termina, por ejemplo por un crash.
Hasta ahora me parecía bastante seguro, pero acabo de darme cuenta de que nunca verifiqué si la conexión a la base de datos seguía en buen estado.
Si el trabajo está relacionado con la base de datos, la consulta fallaría y el trabajo también fallaría, pero si no, podrías no darte cuenta de que ya perdiste el lock.
Si quieres exactitud absoluta sin fencing tokens ni operaciones atómicas, supongo que al final necesitas commit en dos fases para todo.
Probablemente, para hacer correctamente lo que querías, deberías usar “EXCLUSIVE” o “ACCESS EXCLUSIVE”, o asegurar commit en dos fases o idempotencia para el trabajo.
[0] https://www.postgresql.org/docs/current/explicit-locking.htm...
La mayoría de las bibliotecas suelen usar pools de conexiones, así que hay que reservar una conexión dedicada para el lock y asegurarse de que las verificaciones periódicas del lock también se hagan necesariamente con esa misma conexión.
Sería bueno leer el comentario que dejé hace tiempo en los comentarios de ese blog y la respuesta que escribí en mi propio blog.
En orden aleatorio: el autor se perdió los puntos clave sobre cómo funciona el algoritmo y luego rechazó el algoritmo basándose en argumentos restantes más débiles.
Tampoco es cierto que en las computadoras y APIs modernas sea imposible esperar aproximadamente el tiempo correcto. Las pausas de GC son bounded y los relojes monotónicos funcionan, así que es una suposición aceptable.
Una cosa es criticar el mecanismo de liberación automática en sí porque expone una posible condición de carrera, y otra es criticarlo dentro de los objetivos del algoritmo y su modelo de sistema.
Redlock se ha usado con éxito durante años en muchos casos de uso, y si configuras los timeouts mucho más grandes que el tiempo de finalización del trabajo y que cualquier pausa arbitraria que pueda ocurrir en un sistema operativo normal, es muy difícil provocar una condición de carrera.
Por supuesto, si pones un timeout de liberación automática demasiado pequeño y el trabajo puede tardar fácilmente ese tiempo, es un error de diseño, pero no es un problema de Redlock en sí.
¿Usarías RedLock en una situación donde el timeout es bastante corto (por ejemplo, 1–2 segundos), el trabajo normalmente usa alrededor del 90% de ese timeout, y la tarea que se realiza mientras se tiene el lock de RedLock nunca debe ejecutarse al mismo tiempo que la de otro holder del lock?
Creo que la respuesta correcta aquí siempre es “no”. El riesgo de que el lease expire antes de que el cliente termine el trabajo es muy alto.
Como RedLock no puede garantizar exclusión mutua en todas las situaciones, hay que hacer que el trabajo sea idempotente, y este tipo de caso conviene implementarlo con bloqueo optimista.
Estoy reforzando de nuevo mis conocimientos de bajo nivel y de algoritmos. ¿Qué buen libro hay sobre este tema? Tengo el libro escrito por el autor.
Quiero construir algo por diversión, pero los materiales que encuentro son o de nivel juguete o demasiado complejos.
Elige un tema e impleméntalo de verdad.
Hace tiempo escribí un post de blog sobre locks distribuidos basándome en este material: https://medium.com/sahibinden-technology/an-easy-integration...
La explicación de que “los locks tienen timeout (es decir, son leases)” me suena rara.
Primero, si el cliente crashea, incluso sin un timed lease, el OS o el supervisor debería liberar el lock; y si ambos mueren, la conexión finalmente se corta, y el sistema de red debería detectarlo mediante reset, timeout, ausencia de heartbeat, etc., invalidar la conexión y luego liberar el lock.
Segundo, si el problema es que el cliente, por un bug, no crashea y mantiene el lock durante demasiado tiempo, ¿no debería algún supervisor detectarlo y matar al cliente antes de liberar el lock para los demás?
Tercero, si se ponen locks con timeout para manejar estos corner cases, ¿no habría que avisarle al programa real mediante excepciones, señales, terminación o algo parecido? ¿Y no habría que esperar a verificar que el programa recibió la notificación antes de liberar el lock?
La idea misma de permitir que el programa siga ejecutando su flujo de control normal aunque haya ocurrido un timeout parece la raíz del problema, pero no entiendo por qué todos lo pasan por alto. ¿Me estoy perdiendo alguna razón obvia?
Quien invalida el lock de su lado es el servicio de almacenamiento, y sin garantías adicionales que Redlock no ofrece, el cliente no puede detectar por sí mismo su propio problema.
En algunos casos esos paquetes se descartan, y puede quedar una conexión abierta en el servidor aunque el cliente de la máquina remota ya haya muerto.
Por cierto, no fui yo quien votó negativo.
Implementé un bloqueo distribuido con Deno KV, alojado por Deno y Deno Deploy.
Internamente usa FoundationDB, una base de datos distribuida, y las instancias de Deno que corren en dispositivos locales se conectan al mismo Deno KV para adquirir el bloqueo.
Con PostgreSQL también funciona usando
SELECT FOR UPDATE, pero la base de datos en sí no es distribuida.En 2018 evaluamos Redis para nuestro caso de uso, pero al final elegimos una solución menos llamativa y realmente nunca falló ni una vez.
El caso de uso era repartir, uno por uno, tickets con identificador de un conjunto finito de tickets de una campaña, parecido a cómo Ticketmaster asigna asientos en un recinto.
Cuando llegaba una solicitud, había que entregar un ticket disponible, adjuntar los metadatos de la solicitud al ticket asignado y luego excluirlo de solicitudes posteriores.
La exactitud era clave porque en el pasado había habido campañas fallidas con sobreasignación, subasignación y asignaciones duplicadas.
También probamos una implementación simple con Redis para adquirir el bloqueo, verificarlo, realizar el trabajo y liberar el bloqueo, pero en ese momento nos implicaba demasiada carga operativa, y fue una suerte no haber seguido ese camino.
La elección final fue Postgres. Nuestro “bloqueo distribuido” era más bien una sentencia
UPDATEcompuesta que usaba funciones propias de Postgres, convirtiendo la solicitud en una especie de operación de conjuntos para que la base de datos devolviera un registro de éxito o una marca de fallo. Ganaron las transacciones ACID.Después de resolver la exactitud, miramos la escala y el rendimiento; no necesitábamos millones de solicitudes por segundo, pero sí teníamos criterios para picos repentinos.
Optimizamos las instancias de base de datos de lectura y escritura dentro del clúster, ubicamos estratégicamente las campañas más grandes o de mayor demanda en sistemas designados, y seguimos optimizando durante 2 años, pero no hubo ni una sola campaña fallida de distribución de tickets.
No soy experto en tecnologías de bloqueo distribuido; solo me enfoqué en el problema que había que resolver, probé algunas cosas y encontré la solución adecuada.
Como una transacción
UPDATEdura apenas unos microsegundos, se puede centralizar el problema, y eso es más simple, rápido y seguro.Pero, como explica el artículo, esto no es un problema distribuido.
Los bloqueos en sistemas distribuidos son distintos de un mutex en una app multihilo, y son más complejos porque múltiples nodos y redes pueden fallar de forma independiente de muchas maneras.
Se necesita un bloqueo distribuido cuando una transacción puede tardar desde segundos hasta horas y una máquina involucrada puede fallar mientras mantiene el bloqueo.
En este caso, la restricción es “no vender más de N tickets”, y la mayoría de las escalas de tráfico reales para este tipo de problema se pueden resolver con el comportamiento transaccional de una base de datos relacional tradicional, dejando la gestión interna de bloqueos a la base de datos.
Ojalá los desarrolladores no se lanzaran tan rápido a “voy a construir un bloqueo distribuido”. Casi siempre hay una mejor respuesta, pero esa respuesta depende de cada aplicación.
Podría ser un buen encaje para algo como el nuevo SQLite de Cloudflare.
La primera vez que leí sobre esto fue aquí: https://code.flickr.net/2010/02/08/ticket-servers-distribute...
Muchos ingenieros no se preocupan de verdad por los problemas de exactitud hasta que es demasiado tarde. Es parecido a la seguridad.
Incluso cuando sí les importa, muchas veces no verifican que lo que están haciendo sea correcto.
Por ejemplo, en mi área, microservicios, actores y procesos se envían mensajes por la red, y más del 95% de las implementaciones que veo tienen casos límite en los que los mensajes pueden perderse o procesarse fuera de orden.
Pero los incentivos no están lo bastante alineados como para corregir este problema. Las estructuras de compensación de ejecutivos e ingenieros no coinciden con los resultados que serían mejores para clientes y accionistas.
Se quiere poner una frontera de red entre llamadas a funciones sin mucha razón, y luego se crean sin fin servidores y clientes HTTP, serialización y deserialización JSON para esas llamadas, con suerte se usa gRPC, y se intenta reimplementar cosas como transacciones distribuidas a través de esa frontera de red.
Al final, solo se genera trabajo ocupado para lidiar con las inevitables “interacciones espeluznantes a distancia”.
Los equipos de producto e ingeniería deben acordarlos, y cuando se incumpla un SLO hay que cambiar el foco hacia la estabilidad del sistema.
Convencer a todos es difícil, así que se necesita buen liderazgo.
Cuando empiezan a aparecer bugs, las funciones nuevas son lentas o casi inexistentes y los clientes comienzan a irse, el argumento de que la calidad debe formar parte del proceso se vuelve muy fácil.
Un líder maduro se adelanta a esa etapa lo antes posible.
[0] https://en.wikipedia.org/wiki/British_Post_Office_scandal
Pero no veo una forma clara de convencer a los gerentes de ayer de que den tiempo para hacerlo bien.
Esto complica demasiado las cosas
Si existe algo como los tokens de cercado que menciona el artículo, no hace falta un lock
El token ni siquiera tiene que ser monotónicamente creciente; basta con que sea un valor único pasivo que compartan el cliente y el almacenamiento
Si lo llamamos token de versión, puede ser un valor monotónicamente creciente, y también funciona un UUID, que normalmente es más fácil de generar. Técnicamente también podría ser un hash de todos los datos del almacenamiento, pero no es práctico
El flujo es así: el cliente obtiene del almacenamiento el token de versión actual junto con los datos que va a modificar, y el almacenamiento consulta los datos y el token de forma atómica para garantizar que ese token corresponde a esa versión de los datos
Luego el cliente vuelve a enviar el token de versión junto con los cambios, y el almacenamiento acepta el cambio solo si el token actual coincide con el token enviado, generando de forma atómica un nuevo token de versión
Se puede introducir un lock por otros motivos, pero en un sistema distribuido debería ser independiente de la integridad del almacenamiento
Tampoco me gusta mucho el término “lock”. Como es temporal y no está garantizado, lease o reserva podría transmitir mejor la idea
Es una forma de empujar la complejidad hacia la base de datos, pero hay que recordar que aquí hablamos de un lock distribuido
Si es una sola base de datos, es sencillo hasta que la base de datos crashea y queda en un estado en el que no se sabe qué escritura CAS se aplicó realmente
En sistemas grandes que necesitan alta disponibilidad y respaldo en múltiples centros de datos, esto también puede romperse por escenarios alrededor de fallas de nodos, así que se vuelve bastante complejo
Normalmente se usa un log de transacciones tipo Paxos. No hay que asumir que existe una solución fácil en sistemas distribuidos. Siempre es un dolor de cabeza
Desde el punto de vista de la eficiencia, tomar un lock permite evitar hacer innecesariamente dos veces el mismo trabajo. Por ejemplo, un cálculo costoso
Si el lock falla y dos nodos hacen el mismo trabajo, podría ser algo menor si el resultado es apenas un aumento de costo o una notificación de correo duplicada
Pero creo que que varios nodos hagan el mismo trabajo es mucho peor que lo descrito en el ejemplo, porque puede obstaculizar el procesamiento distribuido escalable en sí
Supongamos que el sistema de almacenamiento tiene dos nodos y que se ejecutan dos procesos de lectura-modificación-escritura. Los procesos 1 y 2 obtienen ambos el primer token
abcEl proceso 1 hace commit y el token cambia a
cde, y el cambio se transmite por streaming al nodo 2, pero por latencia de red llega tarde al nodo 2Mientras tanto, si el proceso 2 hace commit en el nodo 2 con el token
abc, el nodo 2 todavía no recibió el mensaje del nodo 1, así que acepta el cambio y el sistema queda en un estado inconsistenteCon un token de cercado monotónicamente creciente, esto no ocurre. Ese requisito obliga a los nodos a ponerse de acuerdo sobre el orden total de las operaciones antes de entregar el token