¿Por qué puede necesitar replicación semisíncrona?

Hola a todos. En contacto Vladislav Rodin. Actualmente estoy impartiendo cursos sobre arquitectura de software y arquitectura de software de alta carga en el portal OTUS. En previsión del inicio de una nueva secuencia del curso "Arquitecto de altas cargas", decidí escribir un pequeño material de autor, que quiero compartir con ustedes.




Introducción


Debido al hecho de que solo se pueden realizar alrededor de 400-700 operaciones por segundo en el HDD (que es incomparable con los rps típicos que caen en un sistema muy cargado), la base de datos de disco clásica es un cuello estrecho de arquitectura. Por lo tanto, se debe prestar especial atención a los patrones de escala de este repositorio.

En este momento hay 2 patrones de escalado de base: replicación y fragmentación. Sharding le permite escalar la operación de escritura y, como resultado, reducir las rps para la grabación por cada servidor en su clúster. La replicación le permite hacer lo mismo, pero con operaciones de lectura. Este artículo está dedicado a este mismo patrón.

Replicación


Si observa la replicación a un nivel muy alto, esto es algo simple: tenía un servidor, los datos estaban en él y luego este servidor dejó de hacer frente a la carga de leer estos datos. Agrega un par de servidores más, sincroniza los datos en todos los servidores y el usuario puede leer desde cualquier servidor de su clúster.

A pesar de la aparente simplicidad, hay varias opciones para clasificar diversas implementaciones de este esquema:

  • Por roles en el clúster (maestro-maestro o maestro-esclavo)
  • Por objetos reenviados (basados ​​en filas, enunciados o mixtos)
  • Según el mecanismo de sincronización de nodos

Hoy nos ocuparemos del tercer punto.

¿Cómo se compromete la transacción?


Este tema no se relaciona directamente con la replicación, sin embargo, se puede escribir un artículo por separado, ya que sin una mayor comprensión del mecanismo de confirmación de transacción, la lectura adicional es inútil, permítame recordarle las cosas más básicas. Una transacción se confirma en 3 etapas:

  1. Escribir una transacción en el registro de la base de datos.
  2. Aplicación de una transacción en un motor de base de datos.
  3. Devolver confirmación al cliente sobre la aplicación exitosa de la transacción.

En varias bases de datos, pueden surgir matices en este algoritmo: por ejemplo, en el motor InnoDB de la base de datos MySQL hay 2 registros: uno para la replicación (registro binario) y el otro para mantener ACID (registro de deshacer / rehacer), mientras que en PostgreSQL hay un registro ejecutándose ambas funciones (escritura anticipada log = WAL). Pero arriba está precisamente el concepto general que permite ignorar tales matices.

Replicación sincrónica (sincronización)


Agreguemos la lógica para replicar los cambios recibidos al algoritmo de confirmación de transacción:

  1. Escribir una transacción en el registro de la base de datos.
  2. Aplicación de una transacción en un motor de base de datos.
  3. Envío de datos a todas las réplicas.
  4. Reciba la confirmación de todas las réplicas sobre la transacción en ellos.
  5. Devolver confirmación al cliente sobre la aplicación exitosa de la transacción.

Con este enfoque, obtenemos una serie de desventajas:

  • El cliente está esperando que se apliquen los cambios a todas las réplicas.
  • A medida que aumenta el número de nodos en el clúster, reducimos la probabilidad de que la operación de escritura tenga éxito.

Si todo está más o menos claro con el primer párrafo, entonces se deben aclarar los motivos del segundo párrafo. Si durante la replicación sincrónica no obtenemos una respuesta de al menos un nodo, revertimos la transacción. Por lo tanto, al aumentar el número de nodos en el clúster, aumenta la probabilidad de que la operación de escritura falle.

¿Podemos esperar la confirmación solo de una cierta fracción de nodos, por ejemplo, del 51% (quórum)? Sí, podemos, pero en la versión clásica, se requiere la confirmación de todos los nodos, porque así es como podemos garantizar la consistencia completa de los datos en el clúster, lo cual es una ventaja indudable de este tipo de replicación.

Replicación asíncrona


Modifiquemos el algoritmo anterior. Enviaremos datos a las réplicas "en algún momento posterior", y "en algún momento posterior" los cambios se aplicarán en las réplicas:

  1. Escribir una transacción en el registro de la base de datos.
  2. Aplicación de una transacción en un motor de base de datos.
  3. Devolver confirmación al cliente sobre la aplicación exitosa de la transacción.
  4. Enviar datos a réplicas y aplicarles cambios.

Este enfoque lleva al hecho de que el clúster funciona rápidamente, porque no mantenemos al cliente esperando que los datos lleguen a las réplicas e incluso se comuniquen.

Pero la condición de soltar datos en las réplicas "en algún momento posterior" puede conducir a la pérdida de la transacción y a la pérdida de la transacción confirmada al usuario, porque si los datos no tuvieron tiempo de replicarse, se envió una confirmación al cliente sobre el éxito de la operación, y el nodo al que llegaron los cambios voló HDD, estamos perdiendo la transacción, lo que puede tener consecuencias muy desagradables.

Replicación semisincrónica


Finalmente, llegamos a la replicación semisíncrona. Este tipo de replicación no es muy conocida ni muy común, pero es de gran interés, ya que puede combinar las ventajas de la replicación sincrónica y asincrónica.

Intentemos combinar los 2 enfoques anteriores. No mantendremos al cliente por mucho tiempo, pero requerimos que los datos se repliquen:

  1. Escribir una transacción en el registro de la base de datos.
  2. Aplicación de una transacción en un motor de base de datos.
  3. Envío de datos a réplicas.
  4. Recibiendo confirmación de la réplica sobre la recepción de cambios (se aplicarán "en algún momento posterior").
  5. Devolver confirmación al cliente sobre la aplicación exitosa de la transacción.

Tenga en cuenta que con este algoritmo, la pérdida de transacción ocurre solo en el caso de una caída del nodo que acepta los cambios y los nodos de réplica. La probabilidad de tal mal funcionamiento se considera pequeña, y se aceptan estos riesgos.

Pero con este enfoque, el riesgo de lecturas fantasmas es posible. Imagine el siguiente escenario: en el paso 4, no recibimos confirmación de ninguna réplica. Debemos revertir esta transacción y no devolver la confirmación al cliente. Dado que los datos se aplicaron en el paso 2, existe un intervalo de tiempo entre el final del paso 2 y la reversión de la transacción, durante el cual las transacciones paralelas pueden ver los cambios que no deberían estar en la base de datos.

Replicación de semisincronización sin pérdida


Si piensa un poco, solo puede cambiar los pasos del algoritmo en lugares para solucionar el problema de las lecturas fantasmas en este escenario:

  1. Escribir una transacción en el registro de la base de datos.
  2. Envío de datos de réplica.
  3. Recibiendo confirmación de la réplica sobre la recepción de cambios (se aplicarán "en algún momento posterior").
  4. Aplicación de una transacción en un motor de base de datos.
  5. Devolver confirmación al cliente sobre la aplicación exitosa de la transacción.

Ahora confirmamos los cambios solo si se replican.

Conclusión


Como siempre, no hay soluciones perfectas, hay un conjunto de soluciones, cada una de las cuales tiene sus propias ventajas y desventajas y es adecuada para resolver varios tipos de problemas. Esto también es cierto para elegir un mecanismo para sincronizar los datos de una base de datos replicada. El conjunto de beneficios que tiene la replicación semisíncrona es lo suficientemente sólido e interesante como para ser considerado merecedor de atención, a pesar de su baja prevalencia.

Eso es todo. ¡Nos vemos en el curso !

All Articles