He escrito una aplicación con un servidor SQL Server que recopila y almacena una gran cantidad de registros. He calculado que, en el pico, la cantidad promedio de registros está en algún lugar en la avenida de 3-4 mil millones por día (20 horas de operación).
Mi solución original (antes de hacer el cálculo real de los datos) era que mi aplicación insertara registros en la misma tabla que mis clientes consultaban. Eso se estrelló y se quemó bastante rápido, obviamente, porque es imposible consultar una tabla que tiene tantos registros insertados.
Mi segunda solución fue usar 2 bases de datos, una para los datos recibidos por la aplicación y otra para los datos listos para el cliente.
Mi aplicación recibiría datos, los dividiría en lotes de ~ 100k registros y los insertaría en masa en la tabla de etapas. Después de ~ 100k registros, la aplicación crearía sobre la marcha otra tabla de etapas con el mismo esquema que antes y comenzaría a insertarse en esa tabla. Crearía un registro en una tabla de trabajos con el nombre de la tabla que tiene 100k registros y un procedimiento almacenado en el lado del Servidor SQL movería los datos de la (s) tabla (s) provisional a la tabla de producción lista para el cliente, y luego soltaría el tabla tabla temporal creada por mi aplicación.
Ambas bases de datos tienen el mismo conjunto de 5 tablas con el mismo esquema, excepto la base de datos provisional que tiene la tabla de trabajos. La base de datos provisional no tiene restricciones de integridad, clave, índices, etc. en la tabla donde residirá la mayor parte de los registros. A continuación se muestra el nombre de la tabla SignalValues_staging
. El objetivo era que mi aplicación bloqueara los datos en SQL Server lo más rápido posible. El flujo de trabajo de crear tablas sobre la marcha para que puedan migrarse fácilmente funciona bastante bien.
Las siguientes son las 5 tablas relevantes de mi base de datos provisional, más mi tabla de trabajos:
El procedimiento almacenado que he escrito maneja el movimiento de los datos de todas las tablas de ensayo y su inserción en producción. A continuación se muestra la parte de mi procedimiento almacenado que se inserta en la producción desde las tablas de preparación:
-- Signalvalues jobs table.
SELECT *
,ROW_NUMBER() OVER (ORDER BY JobId) AS 'RowIndex'
INTO #JobsToProcess
FROM
(
SELECT JobId
,ProcessingComplete
,SignalValueStagingTableName AS 'TableName'
,(DATEDIFF(SECOND, (SELECT last_user_update
FROM sys.dm_db_index_usage_stats
WHERE database_id = DB_ID(DB_NAME())
AND OBJECT_ID = OBJECT_ID(SignalValueStagingTableName))
,GETUTCDATE())) SecondsSinceLastUpdate
FROM SignalValueJobs
) cte
WHERE cte.ProcessingComplete = 1
OR cte.SecondsSinceLastUpdate >= 120
DECLARE @i INT = (SELECT COUNT(*) FROM #JobsToProcess)
DECLARE @jobParam UNIQUEIDENTIFIER
DECLARE @currentTable NVARCHAR(128)
DECLARE @processingParam BIT
DECLARE @sqlStatement NVARCHAR(2048)
DECLARE @paramDefinitions NVARCHAR(500) = N'@currentJob UNIQUEIDENTIFIER, @processingComplete BIT'
DECLARE @qualifiedTableName NVARCHAR(128)
WHILE @i > 0
BEGIN
SELECT @jobParam = JobId, @currentTable = TableName, @processingParam = ProcessingComplete
FROM #JobsToProcess
WHERE RowIndex = @i
SET @qualifiedTableName = '[Database_Staging].[dbo].['+@currentTable+']'
SET @sqlStatement = N'
--Signal values staging table.
SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
ON smd.SignalId = svs.SignalId
INSERT INTO SignalValues SELECT * FROM #sValues
SELECT DISTINCT SignalId INTO #uniqueIdentifiers FROM #sValues
DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId
DROP TABLE #sValues
DROP TABLE #uniqueIdentifiers
IF NOT EXISTS (SELECT TOP 1 1 FROM '+ @qualifiedTableName +') --table is empty
BEGIN
-- processing is completed so drop the table and remvoe the entry
IF @processingComplete = 1
BEGIN
DELETE FROM SignalValueJobs WHERE JobId = @currentJob
IF '''+@currentTable+''' <> ''SignalValues_staging''
BEGIN
DROP TABLE '+ @qualifiedTableName +'
END
END
END
'
EXEC sp_executesql @sqlStatement, @paramDefinitions, @currentJob = @jobParam, @processingComplete = @processingParam;
SET @i = @i - 1
END
DROP TABLE #JobsToProcess
Lo uso sp_executesql
porque los nombres de las tablas para las tablas de preparación vienen como texto de los registros en la tabla de trabajos.
Este procedimiento almacenado se ejecuta cada 2 segundos utilizando el truco que aprendí de esta publicación de dba.stackexchange.com .
El problema que no puedo resolver durante toda mi vida es la velocidad a la que se realizan las inserciones en la producción. Mi aplicación crea tablas temporales y las llena de registros increíblemente rápido. La inserción en producción no puede mantenerse al día con la cantidad de tablas y eventualmente hay un excedente de tablas en miles. La única forma en que he podido mantenerme al día con los datos entrantes es eliminar todas las claves, índices, restricciones, etc. de la SignalValues
tabla de producción . El problema que enfrento es que la tabla termina con tantos registros que resulta imposible consultar.
He intentado particionar la tabla usando la [Timestamp]
columna como partición en vano. Cualquier forma de indexación ralentiza tanto las inserciones que no pueden seguir el ritmo. Además, necesitaría crear miles de particiones (una por minuto, ¿hora?) Con años de anticipación. No pude descubrir cómo crearlos sobre la marcha
He intentado crear la partición mediante la adición de una columna calculada a la tabla llamada TimestampMinute
cuyo valor era, en INSERT
, DATEPART(MINUTE, GETUTCDATE())
. Todavía muy lento.
Intenté convertirlo en una tabla con memoria optimizada según este artículo de Microsoft . Tal vez no entiendo cómo hacerlo, pero el MOT hizo que los insertos fueran más lentos de alguna manera.
Revisé el plan de ejecución del procedimiento almacenado y descubrí que (¿creo?) La operación más intensiva es
SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
ON smd.SignalId = svs.SignalId
Para mí esto no tiene sentido: he agregado el registro de reloj de pared al procedimiento almacenado que demostró lo contrario.
En términos de registro de tiempo, esa declaración particular anterior se ejecuta en ~ 300 ms en 100k registros.
La declaración
INSERT INTO SignalValues SELECT * FROM #sValues
Se ejecuta en 2500-3000ms en 100k registros. Eliminar de la tabla los registros afectados, por:
DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId
Toma otros 300ms.
¿Cómo puedo hacer esto más rápido? ¿Puede SQL Server manejar miles de millones de registros por día?
Si es relevante, este es SQL Server 2014 Enterprise x64.
Configuración de hardware:
Olvidé incluir hardware en el primer paso de esta pregunta. Culpa mía.
Prefacio esto con estas declaraciones: Sé que estoy perdiendo algo de rendimiento debido a mi configuración de hardware. Lo he intentado muchas veces, pero debido al presupuesto, el nivel C, la alineación de los planetas, etc., desafortunadamente no hay nada que pueda hacer para obtener una mejor configuración. El servidor se ejecuta en una máquina virtual y ni siquiera puedo aumentar la memoria porque simplemente no tenemos más.
Aquí está la información de mi sistema:
El almacenamiento está conectado al servidor VM a través de la interfaz iSCSI a una caja NAS (esto degradará el rendimiento). La caja NAS tiene 4 unidades en una configuración RAID 10. Son unidades de disco giratorio de 4 TB WD WD4000FYYZ con interfaz SATA de 6 GB / s. El servidor solo tiene un almacén de datos configurado, por lo que tempdb y mi base de datos están en el mismo almacén de datos.
Max DOP es cero. ¿Debo cambiar esto a un valor constante o simplemente dejar que SQL Server lo maneje? Leí sobre RCSI: ¿Estoy en lo cierto al suponer que el único beneficio de RCSI viene con las actualizaciones de fila? Nunca habrá actualizaciones para ninguno de estos registros particulares, serán INSERT
editados y SELECT
editados. ¿RCSI todavía me beneficiará?
Mi tempdb es de 8mb. Basado en la respuesta a continuación de jyao, cambié los #sValues a una tabla regular para evitar tempdb por completo. Sin embargo, el rendimiento fue casi el mismo. Intentaré aumentar el tamaño y el crecimiento de tempdb, pero dado que el tamaño de #sValues será más o menos siempre el mismo tamaño, no preveo mucha ganancia.
He tomado un plan de ejecución que adjunto a continuación. Este plan de ejecución es una iteración de una tabla de etapas: 100k registros. La ejecución de la consulta fue bastante rápida, alrededor de 2 segundos, pero tenga en cuenta que esto no tiene índices en la SignalValues
tabla y la SignalValues
tabla, el objetivo de la INSERT
, no tiene registros.
Respuestas:
De su captura de pantalla, SOLO tiene 8 GB de memoria RAM total y 6 GB asignados a SQL Server. Esto es demasiado bajo para lo que está tratando de lograr.
Le sugiero que actualice la memoria a un valor más alto: 256 GB y aumente también sus CPU VM.
En este punto, debe invertir en hardware para su carga de trabajo.
Consulte también la guía de rendimiento de carga de datos : describe formas inteligentes de cargar eficientemente los datos.
Según su edición ... debe tener un tempdb sensible, preferiblemente varios archivos de datos tempdb del mismo tamaño junto con TF 1117 y 1118 habilitados para toda la instancia.
Le sugiero que obtenga un chequeo de salud profesional y comience desde allí.
Altamente recomendado
Aumente las especificaciones de su servidor.
Pídale a una persona profesional * que realice un control de salud de la instancia del servidor de su base de datos y siga las recomendaciones.
Una vez. y B. terminados, luego sumérgete en el ajuste de consultas y otras optimizaciones como mirar estadísticas de espera, planes de consulta, etc.
HTH.
fuente
Consejos generales para tales problemas con big-data, cuando se enfrenta a una pared y nada funciona:
Un huevo se va a cocinar 5 minutos aproximadamente. Se cocinarán 10 huevos al mismo tiempo si hay suficiente electricidad y agua.
O, en otras palabras:
Primero, mira el hardware; segundo, separe la lógica del proceso (remodelación de datos) y hágalo en paralelo.
Es muy posible crear particiones verticales personalizadas de forma dinámica y automatizada, por cuenta de tabla y por tamaño de tabla; Si tengo Quarter_1_2017, Quarter_2_2017, Quarter_3_2017, Quarter_4_2017, Quarter_1_2018 ... y no sé dónde están mis registros y cuántas particiones tengo, ejecute las mismas consultas en todas las particiones personalizadas al mismo tiempo, sesiones separadas y ensamblaje El resultado se procesará para mi lógica.
fuente
Haré la siguiente verificación / optimización:
Asegúrese de que los archivos de datos y registros de la base de datos de producción no crezcan durante la operación de inserción (pre-crezca si es necesario)
No utilice
pero en su lugar, predefinir la [tabla dest]. Además, en lugar de soltar la [tabla dest] y volver a crearla, truncaré la tabla. De esta manera, si fuera necesario, en lugar de usar la tabla temporal, usaría la tabla regular. (También puedo crear el índice en [tabla dest] para facilitar el rendimiento de la consulta de combinación)
En lugar de usar sql dinámico, prefiero usar nombres de tabla codificados con alguna lógica de codificación para elegir qué tabla operar.
También supervisaré el rendimiento de E / S de la memoria, la CPU y el disco para ver si hay inanición de recursos durante una gran carga de trabajo.
Como mencionó que puede manejar la inserción soltando los índices en el lado de producción, comprobaría si hay muchas divisiones de página, de ser así, disminuiría el factor de relleno de los índices y reconstruiría los índices antes de considerar la posibilidad de soltarlos. Los índices.
Buena suerte y me encanta tu pregunta.
fuente