El uso de la funcionalidad In-Memory OLTP sigue siendo una rareza en general entre nuestros clientes. Esta funcionalidad tiene un alto potencial para poder mejorar el rendimiento de aquellos sistemas con alto nivel de concurrencia y de transacciones por segundo y que estén limitados por esperas de tipo LATCH o bien por el uso de CPU.

Para poder habilitar dicha funcionalidad tenemos que crear un nuevo filegroup que acaba teniendo la estructura típica de un filegroup filestream en una carpeta. En dicha carpeta se almacenarán los distintos ficheros de datos y deltas que se generen durante la persistencia de las tablas en memoria de tipo schema_and_data.

Un nuevo problema que nos hemos encontrado es que la utilización de este tipo de filegroups pueden no ser reconocidos correctamente por algunos antivirus y éstos detecten algunos de los ficheros como posibles peligros.

Por poner un ejemplo, tenemos a Microsoft Defender detectando que uno de los ficheros con extensión hkchk (extensión que viene de checkpoint de In-Memory OLTP (aka Heckaton) está infectado:

inmemory oltp

En este tipo de situaciones es posible que, bien de forma automática o de forma manual, se acepte la opción de eliminar o poner en cuarentena dicho fichero:

inmemory oltp 2

Una vez esta acción se ha llevado a cabo, alguien podría decidir pasar un CHECKDB para estar más tranquilo, cuyo resultado parece indicar que no hay ningún problema:

In-Memory OLTP: Otra historia de corrupción y problemas de DMVs

Pero esto no es nada más que un espejismo. La cruda realidad es más tozuda, comenzando por el hecho que CHECKDB no valida las tablas en memoria ni sus filegroups (https://docs.microsoft.com/en-us/sql/relational-databases/in-memory-oltp/transact-sql-constructs-not-supported-by-in-memory-oltp?view=sql-server-ver15#databases-that-use-in-memory-oltp). Por tanto este checkdb “limpio” no significa nada para las tablas en memoria, que pueden estar corruptas.

En user voice hay creada una petición desde hace años para que se realice al menos un chequeo de checksums pero de momento con muy pocos apoyos: https://feedback.azure.com/forums/908035-sql-server/suggestions/32902300-dbcc-checkdb-needs-to-check-the-checksums-on-in-me

Una vez el antivirus ha hecho “su trabajo” encontramos que, en este caso particular, obtenemos este bonito fallo al intentar leer de una tabla en memoria:

inmemory oltp 4

También puede pasar que directamente tras la “desinfección” la corrupción sea tal que ya no podamos ni conectar a la base de datos y pase a estar sospechosa:

inmemory oltp 5

En esta situación, toda la base de datos está impactada, incluyendo toda la parte que no sea In-Memory OLTP. Durante el proceso de recovery los errores son tan útiles y legibles como estos que podéis ver donde lo máximo que podemos leer entre líneas es que ha habido un fallo al cargar el checkpoint:

inmemory oltp 6

Si intentamos poner la base de datos online, nos encontramos con este error:

inmemory oltp 7

En este caso podríamos acceder al resto de la información (solo lectura) si pasamos a modo emergencia, pero no podremos reparar el entuerto con un CHECKDB por ejemplo. Tampoco podemos eliminar el filegroup y volverlo a añadir, ya que una de las limitaciones existentes es que no podemos eliminar un filegroup asociado a In-Memory OLTP. Básicamente nos hemos quedado con un “ladrillo” como base de datos y deberemos restaurar desde el último backup no corrupto que dispongamos. Si la restauración desde ese backup no es posible, tendremos que crear una nueva base de datos y volcar los datos mediante algún tipo de proceso de extracción e inserción en la nueva base de datos.

Esta es una de las razones por las que siempre recomiendo aislar la parte In-Memory en una base de datos independiente, de forma que podamos aislarnos de un impacto global si se produce un fallo. Si podemos permitirnos hacerlo, incluso aislaría dicha base de datos en una instancia independiente.

Adicionalmente a estas precauciones lo ideal es contar también con un “plan B” para casos de desastre. Por ejemplo tener tablas “tradicionales” que puedan hacer funcionar a la aplicación, aunque sea de forma más lenta. Entendemos que esto es bastante paranoico pero realmente nuestra experiencia a día de hoy nos lleva a ser extremadamente cautos con esta funcionalidad y con los posibles problemas que puedan experimentarse.

Otra situación que nos puede ocurrir, sin ser de lejos tan catastrófica, es que no seamos capaces de acceder a una base de datos In-Memory OLTP (imoltp) que está en modo single user:

inmemory oltp 8

Si lanzamos un alter con rollback immediate en este caso nos encontramos que somos la víctima del deadlock:

inmemory oltp 9
inmemory oltp 10

Si aumentamos dicha prioridad ante deadlocks, obtendremos un error debido a que no hemos podido abortar todas las sesiones existentes:

set deadlock_priority high
alter database imoltp set single_user with rollback immediate
alter database imoltp set multi_user with rollback immediate
inmemory oltp 11

Llegados a este punto  procedemos a revisar las sesiones que estén conectadas:

inmemory oltp 12

Es decir, aparentemente no es que tengamos una, sino que tenemos tres sesiones conectadas a la misma base de datos, lo cual no tiene mucho sentido si en teoría estamos en single_user. La primera de las sesiones, la 41, corresponde a un proceso propio de sistema existente en las bases de datos en memoria y que se encarga del checkpoint sobre los pares de ficheros de datos y delta:

inmemory oltp 13

Para quien tenga interés en este mecanismo, tenemos mucha más información sobre cómo se gestiona la durabilidad de las tablas en memoria en la documentación oficial: https://docs.microsoft.com/en-us/sql/relational-databases/in-memory-oltp/durability-for-memory-optimized-tables?view=sql-server-ver15#accessing-data-and-delta-files

Ignorando este proceso de sistema, si comprobamos las dos sesiones de Management Studio vemos que tienen un bloqueo entre ellas:

 

inmemory oltp 14

Si nos fijamos en la última consulta de la primera sesión, no parece nada del otro mundo:

select @@spid;
select SERVERPROPERTY(‘ProductLevel’);

Tampoco la segunda, que forma parte de las consultas que genera el Management Studio para obtener información sobre una base de datos que tiene la funcionalidad In Memory OLTP:

SELECT
isnull((select convert(decimal(18,2),(sum(tms.memory_allocated_for_table_kb) + sum(tms.memory_allocated_for_indexes_kb)))
from [sys].[dm_db_xtp_table_memory_stats] tms), 0.00) AS [MemoryAllocatedToMemoryOptimizedObjectsInKB],
isnull((select convert(decimal(18,2),(sum(tms.memory_used_by_table_kb) + sum(tms.memory_used_by_indexes_kb)))
from [sys].[dm_db_xtp_table_memory_stats] tms), 0.00) AS [MemoryUsedByMemoryOptimizedObjectsInKB]

Si examinamos la sesión 78 veremos que realmente no está ejecutando nada, simplemente está “abierta” sin cerrar la sesión por parte del Management Studio durante casi hora y media manteniendo un bloqueo shared sobre SHARED_TRANSACTION_WORKSPACE en la base de datos, que entra en conflicto con el exclusive que solicita la consulta anterior para calcular el espacio de las tablas en memoria:

inmemory oltp 16

Aparentemente se trata de algún fallo/bug que deja una sesión abierta cuando se da la circunstancia de tener la base de datos en modo simple y tenemos configurado In-Memory OLTP. A su vez aparentemente hay algún tipo de bucle infinito de reintentos dentro de la sesión 83 al lanzar ese comando que hace que no termine nunca pero sí vemos cómo van expirando los timeouts de los bloqueos y van reiniciándose los tiempos de espera por lo que igual sube que baja el tiempo de espera a lo largo del tiempo:

inmemory oltp 17

Si matamos esta sesión 78 vemos como la sesión 83 termina inmediatamente y ya podremos lanzar el alter para volver a poner la base de datos en multi_user y que antes no funcionaba correctamente:

inmemory oltp 18

Otra situación que nos podemos encontrar similar a la interior, y que aparentemente no tiene nada que ver con ella, es el siguente error que nos aparece cuando intentamos crear una base de datos:

 
 
 
 
create database aaaa
 
inmemory oltp 19

Si miramos conexiones a la base de datos model no vemos ninguna explícitamente:

 
 
 
 
select * from sys.dm_exec_sessions where database_id=3
inmemory oltp 20

Sin embargo si miramos sys.dm_tran_locks vemos que tenemos una sesión con un bloqueo adquirido sobre model:

inmemory oltp 21

En este caso la consulta problemática vuelve a ser la misma que en el caso anterior, la que accede a sys.dm_db_xtp_table_memory_stats, ya que se comporta de forma «anómala» a nuestro entender cuando adquiere un bloqueo sobre model y luego se queda “colgada” al llegar a la base de datos en modo simple:

SELECT
ISNULL((select top 1 1 from sys.filegroups FG where FG.[type] = ‘FX’), 0) AS [HasMemoryOptimizedObjects],
isnull((select convert(decimal(18,2),(sum(tms.memory_allocated_for_table_kb) + sum(tms.memory_allocated_for_indexes_kb))) from [sys].[dm_db_xtp_table_memory_stats] tms), 0.00) AS [MemoryAllocatedToMemoryOptimizedObjectsInKB],
isnull((select convert(decimal(18,2),(sum(tms.memory_used_by_table_kb) + sum(tms.memory_used_by_indexes_kb))) from [sys].[dm_db_xtp_table_memory_stats] tms), 0.00) AS [MemoryUsedByMemoryOptimizedObjectsInKB]

Es decir, que aquellas operaciones que accedan a tablas/DMVs de sistema de forma encubierta podrían afectarnos a operaciones que aparentemente nada tienen que ver como una creación de una base de datos nueva, un backup, etc.. También hemos tenido en algunos clientes problemas con consultas de monitorización que cada pocos minutos se lanzan y cruzan muchas tablas de sistema. Estas consultas a DMVs acaban generando problemas en procesos que crean/borran tablas de staging o procesos similares que acceden a sys.objects. Nuestra recomendación en estos casos es intentar acceder lo menos posible a estas tablas/DMVs de sistema y, si vamos a lanzar alguna consulta bastante pesada, puede ser conveniente materializar los datos de la tablas/DMVs de sistema en tablas temporales (SELECT * INTO #dmv1 from sys.dmv1) y utilizar ya los contenidos de la tabla temporal en la consulta pesada final.

En resumen, ciertas funcionalidades, como InMemory OLTP no acaban de afinarse durante los años, probablemente por falta de uso generalizado, lo cual ocasiona que no se reporten los bugs y se ponga a prueba con “fuego real” suficientemente. Por otra parte existen otros procesos cuyos errores no dan demasiada claridad respecto a la razón real por la que ocurren, como el bloqueo exclusivo en model. Sería muy de agradecer que fuese posible el uso de niveles de aislamiento bajos (tipo READ UNCOMMITTED) en el acceso a las DMVS así como contar algo más de información en dichos errores que puedan ayudar a diagnosticar el origen del problema al usuario final.

0 Shares:
Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

You May Also Like