Recomiendo usar una muestra del método 2 y dividir el rango de búsqueda en muchas tablas de destino. 10000 mesas es un buen primer intento. Por ejemplo, si busca "012345678901", su consulta vería la tabla asociada con los datos que comienzan con "0123". Todavía tendría aproximadamente un billón de filas en total, pero dividir los datos en muchas tablas tiene las siguientes cualidades positivas:
- Todas las cadenas de 12 dígitos posibles ahora pueden caber en un INT.
- Construir una representación más eficiente en la búsqueda de su cadena de 1 TB probablemente sea costoso, pase lo que pase. Con muchas tablas, puede paralelizar fácilmente el trabajo e incluso solicitar temporalmente que se asignen más CPU a su VM.
- Puede crear una sola tabla como prueba de concepto para determinar el tiempo de consulta y los requisitos de espacio total para la cadena completa.
- Si alguna vez necesita realizar algún tipo de mantenimiento de la base de datos, se alegrará de no haber creado una tabla enorme.
En este punto, la pregunta principal para mí es si usa el almacén de filas comprimido o el almacén de columnas. El siguiente código crea una tabla de almacén de filas para el espacio de búsqueda "0123" e inserta 100 millones de filas en él. Si su cadena es lo suficientemente aleatoria, también podría esperar ver aproximadamente 100 millones de filas por tabla.
DROP TABLE IF EXISTS #t;
SELECT TOP (10000) 0 ID INTO #t
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);
DROP TABLE IF EXISTS dbo.Q229892_RAW_100M_RANGE;
CREATE TABLE dbo.Q229892_RAW_100M_RANGE (
STRING_PIECE INT NOT NULL,
STR_POS BIGINT NOT NULL
);
INSERT INTO dbo.Q229892_RAW_100M_RANGE WITH (TABLOCK)
SELECT ABS(CHECKSUM(NEWID()) % 100000000),
TRY_CAST(ABS(CHECKSUM(NEWID())) AS BIGINT) * TRY_CAST(ABS(CHECKSUM(NEWID())) AS BIGINT)
FROM #t t1
CROSS JOIN #t t2
OPTION (MAXDOP 4);
DROP TABLE IF EXISTS dbo.T0123_Q229892_PAGE_COMPRESSION;
CREATE TABLE dbo.T0123_Q229892_PAGE_COMPRESSION (
STRING_PIECE INT NOT NULL,
STR_POS BIGINT NOT NULL,
PRIMARY KEY (STRING_PIECE, STR_POS)
) WITH (DATA_COMPRESSION = PAGE);
INSERT INTO dbo.T0123_Q229892_PAGE_COMPRESSION WITH (TABLOCK)
SELECT STRING_PIECE, STR_POS
FROM dbo.Q229892_RAW_100M_RANGE;
La mala noticia es que para el conjunto de datos completo probablemente necesitará alrededor de 15.4 TB. La buena noticia es que las consultas solo toman 1 ms para mí, incluso cuando no hay datos relevantes en la memoria caché del búfer, que casi siempre será el caso para un conjunto de datos tan grande como el suyo.
-- 1 ms
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_PAGE_COMPRESSION
WHERE STRING_PIECE = 45678901; -- searching for '012345678901'
Probablemente pueda arrojar estos datos en el almacenamiento más barato que tenga y aún así ver buenos tiempos de respuesta ya que la consulta realiza muy pocas lecturas lógicas.
Para el almacén de columnas, no puede buscar los datos que necesita y aún es extremadamente improbable que guarde todos sus datos en la memoria, por lo que es importante leer la menor cantidad de datos comprimidos posible con sus consultas. Recomiendo particionar sus tablas. Un enfoque simple que funciona bien es usar los primeros cuatro dígitos de su cadena de búsqueda para encontrar el nombre de la tabla y los siguientes dos dígitos como partición. Usando "012345678901" nuevamente, iría a la partición 45 de la tabla que contiene datos para "0123". 100 particiones es un buen número para evitar problemas causados por demasiadas particiones y terminará con aproximadamente 1 millón de filas para cada partición en promedio. El número máximo de filas que pueden caber en un solo grupo de filas es 1048576, por lo que con este enfoque, hará la menor cantidad de E / S posible.
DROP TABLE IF EXISTS dbo.Q229892_RAW_1M_RANGE;
CREATE TABLE dbo.Q229892_RAW_1M_RANGE (
STRING_PIECE INT NOT NULL,
STR_POS BIGINT NOT NULL
);
INSERT INTO dbo.Q229892_RAW_1M_RANGE WITH (TABLOCK)
SELECT TOP (1000000) ABS(CHECKSUM(NEWID()) % 1000000),
TRY_CAST(ABS(CHECKSUM(NEWID())) AS BIGINT) * TRY_CAST(ABS(CHECKSUM(NEWID())) AS BIGINT)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);
DECLARE @IntegerPartitionFunction nvarchar(max) =
N'CREATE PARTITION FUNCTION partition100 (tinyint)
AS RANGE LEFT FOR VALUES (';
DECLARE @i int = 0;
WHILE @i < 100
BEGIN
SET @IntegerPartitionFunction += CAST(@i as nvarchar(10)) + N', ';
SET @i += 1;
END
SET @IntegerPartitionFunction += CAST(@i as nvarchar(10)) + N');';
EXEC sp_executesql @IntegerPartitionFunction;
GO
CREATE PARTITION SCHEME partition100_scheme
AS PARTITION partition100
ALL TO ([DEFAULT]);
DROP TABLE IF EXISTS dbo.T0123_Q229892_COLUMNSTORE;
-- this table must be partitioned by PART_ID!
CREATE TABLE dbo.T0123_Q229892_COLUMNSTORE (
PART_ID TINYINT NOT NULL,
STRING_PIECE INT NOT NULL,
STR_POS BIGINT NOT NULL,
INDEX CCS CLUSTERED COLUMNSTORE
) ON partition100_scheme (PART_ID);
GO
DECLARE @part_id TINYINT = 0;
SET NOCOUNT ON;
WHILE @part_id < 100
BEGIN
INSERT INTO dbo.T0123_Q229892_COLUMNSTORE WITH (TABLOCK)
SELECT @part_id, STRING_PIECE, STR_POS
FROM dbo.Q229892_RAW_1M_RANGE
OPTION (MAXDOP 1);
SET @part_id = @part_id + 1;
END;
GO
Con este enfoque, el conjunto de datos completo requeriría aproximadamente 10,9 TB. No me queda claro cómo hacerlo más pequeño. La consulta de búsqueda es un poco más lenta en este caso. En mi máquina tarda unos 25 ms, pero esto dependerá principalmente de IO:
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_COLUMNSTORE
WHERE PART_ID = 45
AND STRING_PIECE = 678901; -- searching for '012345678901'
Una nota importante sobre el enfoque de almacén de columnas es que la cifra de 10,9 TB es para datos 100% comprimidos. Será un desafío poblar dicha tabla de manera eficiente mientras se evitan las tiendas delta. Es probable que termine con datos sin comprimir en almacenes delta en algún momento del proceso que fácilmente podría requerir más de los 15.4 TB utilizados para el enfoque de almacén de filas.