Я рекомендую использовать разновидность метода 2 и разбить диапазон поиска на множество целевых таблиц. 10000 столов - хорошая первая попытка. Например, если вы ищете «012345678901», тогда ваш запрос будет искать в таблице, связанной с данными, которые начинаются с «0123». Всего будет по-прежнему триллион строк, но разделение данных на множество таблиц имеет следующие положительные качества:
- Все возможные 12-значные строки теперь могут помещаться в INT.
- Создание более эффективного поискового представления вашей строки размером 1 ТБ, вероятно, будет дорогостоящим, несмотря ни на что. Имея множество таблиц, вы можете легко распараллелить работу и даже временно запросить выделение большего количества процессоров для вашей виртуальной машины.
- Вы можете создать отдельную таблицу в качестве доказательства концепции для определения времени запроса и требований к общему пространству для полной строки.
- Если вам когда-нибудь понадобится какое-либо обслуживание базы данных, вы будете рады, что не создали одну огромную таблицу.
На данный момент главный вопрос для меня заключается в том, используете ли вы сжатое хранилище строк или columnstore. Приведенный ниже код создает таблицу хранилища строк для пространства поиска «0123» и вставляет в нее 100 миллионов строк. Если ваша строка достаточно случайная, вы также можете увидеть около 100 миллионов строк в таблице.
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 ТБ. Хорошей новостью является то, что запросы занимают у меня всего 1 мс, даже если в буферном кеше нет соответствующих данных, что почти всегда будет иметь место для такого большого набора данных, как ваш.
-- 1 ms
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_PAGE_COMPRESSION
WHERE STRING_PIECE = 45678901; -- searching for '012345678901'
Вы, вероятно, можете выбросить эти данные в самое дешевое хранилище, которое у вас есть, и при этом по-прежнему видеть хорошее время отклика, так как запрос выполняет так мало логических операций чтения.
Что касается columnstore, вы не можете искать нужные вам данные, и все равно крайне маловероятно, что вы поместите все свои данные в память, поэтому важно читать как можно меньше сжатых данных в ваших запросах. Я настоятельно рекомендую разделить ваши таблицы. Один простой подход, который хорошо работает, состоит в том, чтобы использовать первые четыре цифры строки поиска, чтобы найти имя таблицы и следующие две цифры в качестве раздела. Снова используя «012345678901», вы перейдете к разделу 45 таблицы, в которой содержатся данные для «0123». 100 разделов - это хорошее число, чтобы избежать проблем, вызванных слишком большим количеством разделов, и в среднем у вас будет около 1 миллиона строк на каждый раздел. Максимальное количество строк, которое может поместиться в одну группу строк, составляет 1048576, поэтому при таком подходе вы будете выполнять как можно меньше операций ввода-вывода.
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 ТБ. Мне не понятно, как сделать это меньше. В этом случае поисковый запрос немного медленнее. На моей машине это занимает около 25 мс, но это в основном зависит от ввода-вывода:
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
SELECT MIN(STR_POS)
FROM dbo.T0123_Q229892_COLUMNSTORE
WHERE PART_ID = 45
AND STRING_PIECE = 678901; -- searching for '012345678901'
Одно важное замечание о подходе columnstore заключается в том, что показатель 10,9 ТБ предназначен для 100% сжатых данных. Будет сложно заполнить такую таблицу эффективно, избегая при этом магазинов дельты. Вполне вероятно, что в какой-то момент процесса вы получите несжатые данные в дельта-хранилищах, для которых может потребоваться больше, чем 15,4 ТБ, использованных для хранилища строк.