Ich empfehle die Verwendung einer Variante von Methode 2 und die Aufteilung des Suchbereichs in viele Zieltabellen. 10000 Tische sind ein guter erster Versuch. Wenn Sie beispielsweise nach "012345678901" suchen, wird in Ihrer Abfrage die Tabelle angezeigt, die Daten zugeordnet ist, die mit "0123" beginnen. Sie hätten immer noch insgesamt etwa eine Billion Zeilen, aber die Aufteilung der Daten in viele Tabellen hat die folgenden positiven Eigenschaften:
- Alle möglichen 12-stelligen Zeichenfolgen können jetzt in eine INT passen.
- Das Erstellen einer sucheffizienteren Darstellung Ihrer 1-TB-Zeichenfolge wird wahrscheinlich teuer, egal was passiert. Mit vielen Tabellen können Sie den Job problemlos parallelisieren und sogar vorübergehend nach mehr CPUs fragen, die Ihrer VM zugewiesen werden sollen.
- Sie können eine einzelne Tabelle als Proof-of-Concept erstellen, um die Abfragezeit und den Gesamtplatzbedarf für die vollständige Zeichenfolge zu bestimmen.
- Wenn Sie jemals eine Datenbankwartung durchführen müssen, sind Sie froh, dass Sie keine einzige große Tabelle erstellt haben.
An dieser Stelle ist die Hauptfrage für mich, ob Sie einen komprimierten Zeilen- oder Spaltenspeicher verwenden. Der folgende Code erstellt eine Rowstore-Tabelle für den Suchbereich "0123" und fügt 100 Millionen Zeilen ein. Wenn Ihre Zeichenfolge zufällig genug ist, können Sie auch mit etwa 100 Millionen Zeilen pro Tabelle rechnen.
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;
Die schlechte Nachricht ist, dass Sie für den gesamten Datensatz wahrscheinlich etwa 15,4 TB benötigen. Die gute Nachricht ist, dass Abfragen für mich nur 1 ms dauern, selbst wenn sich keine relevanten Daten im Puffercache befinden, was bei einem so großen Datensatz wie Ihrem fast immer der Fall ist.
-- 1 ms
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_PAGE_COMPRESSION
WHERE STRING_PIECE = 45678901; -- searching for '012345678901'
Sie können diese Daten wahrscheinlich auf den billigsten Speicher werfen, den Sie haben, und trotzdem gute Antwortzeiten sehen, da die Abfrage so wenige logische Lesevorgänge ausführt.
Beim Spaltenspeicher können Sie nicht nach den Daten suchen, die Sie benötigen, und es ist äußerst unwahrscheinlich, dass alle Ihre Daten in den Speicher passen. Daher ist es wichtig, bei Ihren Abfragen so wenig komprimierte Daten wie möglich zu lesen. Ich empfehle dringend, Ihre Tabellen zu partitionieren. Ein einfacher Ansatz, der gut funktioniert, besteht darin, die ersten vier Ziffern Ihrer Suchzeichenfolge zu verwenden, um den Tabellennamen und die nächsten zwei Ziffern als Partition zu finden. Wenn Sie erneut "012345678901" verwenden, gelangen Sie zur Partition 45 der Tabelle, die Daten für "0123" enthält. 100 Partitionen sind eine gute Zahl, um Probleme zu vermeiden, die durch zu viele Partitionen verursacht werden, und Sie erhalten durchschnittlich 1 Million Zeilen für jede Partition. Die maximale Anzahl von Zeilen, die in eine einzelne Zeilengruppe passen, beträgt 1048576, sodass Sie mit diesem Ansatz so wenig E / A wie möglich ausführen.
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
Bei diesem Ansatz würde der vollständige Datensatz etwa 10,9 TB erfordern. Mir ist nicht klar, wie ich das kleiner machen soll. Die Suchabfrage ist in diesem Fall etwas langsamer. Auf meinem Computer dauert es ungefähr 25 ms, dies hängt jedoch hauptsächlich von der E / A ab:
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_COLUMNSTORE
WHERE PART_ID = 45
AND STRING_PIECE = 678901; -- searching for '012345678901'
Ein wichtiger Hinweis zum Columnstore-Ansatz ist, dass die 10,9-TB-Zahl für 100% komprimierte Daten gilt. Es wird schwierig sein, eine solche Tabelle effizient zu füllen und gleichzeitig die Delta-Speicher zu meiden. Es ist wahrscheinlich, dass Sie zu einem bestimmten Zeitpunkt unkomprimierte Daten in Delta-Speichern haben, für die möglicherweise mehr als 15,4 TB für den Rowstore-Ansatz erforderlich sind.