El rendimiento de una tabla en memoria es peor que una tabla basada en disco


10

Tengo una tabla en SQL Server 2014 que se parece a la siguiente:

CREATE TABLE dbo.MyTable
(
[id1] [bigint] NOT NULL,
[id2] [bigint] NOT NULL,
[col1] [int] NOT NULL default(0),
[col2] [int] NOT NULL default(0)
)

con (id1, id2) siendo la PK. Básicamente, id1 es un identificador para agrupar un conjunto de resultados (id2, col1, col2), cuyo pk es id2.

Estoy tratando de usar una tabla en memoria para deshacerme de una tabla basada en disco existente, que es mi cuello de botella.

  • Los datos en la tabla se escriben -> leer -> borrados una vez.
  • Cada valor de id1 tiene varios (decenas / cientos de) miles de id2.
  • Los datos se almacenan en la tabla durante un período de tiempo muy corto, por ejemplo, 20 segundos.

Las consultas realizadas en esta tabla son las siguientes:

-- INSERT (can vary from 10s to 10,000s of records):
INSERT INTO MyTable
  SELECT @fixedValue, id2, col1, col2 FROM AnotherTable

-- READ:
SELECT id2, col1
FROM MyTable INNER JOIN OtherTbl ON MyTable.id2 = OtherTbl.pk
WHERE id1 = @value
ORDER BY col1

-- DELETE:
DELETE FROM MyTable WHERE id1 = @value

Aquí está la definición actual que usé para la tabla:

CREATE TABLE dbo.SearchItems
(
  [id1] [bigint] NOT NULL,
  [id2] [bigint] NOT NULL,
  [col1] [int] NOT NULL default(0),
  [col2] [int] NOT NULL default(0)

  CONSTRAINT PK_Mem PRIMARY KEY NONCLUSTERED (id1,id2),
  INDEX idx_Mem HASH (id1,id2) WITH (BUCKET_COUNT = 131072)
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY)

Desafortunadamente, esta definición da como resultado una degradación del rendimiento con respecto a la situación anterior con una tabla basada en disco. El orden de magnitud es más o menos un 10% más alto (que en algunos casos alcanza el 100%, entonces el doble de tiempo).

Sobre todo, esperaba obtener una superventaja en escenarios de alta concurrencia, dada la arquitectura sin bloqueo anunciada por Microsoft. En cambio, las peores actuaciones son exactamente cuando hay varios usuarios concurrentes que ejecutan varias consultas en la tabla.

Preguntas:

  • ¿Cuál es el BUCKET_COUNT correcto para establecer?
  • ¿Qué tipo de índice debo usar?
  • ¿Por qué el rendimiento es peor que con la tabla basada en disco?

Una consulta de sys.dm_db_xtp_hash_index_stats devuelve:

total_bucket_count = 131072
empty_bucket_count = 0
avg_chain_len = 873
max_chain_length = 1009

Cambié el conteo del depósito, por lo que la salida de sys.dm_db_xtp_hash_index_stats es:

total_bucket_count = 134217728
empty_bucket_count = 131664087
avg_chain_len = 1
max_chain_length = 3

Aún así, los resultados son casi los mismos, si no peores.


¿Está seguro de que no se encuentra con la detección de parámetros? ¿Has intentado ejecutar las consultas con OPTION(OPTIMIZE FOR UNKNOWN)(ver Sugerencias de tabla )?
TT.

Supongo que te encuentras con problemas de cadena de fila. ¿Nos puede dar la salida deselect * from sys.dm_db_xtp_hash_index_stats ? Además, este enlace debe responder a la mayoría de sus preguntas: msdn.microsoft.com/en-us/library/…
Sean Gallardy

44
El índice hash solo es útil para predicados en ambas columnas incluidas. ¿Has probado sin un índice hash en la tabla?
Mikael Eriksson

Descubrí que las mejores mejoras de rendimiento con la tecnología en memoria solo se pueden lograr utilizando procedimientos almacenados compilados de forma nativa .
Daniel Hutmacher

@DanielHutmacher FWIW He visto contraejemplos en los que todo el beneficio era eliminar el enclavamiento y agregar procedimientos compilados de forma nativa que dieron una mejora nula o insignificante. No creo que haya lugar para una declaración general (aunque puede tener razón en este caso, ni siquiera he mirado los detalles).
Aaron Bertrand

Respuestas:


7

Si bien esta publicación no será una respuesta completa debido a la falta de información, debería ser capaz de orientarlo en la dirección adecuada o, de lo contrario, obtener información que luego podrá compartir con la comunidad.

Desafortunadamente, esta definición da como resultado una degradación del rendimiento con respecto a la situación anterior con una tabla basada en disco. El orden de magnitud es más o menos un 10% más alto (que en algunos casos alcanza el 100%, entonces el doble de tiempo).

Sobre todo, esperaba obtener una superventaja en escenarios de alta concurrencia, dada la arquitectura sin bloqueo anunciada por Microsoft. En cambio, las peores actuaciones son exactamente cuando hay varios usuarios concurrentes que ejecutan varias consultas en la tabla.

Esto es preocupante ya que definitivamente no debería ser el caso. Ciertas cargas de trabajo no son para tablas de memoria (SQL 2014) y algunas cargas de trabajo se prestan a ello. En la mayoría de las situaciones, puede haber un aumento mínimo en el rendimiento simplemente migrando y eligiendo los índices adecuados.

Originalmente estaba pensando muy estrechamente sobre sus preguntas con respecto a esto:

Preguntas:

  • ¿Cuál es el BUCKET_COUNT correcto para establecer?
  • ¿Qué tipo de índice debo usar?
  • ¿Por qué el rendimiento es peor que con la tabla basada en disco?

Inicialmente creía que había un problema con la tabla de memoria real y los índices no eran óptimos. Si bien hay algunos problemas con la definición del índice de hash optimizado para la memoria, creo que el problema real es con las consultas utilizadas.

-- INSERT (can vary from 10s to 10,000s of records):
INSERT INTO MyTable
  SELECT @fixedValue, id2, col1, col2 FROM AnotherTable

Este inserto debería ser extremadamente rápido si solo involucrara la tabla en memoria. Sin embargo, también involucra una tabla basada en disco y está sujeta a todos los bloqueos y bloqueos asociados con eso. Por lo tanto, la pérdida de tiempo real aquí está en la tabla basada en disco.

Cuando hice una prueba rápida contra la inserción de 100,000 filas de la tabla basada en el disco después de cargar los datos en la memoria, fueron tiempos de respuesta de menos de un segundo. Sin embargo, la mayoría de sus datos solo se conservan durante un período de tiempo muy corto, menos de 20 segundos. Esto no le da mucho tiempo para vivir realmente en caché. Además, no estoy seguro de qué tan grandeAnotherTable es realmente y no sé si los valores se leen del disco o no. Tenemos que confiar en usted para estas respuestas.

Con la consulta Seleccionar:

SELECT id2, col1
FROM MyTable INNER JOIN OtherTbl ON MyTable.id2 = OtherTbl.pk
WHERE id1 = @value
ORDER BY col1

Nuevamente, estamos a merced del rendimiento de la tabla basada en interoperabilidad + disco. Además, los tipos no son baratos en los índices HASH y se debe usar un índice no agrupado. Esto se menciona en la guía de índice que vinculé en los comentarios.

Para dar algunos datos reales basados ​​en la investigación, cargué la SearchItemstabla en memoria con 10 millones de filas y AnotherTablecon 100,000 ya que no sabía el tamaño real o las estadísticas de la misma. Luego usé la consulta de selección anterior para ejecutar. Además, creé una sesión de eventos extendidos en wait_completed y la puse en un búfer de anillo. Se limpió después de cada ejecución. También corrí DBCC DROPCLEANBUFFERSpara simular un entorno donde todos los datos pueden no residir en la memoria.

Los resultados no fueron nada espectaculares al mirarlos en el vacío. Dado que la computadora portátil en la que estoy probando esto está usando un SSD de mayor grado, reduje artificialmente el rendimiento basado en disco para la VM que estoy usando.

Los resultados llegaron sin información de espera después de 5 ejecuciones de la consulta solo en la tabla basada en memoria (eliminando la unión y sin subconsultas). Esto es más o menos como se esperaba.

Sin embargo, cuando utilicé la consulta original, tuve que esperar. En este caso, fue PAGEIOLATCH_SH lo que tiene sentido ya que los datos se leen del disco. Ya que soy el unico usuario en este sistema y no dediqué tiempo a crear un entorno de prueba masivo para inserciones, actualizaciones y eliminaciones en la tabla unida, no esperaba que entrara en vigencia ningún bloqueo o bloqueo.

En este caso, una vez más, la porción significativa de tiempo se gastó en la tabla basada en disco.

Finalmente la consulta de eliminación. Encontrar las filas basadas solo en ID1 no es extremadamente eficiente con un índice has. Si bien es cierto que los predicados de igualdad son para lo que son adecuados los índices hash, el depósito en el que se encuentran los datos se basa en las columnas hash completas. Por lo tanto, id1, id2 donde id1 = 1, id2 = 2 e id1 = 1, id2 = 3 se dividirán en diferentes segmentos, ya que el hash estará en (1,2) y (1,3). Este no será un simple escaneo de rango B-Tree ya que los índices hash no están estructurados de la misma manera. Entonces esperaría que este no sea el índice ideal para esta operación, sin embargo, no esperaría que tome órdenes de magnitud más tiempo de lo experimentado. Me interesaría ver wait_info sobre esto.

Sobre todo, esperaba obtener una superventaja en escenarios de alta concurrencia, dada la arquitectura sin bloqueo anunciada por Microsoft. En cambio, las peores actuaciones son exactamente cuando hay varios usuarios concurrentes que ejecutan varias consultas en la tabla.

Si bien es cierto que los bloqueos se usan para lograr coherencia lógica, las operaciones aún deben ser atómicas. Esto se realiza a través de un operador especial de comparación basado en CPU (razón por la cual In-Memory solo funciona con ciertos procesadores [aunque casi todos los cpus fabricados en los últimos 4 años]). Por lo tanto, no obtenemos todo gratis, aún habrá tiempo para completar estas operaciones.

Otro punto a destacar es el hecho de que en casi todas las consultas, la interfaz utilizada es T-SQL (y no SPROC compilados de forma nativa) que tocan al menos una tabla basada en disco. Es por eso que creo que, al final, en realidad no estamos teniendo un mayor rendimiento, ya que todavía estamos limitados al rendimiento de las tablas basadas en disco.

Seguimiento:

  1. Cree una sesión de evento extendida para wait_completed y especifique un SPID conocido por usted. Ejecute la consulta y bríndenos el resultado o consúmalo internamente.

  2. Danos una actualización de la salida del # 1.

  3. No hay un número mágico para determinar el recuento de depósitos para los índices hash. Básicamente, siempre y cuando los cubos no se llenen por completo y las cadenas de fila permanezcan por debajo de 3 o 4, el rendimiento debe ser aceptable. Esto es como preguntar: "¿En qué debo configurar mi archivo de registro?" - va a depender por proceso, por base de datos, por tipo de uso.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.