Transacciones globales de InterSystems IRIS

InterSystems IRIS y transacción InterSystems IRIS DBMS admite estructuras curiosas de almacenamiento de datos: globales. De hecho, se trata de claves de varios niveles con varias ventajas adicionales en forma de transacciones, funciones rápidas para atravesar árboles de datos, bloqueos y su propio lenguaje ObjectScript.

Más información sobre los globales en la serie de artículos "Globales - Espadas-Masones para el almacenamiento de datos":

Los arboles. Parte 1
Los arboles. Parte 2
Matrices dispersas. Parte 3

Me resultó interesante cómo se implementan las transacciones en el mundo, qué características hay. Después de todo, esta es una estructura completamente diferente para almacenar datos que las tablas habituales. Nivel mucho más bajo.

Como sabe por la teoría de la base de datos relacional, una buena implementación de transacción debe satisfacer los requisitos de ACID :

A - Atómico (atomicidad). Se registran todos los cambios realizados en la transacción o ninguno.

C - Consistencia. Una vez completada la transacción, el estado lógico de la base de datos debe ser internamente coherente. En muchos sentidos, este requisito se aplica al programador, pero en el caso de las bases de datos SQL, también se aplica a las claves externas.

I - Aislar (aislamiento). Las transacciones paralelas no deberían afectarse entre sí.

D - Durable. Después de que la transacción se complete con éxito, los problemas en los niveles inferiores (falla de energía, por ejemplo) no deberían afectar los datos modificados por la transacción.

Los globales son estructuras de datos no relacionales. Fueron creados para un trabajo ultrarrápido en hardware muy limitado. Comprendamos la implementación de transacciones en globales utilizando la imagen oficial del acoplador IRIS .

Para admitir transacciones en IRIS, se utilizan los siguientes comandos: TSTART , TCOMMIT , TROLLBACK .

1. Atomicidad


La forma más fácil de verificar la atomicidad. Comprobación desde la consola de la base de datos.

Kill ^a TSTART Set ^a(1) = 1 Set ^a(2) = 2 Set ^a(3) = 3 TCOMMIT 

Entonces concluimos:

 Write ^a(1), “ ”, ^a(2), “ ”, ^a(3) 

Obtenemos:

 1 2 3 

Todo esta bien. Atomicidad observada: se registran todos los cambios.

Complicamos la tarea, introducimos un error y vemos cómo se guarda la transacción, parcialmente o en absoluto.

Verifiquemos la atomicidad una vez más:

 Kill ^A TSTART Set ^a(1) = 1 Set ^a(2) = 2 Set ^a(3) = 3 

Luego, detenga el contenedor por la fuerza, comience y vea.

 docker kill my-iris 

Este comando es casi equivalente a apagar la alimentación por la fuerza, ya que envía una señal para detener inmediatamente el proceso SIGKILL.

¿Quizás la transacción se guardó parcialmente?

 WRITE ^a(1), ^a(2), ^a(3) ^ <UNDEFINED> ^a(1) 

- No, no conservado.

Pruebe el comando de reversión:

 Kill ^A TSTART Set ^a(1) = 1 Set ^a(2) = 2 Set ^a(3) = 3 TROLLBACK WRITE ^a(1), ^a(2), ^a(3) ^ <UNDEFINED> ^a(1) 

Nada fue preservado tampoco.

2. Consistencia


Dado que en las bases de datos en globales, las claves también se hacen en globales (recuerdo que un global es una estructura de nivel inferior para almacenar datos que una tabla relacional), para cumplir con el requisito de coherencia, debe incluir el cambio de clave en la misma transacción que el cambio global.

Por ejemplo, tenemos una persona ^ global en la que almacenamos personalidades y usamos el TIN como clave.

 ^person(1234567, 'firstname') = 'Sergey' ^person(1234567, 'lastname') = 'Kamenev' ^person(1234567, 'phone') = '+74995555555 ... 

Para tener una búsqueda rápida por apellido y nombre, creamos la clave ^ index.

 ^index('Kamenev', 'Sergey', 1234567) = 1 

Para que se acuerde la base, debemos agregar personalidades como esta:

 TSTART ^person(1234567, 'firstname') = 'Sergey' ^person(1234567, 'lastname') = 'Kamenev' ^person(1234567, 'phone') = '+74995555555 ^index('Kamenev', 'Sergey', 1234567) = 1 TCOMMIT 

En consecuencia, al eliminar, también debemos usar la transacción:

 TSTART Kill ^person(1234567) ZKill ^index('Kamenev', 'Sergey', 1234567) TCOMMIT 

En otras palabras, el cumplimiento del requisito de coherencia recae completamente en el programador. Pero cuando se trata de globales, esto es normal, debido a su naturaleza de bajo nivel.

3. Aislamiento


Aquí es donde comienzan las selvas. Muchos usuarios trabajan simultáneamente en la misma base de datos, modifican los mismos datos.

La situación es comparable a la situación en la que muchos usuarios trabajan simultáneamente con el mismo repositorio con el código e intentan realizar cambios en muchos archivos a la vez.

La base de datos debe resolver esto en tiempo real. Dado que en las empresas serias hay incluso una persona especial responsable del control de versiones (para fusionar sucursales, resolver conflictos, etc.), y la base de datos debe hacer todo esto en tiempo real, se hace evidente la complejidad de la tarea y el diseño correcto de la base de datos y El código que lo sirve.

La base de datos no puede entender el significado de las acciones realizadas por los usuarios para evitar conflictos si trabajan con los mismos datos. Solo puede cancelar una transacción contraria a otra o ejecutarlas secuencialmente.

Otro problema es que durante la ejecución de la transacción (antes de la confirmación), el estado de la base de datos puede ser inconsistente, por lo que es deseable que otras transacciones no tengan acceso al estado inconsistente de la base de datos, que se logra en las bases de datos relacionales de muchas maneras: creando instantáneas, filas de varias versiones y etc.

En la ejecución paralela de transacciones, es importante para nosotros que no interfieran entre sí. Esta es la propiedad del aislamiento.

SQL define 4 niveles de aislamiento:

  • LEER SIN COMPROMISO
  • LEER COMPROMETIDO
  • LECTURA REPETIBLE
  • SERIALIZABLE

Consideremos cada nivel por separado. Los costos de implementar cada nivel están creciendo casi exponencialmente.

LEER NO COMPROMETIDO es el nivel más bajo de aislamiento, pero el más rápido. Las transacciones pueden leer los cambios realizados entre sí.

LEER COMPROMETIDO es el siguiente nivel de aislamiento, que es un compromiso. Las transacciones no pueden leer los cambios realizados entre sí antes de una confirmación, pero pueden leer cualquier cambio realizado después de una confirmación.

Si tenemos una transacción larga T1, durante la cual hubo confirmaciones en las transacciones T2, T3 ... Tn que funcionaron con los mismos datos que T1, entonces cuando solicitamos datos en T1, obtendremos resultados diferentes cada vez. Este fenómeno se llama lectura no repetible.

LECTURA REPETIBLE : en este nivel de aislamiento, no tenemos el fenómeno de la lectura no repetible, debido al hecho de que para cada solicitud de lectura de datos, se crea una instantánea de los datos del resultado y cuando se reutiliza en la misma transacción, se utilizan los datos de la instantánea. Sin embargo, a este nivel de aislamiento, se pueden leer datos fantasma. Esto se refiere a la lectura de nuevas líneas que fueron agregadas por transacciones confirmadas concurrentes.

SERIALIZABLE es el nivel más alto de aislamiento. Se caracteriza por el hecho de que los datos utilizados de cualquier manera en la transacción (lectura o cambio) están disponibles para otras transacciones solo después de la finalización de la primera transacción.

Primero, averigüemos si hay un aislamiento de operaciones en una transacción del hilo principal. Abramos 2 ventanas de terminal.
 Kill ^t Write ^t(1) 2 
 TSTART Set ^t(1)=2 

No hay aislamiento Un hilo ve lo que hace el segundo que abrió la transacción.

Veamos si las transacciones de diferentes flujos ven lo que sucede dentro de ellos.

Abrimos 2 ventanas de terminal y abrimos 2 transacciones en paralelo.
 kill ^t TSTART Write ^t(1) 3 
 TSTART Set ^t(1)=3 

Las transacciones concurrentes ven los datos del otro. Entonces, obtuvimos el nivel de aislamiento más simple, pero también el más rápido LEER SIN COMPROMISO.

En principio, esto podría esperarse para los globales, para quienes la velocidad siempre ha sido primordial.

Pero, ¿qué pasa si necesitamos un mayor nivel de aislamiento en las operaciones globales?

Aquí debe pensar por qué se necesitan niveles de aislamiento y cómo funcionan.

El nivel de aislamiento más alto de SERIALIZE significa que el resultado de las transacciones ejecutadas simultáneamente es equivalente a su ejecución secuencial, lo que garantiza la ausencia de colisiones.

Podemos hacer esto con la ayuda de bloqueos competentes en ObjectScript, que tienen muchas formas diferentes de aplicación: puede hacer bloqueos múltiples, incrementales y regulares con el comando LOCK .

Los niveles de aislamiento más bajos son compensaciones diseñadas para aumentar la velocidad de la base de datos.

Veamos cómo podemos lograr diferentes niveles de aislamiento usando bloqueos.

Este operador le permite tomar no solo los bloqueos exclusivos necesarios para cambiar los datos, sino también los llamados compartidos, que pueden tomar varios hilos a la vez, cuando necesitan leer datos que otros procesos no deberían cambiar durante la lectura.

Más información sobre el método de bloqueo de dos fases en ruso e inglés:

Cerradura de dos fases
Bloqueo bifásico

La dificultad es que durante la transacción el estado de la base de datos puede ser inconsistente, sin embargo, estos datos inconsistentes son visibles para otros procesos. ¿Cómo evitar esto?

Usando bloqueos, crearemos ventanas de visibilidad en las que se acordará el estado de la base de datos. Y todas las llamadas a tales ventanas de visibilidad del estado acordado serán controladas por cerraduras.

Los bloqueos compartidos de los mismos datos son reutilizables: varios procesos pueden tomarlos. Estos bloqueos evitan que otros procesos cambien datos, es decir Se utilizan para formar las ventanas del estado coordinado de la base de datos.

Los bloqueos exclusivos se utilizan para modificar datos: solo un proceso puede tomar dicho bloqueo. El bloqueo exclusivo puede tomar:

  1. Cualquier proceso si los datos son libres
  2. Solo el proceso que tiene un bloqueo compartido en estos datos y el primero solicitó un bloqueo exclusivo.



Cuanto más estrecha sea la ventana de visibilidad, más tardarán otros procesos en esperar, pero más coherente puede ser el estado de la base de datos.

READ_COMMITED : la esencia de este nivel es que solo vemos datos de otras transmisiones que están bloqueados. Si los datos en otra transacción aún no se han confirmado, entonces vemos su versión anterior.

Esto nos permite paralelizar el trabajo en lugar de esperar a que se libere el bloqueo.

Sin trucos especiales, no podremos ver la versión anterior de los datos en IRIS, por lo que tenemos que ver con bloqueos.

En consecuencia, tendremos que usar bloqueos compartidos para permitir la lectura de datos solo en momentos de consistencia.

Supongamos que tenemos una base de usuarios ^ persona que transfiere dinero entre sí.

El momento de la transferencia de la persona 123 a la persona 242:

 LOCK +^person(123), +^person(242) Set ^person(123, amount) = ^person(123, amount) - amount Set ^person(242, amount) = ^person(242, amount) + amount LOCK -^person(123), -^person(242) 

El momento de solicitar la cantidad de dinero de la persona 123 antes del débito debe ir acompañado de un bloqueo exclusivo (por defecto):

 LOCK +^person(123) Write ^person(123) 

Y si necesita mostrar el estado de la cuenta en su cuenta, puede usar el bloqueo compartido o no usarlo en absoluto:

 LOCK +^person(123)#”S” Write ^person(123) 

Sin embargo, si suponemos que las operaciones de la base de datos se realizan casi instantáneamente (recuerdo que los globales son una estructura de nivel mucho más baja que una tabla relacional), entonces la necesidad de este nivel disminuye.

LECTURA REPETIBLE : en este nivel de aislamiento, se supone que puede haber múltiples lecturas de datos que pueden modificarse mediante transacciones concurrentes.

En consecuencia, tendremos que poner un bloqueo compartido en la lectura de los datos que estamos cambiando y bloqueos exclusivos en los datos que estamos cambiando.

Afortunadamente, el operador LOCK le permite a un operador enumerar en detalle todos los bloqueos necesarios, que pueden ser muchos.

 LOCK +^person(123, amount)#”S”  ^person(123, amount) 

otras operaciones (en este momento, los hilos paralelos intentan cambiar ^ persona (123, cantidad), pero no pueden)

 LOCK +^person(123, amount)  ^person(123, amount) LOCK -^person(123, amount)  ^person(123, amount) LOCK -^person(123, amount)#”S” 

Cuando se enumeran los bloqueos separados por comas, se toman secuencialmente, y si lo hace:

 LOCK +(^person(123),^person(242)) 

entonces se toman atómicamente de una vez.

SERIALIZAR : tendremos que establecer los bloqueos para que, en última instancia, todas las transacciones que tienen datos comunes se ejecuten secuencialmente. Para este enfoque, la mayoría de las cerraduras deben ser exclusivas y llevadas a las áreas más pequeñas del mundo para el rendimiento.

Si hablamos de cancelaciones en la persona global ^, entonces solo el nivel de aislamiento SERIALIZE es aceptable para él, ya que el dinero debe gastarse estrictamente secuencialmente, de lo contrario es posible gastar la misma cantidad varias veces.

4. Durabilidad


Realicé pruebas con un corte duro del contenedor a través de

 docker kill my-iris 

La base los toleró bien. No se identificaron problemas.

Conclusión


Para los globales, InterSystems IRIS tiene soporte para transacciones. Son verdaderamente atómicos, confiables. Para garantizar la coherencia de la base de datos en los globales, los esfuerzos del programador y el uso de transacciones son necesarios, ya que no existen construcciones integradas complejas, como las claves externas.

El nivel de aislamiento de los globales sin el uso de cerraduras se LEE SIN COMPROMISO, y cuando se usan cerraduras, se puede asegurar hasta el nivel de SERIALIZAR.

La exactitud y la velocidad de las transacciones en los globales depende en gran medida de la habilidad del programador: cuanto más se utilicen los bloqueos compartidos al leer, cuanto mayor sea el nivel de aislamiento y más bloqueos exclusivos se realicen, mayor será la velocidad.

Source: https://habr.com/ru/post/461753/


All Articles