方法2のフレーバーを使用し、検索範囲を多数のターゲットテーブルに分割することをお勧めします。最初の試みとしては、10000テーブルが適切です。たとえば、「012345678901」を検索すると、クエリは「0123」で始まるデータに関連付けられたテーブルを調べます。合計で約1兆行になりますが、データを多くのテーブルに分割すると、次のようなメリットがあります。
- 可能なすべての12桁の文字列がINTに収まるようになりました。
- 1 TB文字列の検索効率の高い表現を作成すると、何があってもコストがかかる可能性があります。多くのテーブルを使用すると、ジョブを簡単に並列化でき、VMに割り当てるCPUを一時的に増やすこともできます。
- 完全な文字列のクエリ時間と合計スペース要件を決定するための概念実証として、単一のテーブルを構築できます。
- なんらかのデータベースメンテナンスを行う必要がある場合は、巨大なテーブルを1つも作成していなくても大丈夫です。
この時点での主な質問は、圧縮された行ストアと列ストアのどちらを使用するかです。以下のコードは、「0123」検索スペース用の行ストアテーブルを作成し、1億行を挿入します。文字列が十分にランダムである場合、テーブルごとに約1億行が表示されることも期待できます。
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;
悪いニュースは、おそらく約15.4 TBが必要となる完全なデータセットです。良いニュースは、関連するデータがバッファキャッシュにない場合でも、クエリが1ミリ秒しかかからないことです。これは、ほとんどの場合、データセットのサイズが大きい場合に当てはまります。
-- 1 ms
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_PAGE_COMPRESSION
WHERE STRING_PIECE = 45678901; -- searching for '012345678901'
クエリで論理的な読み取りがほとんど行われないため、このデータを最も安価なストレージに投入し、応答時間を改善することができます。
列ストアの場合、必要なデータを探すことができず、すべてのデータをメモリに収めることが非常に困難であるため、クエリでできるだけ少ない圧縮データを読み取ることが重要です。テーブルを分割することを強くお勧めします。うまく機能する簡単な方法の1つは、検索文字列の最初の4桁を使用してテーブル名と次の2桁をパーティションとして見つけることです。もう一度「012345678901」を使用すると、「0123」のデータを保持するテーブルのパーティション45に移動します。100パーティションは、パーティションが多すぎるために発生する問題を回避するのに適した数であり、各パーティションの平均で約100万行になります。1つの行グループに収まる行の最大数は1048576なので、このアプローチでは、IOをできるだけ少なくします。
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
このアプローチでは、完全なデータセットには約10.9 TBが必要になります。それを小さくする方法は私には明らかではありません。この場合、検索クエリは少し遅くなります。私のマシンでは約25ミリ秒かかりますが、これは主にIOに依存します。
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_COLUMNSTORE
WHERE PART_ID = 45
AND STRING_PIECE = 678901; -- searching for '012345678901'
列ストアアプローチに関する重要な注意点の1つは、10.9 TBの数値は100%圧縮されたデータに対するものであるということです。デルタストアを回避しながら、このようなテーブルに効率的にデータを投入することは困難です。プロセスのある時点で、デルタストアに圧縮されていないデータができる可能性があります。これは、行ストアアプローチに使用される15.4 TB以上を簡単に必要とする可能性があります。