Raccomando di utilizzare una variante del metodo 2 e suddividere l'intervallo di ricerca in molte tabelle di destinazione. 10000 tavoli è un buon primo tentativo. Ad esempio, se si cerca "012345678901", la query esaminerebbe la tabella associata ai dati che iniziano con "0123". Avresti ancora circa un trilione di righe in totale, ma la suddivisione dei dati in molte tabelle ha le seguenti qualità positive:
- Tutte le possibili stringhe a 12 cifre ora possono adattarsi a un INT.
- Costruire una rappresentazione più efficiente in termini di ricerca della stringa da 1 TB sarà probabilmente costoso, qualunque cosa accada. Con molte tabelle, è possibile parallelizzare facilmente il lavoro e persino richiedere temporaneamente l'assegnazione di più CPU alla VM.
- È possibile creare una singola tabella come prova di concetto per determinare i tempi di query e i requisiti di spazio totale per l'intera stringa.
- Se hai mai bisogno di eseguire qualsiasi tipo di manutenzione del database, sarai felice di non aver creato una tabella enorme.
A questo punto, la domanda principale per me è se usi un archivio righe o un archivio colonne compressi. Il codice seguente crea una tabella di archivio righe per lo spazio di ricerca "0123" e vi inserisce 100 milioni di righe. Se la tua stringa è abbastanza casuale, potresti anche aspettarti di vedere circa 100 milioni di righe per tabella.
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 cattiva notizia è per il set completo di dati di cui probabilmente avresti bisogno intorno a 15,4 TB. La buona notizia è che le query richiedono solo 1 ms per me anche quando non ci sono dati rilevanti nella cache del buffer, il che sarà quasi sempre il caso di un set di dati grande come il tuo.
-- 1 ms
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_PAGE_COMPRESSION
WHERE STRING_PIECE = 45678901; -- searching for '012345678901'
Probabilmente puoi lanciare questi dati sullo spazio di archiviazione più economico che hai e vedere ancora buoni tempi di risposta poiché la query esegue così poche letture logiche.
Per il columnstore, non puoi cercare i dati di cui hai bisogno e sei ancora estremamente improbabile di adattare tutti i tuoi dati in memoria, quindi è importante leggere il minor numero possibile di dati compressi con le tue query. Consiglio vivamente di partizionare i tuoi tavoli. Un approccio semplice che funziona bene consiste nell'utilizzare le prime quattro cifre della stringa di ricerca per trovare il nome della tabella e le due cifre successive come partizione. Utilizzando di nuovo "012345678901", si passerà alla partizione 45 della tabella che contiene i dati per "0123". 100 partizioni sono un buon numero per evitare problemi causati da troppe partizioni e in media finirai con circa 1 milione di righe per ogni partizione. Il numero massimo di righe che possono rientrare in un singolo rowgroup è 1048576, quindi con questo approccio eseguirai il minor numero di IO possibile.
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 questo approccio l'intero set di dati richiederebbe circa 10,9 TB. Non mi è chiaro come ridurlo. La query di ricerca è un po 'più lenta in questo caso. Sulla mia macchina ci vogliono circa 25 ms, ma questo dipenderà principalmente da 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 sull'approccio columnstore è che la cifra di 10,9 TB è per dati compressi al 100%. Sarà difficile popolare una tabella del genere in modo efficiente evitando i negozi delta. È probabile che finirai con dati non compressi negli archivi delta a un certo punto del processo che potrebbero facilmente richiedere più di 15,4 TB utilizzati per l'approccio del negozio di file.