Принимайте участие в зарплатном опросе! Уже собрано почти 8 000 анкет.
×Закрыть

Параллельная загрузка данных в Neo4j

Просто решил поделиться.

Попался мне проект от заказчика из Дании, наверное, учёного.

У него был 500 Мб файл с 11 млн строк, в котором всего 3 колонки: код протеина 1, код протеина 2 и какое-то число, означавшее связь между ними.

Он хотел загрузить эти данные в графовую базу данных Neo4j, чтобы считать кратчайшие пути по графу.

Проблема была в том, что он пытался загрузить это стандартными средствами, и по моим прикидкам, на это ушло бы 600 часов.

proteins

500 Мб данных — как бы немного, но последовательно вставить 11 млн записей означало найти нужные ноды, заблокировать их и создать объект связи между ними. Это сильно тормозит процесс.

Я взялся попробовать.

Сначала я уменьшил имена протеинов: вместо строкового представления типа «9606.ENSP00000000233» (по согласию клиента) было решено использовать только числовую часть, то есть в этом примере просто «233».

Размер файла сильно уменьшился, плюс я рассчитывал на то, что если сделать поле с этим кодом в Neo4j числовым, а не текстовым, то и размер базы и поисковых индексов будет меньше, т.е. быстрее.

Я попробовал загружать этот файл паралелльно в несколько потоков. Работало быстро, но практически ничего не записывало в базу — если мы говорим базе данных «запиши: протеин А связан с протеином В», то оба эти объекта блокируются для записи, и все остальные параллельные попытки связать протеин А с протеинами C, D и E просто идут в никуда — нельзя записывать то, что заблокировано (а ждать Neo4j не хочет).

То есть работало быстро, но неправильно.

Долго возился я с разными вариациями, в итоге пришел к такой идее: эта база данных умеет загружать сразу большой блок данных, например, 1000 строк, но если в этом блоке будут повторяющиеся протеины, то часть данных запишется, а часть пропадёт (из-за описанных выше блокировок).

Идея была в том, чтобы перегруппировать 11 млн записей группами по 1000 строк. Так, чтобы в каждой группе не было повторяющихся протеинов. Тогда все эти 1000 строк могут быть записаны параллельно. Потом берётся следующая группа из 1000 строк.

Перегруппировка шла медленно, так что я оставил скрипт на ночь. Утром попробовал загрузить результат: вместо 600 часов данные загружаются за 8.5 минут.

Когда уже знаешь, «как надо было», то это очевидно, а вот прийти к правильной идее самому — неоценимое удовольствие.

LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

треба загружати батчами-порціями, для прикладу по 10к даних в одному батчі

Просто интересно, а что, стандартные тулзы типа neo4j-import (или как оно там правильно зовется) эту задачу не решают? Если исходник трансформировать в два — отдельно ноды, отдельно связи — и скормить штатному лоадеру... Я помню где-то читал пример загрузки десятка миллионов записей за три минуты или около того. У вас порядок цифр на загрузку соответствует, но к нему нужно добавить ночь на подготовку данных (а трансформировать связи в нужный формат и вылить ноды в отдельный файл можно по логике за одну итерацию — то есть еще плюс несколько минут).

вылить ноды в отдельный файл

Это я сделал. Без этого итоговое время примерно удваивается.

Я думаю, дело не в тулзе, а в самом подходе — оно лочит те ноды, для которых создаёт связи. Отсюда думаю и лаги.

Ясно, спасибо. А так вообще на первый взгляд подготовка данных похожа на задачу поиска connected components (и дальше колбасить каждый сет в отдельном потоке, не наступая на локи).

Подписаться на комментарии