Eu recomendo usar o método 2 e dividir o intervalo de pesquisa em várias tabelas de destino. 10000 tabelas é uma boa primeira tentativa. Por exemplo, se você procurar "012345678901", sua consulta examinará a tabela associada aos dados que começam com "0123". Você ainda teria cerca de um trilhão de linhas no total, mas dividir os dados em muitas tabelas tem as seguintes qualidades positivas:
- Todas as seqüências de 12 dígitos possíveis agora podem caber em uma INT.
- Construir uma representação mais eficiente da pesquisa de sua cadeia de 1 TB provavelmente será caro, não importa o quê. Com muitas tabelas, você pode facilmente paralelizar o trabalho e até solicitar temporariamente que mais CPUs sejam alocadas à sua VM.
- Você pode criar uma única tabela como prova de conceito para determinar o tempo de consulta e os requisitos de espaço total para a sequência completa.
- Se você precisar fazer algum tipo de manutenção no banco de dados, ficará feliz por não ter criado uma tabela enorme.
Neste ponto, a principal questão para mim é se você usa o rowstore compactado ou o columnstore. O código abaixo cria uma tabela rowstore para o espaço de pesquisa "0123" e insere 100 milhões de linhas nele. Se sua sequência for aleatória o suficiente, você também poderá ver cerca de 100 milhões de linhas por tabela.
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;
A má notícia é que, para o conjunto de dados completo, você provavelmente precisará de cerca de 15,4 TB. A boa notícia é que as consultas levam apenas 1 ms para mim, mesmo quando não há dados relevantes no cache do buffer, o que quase sempre será o caso de um conjunto de dados tão grande quanto o seu.
-- 1 ms
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_PAGE_COMPRESSION
WHERE STRING_PIECE = 45678901; -- searching for '012345678901'
Provavelmente, você pode lançar esses dados no armazenamento mais barato que você possui e ainda obter bons tempos de resposta, pois a consulta faz poucas leituras lógicas.
Para o columnstore, você não pode procurar os dados necessários e ainda é extremamente improvável que encaixe todos os dados na memória; portanto, é importante ler o mínimo possível de dados compactados com suas consultas. Eu recomendo particionar suas tabelas. Uma abordagem simples que funciona bem é usar os quatro primeiros dígitos da sua sequência de pesquisa para encontrar o nome da tabela e os próximos dois dígitos como a partição. Usando "012345678901" novamente, você iria para a partição 45 da tabela que contém dados para "0123". 100 partições é um bom número para evitar problemas causados por muitas partições e você terá, em média, cerca de 1 milhão de linhas para cada partição. O número máximo de linhas que podem caber em um único grupo de linhas é 1048576, portanto, com essa abordagem, você fará o mínimo de IO possível.
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
Com essa abordagem, o conjunto completo de dados exigiria cerca de 10,9 TB. Não está claro para mim como fazer isso menor. A consulta de pesquisa é um pouco mais lenta nesse caso. Na minha máquina, são necessários cerca de 25 ms, mas isso depende principalmente do IO:
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_COLUMNSTORE
WHERE PART_ID = 45
AND STRING_PIECE = 678901; -- searching for '012345678901'
Uma observação importante sobre a abordagem columnstore é que o valor de 10,9 TB refere-se a 100% de dados compactados. Será um desafio preencher essa tabela com eficiência, evitando as lojas delta. É provável que você acabe com dados não compactados em lojas delta em algum momento do processo, o que poderia facilmente exigir mais do que os 15,4 TB usados na abordagem de armazenamento de linhas.