Transacciones en InterSystems IRIS globales

Transacciones en InterSystems IRIS globalesEl DBMS IRIS de InterSystems admite estructuras interesantes para almacenar datos: globales. Básicamente, se trata de claves de varios niveles con varios beneficios adicionales en forma de transacciones, funciones rápidas para atravesar árboles de datos, bloqueos y su propio lenguaje ObjectScript.

Lea más sobre los globales en la serie de artículos “Los globales son espadas-tesoros para almacenar datos”:

Árboles. Parte 1
Árboles. Parte 2
Conjuntos dispersos. parte 3

Me interesé en cómo se implementan las transacciones en globales, qué características hay. Después de todo, se trata de una estructura de almacenamiento de datos completamente diferente a la de las tablas habituales. Nivel mucho más bajo.

Como se sabe por la teoría de las bases de datos relacionales, una buena implementación de transacciones debe satisfacer los requisitos ACID:

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

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

Yo - Aislar. Las transacciones que se ejecutan en paralelo no deberían afectarse entre sí.

D - Duradero. Después de completar exitosamente una transacción, los problemas en niveles inferiores (fallo 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 diseñados para funcionar súper rápido en hardware muy limitado. Veamos la implementación de transacciones en globales usando imagen oficial de IRIS Docker.

Para admitir transacciones en IRIS, se utilizan los siguientes comandos: INICIO, COMPROMISO, TROLLBACK.

1. Atomicidad

La forma más sencilla de comprobarlo es la atomicidad. Comprobamos desde la consola de la base de datos.

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

Luego concluimos:

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

Obtenemos:

1 2 3

Todo esta bien. Se mantiene la atomicidad: se registran todos los cambios.

Compliquemos la tarea, introduzcamos un error y veamos cómo se guarda la transacción, parcialmente o nada.

Comprobemos la atomicidad nuevamente:

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

Luego detendremos con fuerza el contenedor, lo lanzaremos y veremos.

docker kill my-iris

Este comando es casi equivalente a un apagado forzado, ya que envía una señal SIGKILL para detener el proceso inmediatamente.

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

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

- No, no ha sobrevivido.

Probemos 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)

Tampoco ha sobrevivido nada.

2. Consistencia

Dado que en las bases de datos basadas en globales, las claves también se crean en globales (permítanme recordarles que una global es una estructura de almacenamiento de datos de nivel inferior a una tabla relacional), para cumplir con el requisito de coherencia, se debe incluir un cambio en la clave. en la misma transacción como un cambio en el global.

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

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

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

^index(‘Kamenev’, ‘Sergey’, 1234567) = 1

Para que la base de datos sea consistente, debemos agregar la persona así:

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 utilizar una transacción:

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

En otras palabras, cumplir el requisito de coherencia recae enteramente sobre los hombros del programador. Pero cuando se trata de globales, esto es normal, debido a su naturaleza de bajo nivel.

3. Aislamiento

Aquí es donde comienza lo salvaje. Muchos usuarios trabajan simultáneamente en la misma base de datos, cambiando los mismos datos.

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

La base de datos debería ordenarlo todo en tiempo real. Teniendo en cuenta que en las empresas serias incluso hay una persona especial que se encarga del control de versiones (para fusionar sucursales, resolver conflictos, etc.), y la base de datos debe hacer todo esto en tiempo real, la complejidad de la tarea y la corrección de la Diseño de base de datos y código que la sirve.

La base de datos no puede comprender el significado de las acciones realizadas por los usuarios para evitar conflictos si están trabajando con los mismos datos. Solo puede deshacer una transacción que entre en conflicto con otra o ejecutarlas secuencialmente.

Otro problema es que durante la ejecución de una transacción (antes de una 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, lo cual se logra en las bases de datos relacionales. de muchas maneras: creando instantáneas, filas de múltiples versiones, etc.

Al realizar transacciones en paralelo, para nosotros es importante que no interfieran entre sí. Ésta es la propiedad del aislamiento.

SQL define 4 niveles de aislamiento:

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

Veamos cada nivel por separado. Los costos de implementar cada nivel crecen casi exponencialmente.

LEER SIN COMPROMISO - Este es el nivel más bajo de aislamiento, pero al mismo tiempo 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 de las demás antes de la confirmación, pero pueden leer los cambios realizados después de la confirmación.

Si tenemos una transacción T1 larga, durante la cual se realizaron confirmaciones en las transacciones T2, T3 ... Tn, que trabajaron con los mismos datos que T1, entonces al solicitar datos en T1 obtendremos un resultado diferente cada vez. Este fenómeno se llama lectura no repetible.

LECTURA REPETIBLE — en este nivel de aislamiento no tenemos el fenómeno de lectura no repetible, debido a 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, los datos de la instantánea se utiliza. Sin embargo, es posible leer datos fantasma en este nivel de aislamiento. Esto se refiere a la lectura de nuevas filas agregadas por transacciones confirmadas paralelas.

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

Primero, averigüemos si existe un aislamiento de las 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 está haciendo el segundo que abrió la transacción.

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

Abramos 2 ventanas de terminal y abramos 2 transacciones en paralelo.

kill ^t
TSTART
Write ^t(1)
3

TSTART
Set ^t(1)=3

Las transacciones paralelas ven los datos de cada una. 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 de las globales, para las cuales el rendimiento siempre ha sido una prioridad.

¿Qué pasa si necesitamos un mayor nivel de aislamiento en las operaciones globales?

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

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

Podemos hacer esto usando bloqueos inteligentes en ObjectScript, que tienen muchos usos diferentes: puedes realizar bloqueos regulares, incrementales y múltiples con el comando CANDADO (SEGURO).

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 cerraduras.

Este operador le permite tomar no solo bloqueos exclusivos necesarios para cambiar datos, sino también los llamados bloqueos compartidos, que pueden tomar varios subprocesos en paralelo cuando necesitan leer datos que otros procesos no deben cambiar durante el proceso de lectura.

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

Bloqueo de dos fases
Bloqueo de dos fases

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

Usando bloqueos, crearemos ventanas de visibilidad en las que el estado de la base de datos será consistente. Y todo acceso a dichas ventanas de visibilidad del estado acordado estará controlado mediante cerraduras.

Los bloqueos compartidos sobre los mismos datos son reutilizables: varios procesos pueden aceptarlos. Estos bloqueos evitan que otros procesos cambien los datos, es decir. se utilizan para formar ventanas de estado de base de datos consistente.

Los bloqueos exclusivos se utilizan para cambios de datos; solo un proceso puede aceptar dicho bloqueo. Un candado exclusivo puede ser adquirido por:

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

Transacciones en InterSystems IRIS globales

Cuanto más estrecha sea la ventana de visibilidad, más tiempo tendrán que esperar otros procesos, pero más consistente puede ser el estado de la base de datos dentro de ella.

LEER_COMMITTED — la esencia de este nivel es que solo vemos datos confirmados de otros subprocesos. Si los datos de otra transacción aún no se han confirmado, veremos 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 tendremos que conformarnos con bloqueos.

En consecuencia, tendremos que utilizar bloqueos compartidos para permitir que los datos se lean sólo en momentos de coherencia.

Digamos que tenemos una base de usuarios ^personas que se transfieren dinero entre sí.

Momento de 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 a la persona 123 antes del cargo 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 personal, puede usar un bloqueo compartido o no usarlo en absoluto:

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

Sin embargo, si asumimos que las operaciones de la base de datos se realizan casi instantáneamente (permítanme recordarles que los globales son una estructura de nivel mucho más bajo que una tabla relacional), entonces la necesidad de este nivel disminuye.

LECTURA REPETIBLE - Este nivel de aislamiento permite múltiples lecturas de datos que pueden modificarse mediante transacciones simultáneas.

En consecuencia, tendremos que poner un bloqueo compartido a la lectura de los datos que cambiamos y bloqueos exclusivos a los datos que cambiamos.

Afortunadamente, el operador LOCK le permite enumerar en detalle todos los bloqueos necesarios, de los cuales puede haber muchos, en una sola declaración.

LOCK +^person(123, amount)#”S”
чтение ^person(123, amount)

otras operaciones (en este momento, los subprocesos 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”

Al enumerar los candados separados por comas, se toman secuencialmente, pero si haces esto:

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

luego se toman atómicamente todos a la vez.

PUBLICAR POR FASCÍCULOS — Tendremos que establecer bloqueos para que, en última instancia, todas las transacciones que tienen datos comunes se ejecuten de forma secuencial. Para este enfoque, la mayoría de los bloqueos deben ser exclusivos y tomarse en las áreas más pequeñas del global para su rendimiento.

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

4. Durabilidad

Realicé pruebas con corte duro del contenedor usando

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 y confiables. Para garantizar la coherencia de una base de datos basada en datos globales, se requieren esfuerzos del programador y el uso de transacciones, ya que no tiene construcciones integradas complejas, como claves externas.

El nivel de aislamiento de los globales sin usar bloqueos es LEER NO COMPROMETIDO y cuando se usan bloqueos se puede garantizar hasta el nivel SERIALIZE.

La exactitud y la velocidad de las transacciones en globales depende en gran medida de la habilidad del programador: cuanto más ampliamente se utilizan los bloqueos compartidos al leer, mayor es el nivel de aislamiento, y cuanto más exclusivos se toman los bloqueos, más rápido es el rendimiento.

Fuente: habr.com

Añadir un comentario