Проблема заключается в следующем:
- Входные данные: все статьи из Википедии (33 ГБ текста)
- Вывод: Количество скипграмм каждого слова (n-грамм с максимальным пропуском k) из Википедии в файле SQLite.
Схема таблицы вывода:
CREATE TABLE [tokens] ([token] TEXT UNIQUE NOT NULL PRIMARY KEY, [count] INTEGER NOT NULL
Наивный подход заключается в том, что для каждой скипграммы мы создаем новую запись в таблице или счетчик приращений в существующей записи:
INSERT OR REPLACE INTO [tokens] VALUES (@token, COALESCE((SELECT count FROM [tokens] WHERE token=@token), 0) + 1)
Проблема этого подхода заключается в том, что индекс постоянно обновляется, и когда база данных увеличивается до нескольких гигабайт, эти обновления выполняются очень медленно. Мы можем решить эту проблему, создав таблицу «токенов» без индекса и добавив индекс в конце обработки.
Проблема в том, что оператор выбора SELECT count FROM [tokens] WHERE token=@token
, который должен сканировать таблицу, значительно снижает производительность.
Лучший метод, который я нашел, заключается в следующем (я использую C #):
Создайте Dictionary<string,int>
для подсчета токенов.
Добавляйте токены в этот словарь, пока он не станет слишком большим, чтобы поместиться в ОЗУ.
Вставить (не обновлять) токены из словаря во временную таблицу без индекса. Таблица имеет следующую схему:
CREATE TABLE [temp] ([token] TEXT, [count] INTEGER)
Если есть еще токены, очистите словарь и перейдите к шагу 2.
Копирование токенов из временной таблицы в таблицу токенов:
INSERT INTO [tokens] SELECT [token], SUM([count]) AS [count] FROM [temp] GROUP BY [token]
Этот метод занимает "всего лишь" 24 часа для обработки набора данных, но я считаю, что это не лучший подход, потому что шаг 5 занимает 22 из 24 часов.
Знаете ли вы альтернативный подход, который может решить эту проблему?
P.S. Мое приложение однопоточное, и я делаю вышеупомянутые вставки в пакетах (100000 на пакет) в рамках транзакции.