Статьи

Сочетание Neo4j и Hadoop

В предыдущем посте « Объединение Neo4J и Hadoop» (часть I) мы описали, как мы объединяем Hadoop и Neo4J и как мы переносим данные в Neo4J.

Во второй части мы проведем вас через путь, который мы предприняли для реализации распределенного способа создания базы данных Neo4J. Идея состоит в том, чтобы использовать наш кластер Hadoop для создания базовой файловой структуры базы данных Neo4J.
Чтобы сделать это, мы должны сначала понять эту файловую структуру. К счастью, Крис Джиоран проделал большую работу, описав эту структуру во внутреннем файловом хранилище Neo4J своего блога
. Описание было сделано для версии 1.6, но в основном все еще соответствует структуре файла 1.8.
Сначала я начну с небольшого резюме структуры файла.

Файловая структура Neo4J

Наиболее важными для базы данных являются файлы внутри каталога graph.db

neostore
neostore.id
neostore.nodestore.db
neostore.nodestore.db.id
neostore.relationshipstore.db
neostore.relationshipstore.db.id
neostore.relationshiptypestore.db
neostore.relationshiptypestore.db.id
neostore.relationshiptypestore.db.names
neostore.relationshiptypestore.db.names.id
neostore.propertystore.db
neostore.propertystore.db.id
neostore.propertystore.db.arrays
neostore.propertystore.db.arrays.id
neostore.propertystore.db.strings
neostore.propertystore.db.strings.id
neostore.propertystore.db.index
neostore.propertystore.db.index.id
neostore.propertystore.db.index.keys
neostore.propertystore.db.index.keys.id

Первый файл — это файл neostore, который кодирует время создания, случайный номер, версию, идентификатор последней совершенной транзакции, версию хранилища и следующее свойство.
Каждое значение кодируется как long вместе с однобайтовым маркером inUse. Это дает файл размером 6 * 9 байт + 15 байт для типа файла и строки версии «NeoStore v0.A.0».

Каждый файл сопровождается файлом .id. Здесь хранится последний бесплатный идентификатор. Для neostore это будет 6, потому что мы уже используем 5 записей по 9 байт.

Узлы:

Узлы хранятся в файле neostore.nodestore.db. Узлы кодируются в записях общим размером 9 байт.

1-байтовый флаг использования
4-байтовый идентификатор первого отношения
4-байтовый идентификатор первого свойства

Позиция в файле делает nodeId, поэтому с этими 9 байтами у нас есть вся информация, необходимая для узла.
В конце файла тип и версия кодируются («NodeStore v0.A.0») в 16 байтов.

Это выглядит примерно так:

01 ff ff ff ff ff ff ff ff // root node, no relationships, no properties
01 00 00 00 00 00 00 00 01 // node 1, first relationship 0, first property 1
01 00 00 00 02 00 00 00 04 // node 2, first relationship 2, first property 4
4e 6f 64 65 53 74 6f 72 65 20 76 30 2e 41 2e 30 // NodeStore v0.A.0

Отношения:

отношения хранятся в файле neostore.relationshipstore.db. Отношения кодируются в 33 байта

1-байтовый флаг использования
4 байта от идентификатора узла
4 байта до идентификатора узла
4 байта типа связи
4 байта от узла предыдущий идентификатор rel
4 байта от узла следующий идентификатор id
4 байта к узлу предыдущий идентификатор id
4 байта к узлу следующий идентификатор id
4-байтовый идентификатор первое свойство

Позиция в файле снова делает идентификатор, и в конце файла тип и версия кодируются («RelationshipStore v0.A.0 ″) в 24 байта.
Кроме узловых идентификаторов, предыдущего и следующего отношений и первого идентификатора свойства есть указатель на тип отношений, который хранится в neostore.relationshiptypestore.db

Таким образом, файл отношений выглядит следующим образом:

01 00 00 00 02 00 00 00 01 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff // relationship 1, from node 2, to node 1, type 0, no prev, no next,
01 00 00 00 04 00 00 00 03 00 00 00 01 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 00 00 00 06
01 00 00 00 06 00 00 00 05 00 00 00 01 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 00 00 00 0a
52 65 6c 61 74 69 6f 6e 73 68 69 70 53 74 6f 72 65 20 76 30 2e 41 2e 30

Файл neostore.relationshiptypestore.db представляет собой простую 5-байтовую кодировку

1-байтовый флаг использования
4-байтовый идентификатор typename

в конце файла тип и версия кодируются («RelationshipTypeStore v0.A.0») в 28 байтов

Идентификатор typename — это запись в neostore.relationshiptypestore.db.names.
Остальные файлы прекрасно объяснены в упомянутом блоге Криса Джорана.

В предыдущем блоге мы закончили на третьем этапе, поэтому мы продолжим оттуда

Фаза IV:

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

Поскольку идентификатор узлов и ребер основан на положении в файле, нам нужен порядковый номер для узлов и ребер, к счастью, мой коллега Фрисо уже предпринял такое усилие (см. Монотонное увеличение идентификаторов строк ).
Как уже упоминалось в предыдущем блоге. Начнем с двух входных файлов. Один для узлов и один для ребер между узлами.
Таким образом, первые 2 задания просты, просто возьмите данные об узле и добавьте к нему число, то же самое для данных ребер

Это даст нам следующие данные (основанные на том, с чего мы начали в предыдущем блоге)

Таблица / файл узлов выглядит примерно так:

RowNum NodeId Property1 Property2 PropertyN
0      AAA    nameOfA   amountOfA someAThing
1      BBB    nameOfB   amountOfB someBThing
2      CCC    nameOfC   amountOfC someCThing
3      DDD    nameOfD   amountOfD someDThing

Таблица / файл границ выглядит примерно так:

RowNum fromNodeId ToNodeId EdgeProperty1 EdgePropertyN
0      AAA        BBB      someDate1     someNumber1
1      AAA        DDD      someDate2     someNumber2
2      BBB        DDD      someDate3     someNumber3
3      CCC        BBB      someDate4     someNumber4
4      DDD        BBB      someDate5     someNumber5
5      DDD        CCC      someDate6     someNumber6

Начни с УЗЛОВ

Изучая форматы файлов, мы узнали, что у узлов есть указатель на первое ребро и указатель на первое свойство,
если мы знаем, что количество свойств, на которое у узла есть указатель на первое свойство, не составляет труда (просто умножьте идентификатор узла на количество свойств узла и нам пора)

Далее нам нужно выполнить следующие шаги:

1 — объединить узлы и ребра в id и idfrom (свойства можно игнорировать)
2 — объединить узлы и ребра в id и idto (свойства можно игнорировать)
3 —
объединить эти два соединения 4 — отсортировать по возрастанию по узлу, по убыванию по relnum
5 — захватывать только первое из каждого идентификатора
6 — сортировать по rownum
7 — создавать записи байтов для каждого узла

Шаг 1 приведет к:

nodeNum nodeId relNum fromNodeId ToNodeId fromNodeNum
0       AAA    0      AAA        BBB      0
0       AAA    1      AAA        DDD      0
1       BBB    2      BBB        DDD      1
2       CCC    3      CCC        BBB      2
3       DDD    4      DDD        BBB      3
3       DDD    5      DDD        CCC      3

Шаг 2 приведет к:

nodeNum nodeId relNum fromNodeId ToNodeId toNodeNum
1       BBB    0      AAA        BBB      1
3       DDD    1      AAA        DDD      3
3       DDD    2      BBB        DDD      3
1       BBB    3      CCC        BBB      1
1       BBB    4      DDD        BBB      1
2       CCC    5      DDD        CCC      2

Шаг 3 приведет к:

nodeNum nodeId relNum fromNodeId ToNodeId fromNodeNum toNodeNum
0       AAA    0      AAA        BBB      0           1
0       AAA    1      AAA        DDD      0           3
1       BBB    2      BBB        DDD      1           3
2       CCC    3      CCC        BBB      2           1
3       DDD    4      DDD        BBB      3           1
3       DDD    5      DDD        CCC      3           2
1       BBB    0      AAA        BBB      0           1
3       DDD    1      AAA        DDD      0           3
3       DDD    2      BBB        DDD      1           3
1       BBB    3      CCC        BBB      2           1
1       BBB    4      DDD        BBB      3           1
2       CCC    5      DDD        CCC      3           2

Шаг 4 приведет к:

nodeNum nodeId relNum fromNodeId ToNodeId fromNodeNum toNodeNum
0       AAA    1      AAA        DDD      0           3
0       AAA    0      AAA        BBB      0           1
1       BBB    4      DDD        BBB      3           1
1       BBB    3      CCC        BBB      2           1
1       BBB    2      BBB        DDD      1           3
1       BBB    0      AAA        BBB      0           1
2       CCC    5      DDD        CCC      3           2
2       CCC    3      CCC        BBB      2           1
3       DDD    5      DDD        CCC      3           2
3       DDD    4      DDD        BBB      3           1
3       DDD    2      BBB        DDD      1           3
3       DDD    1      AAA        DDD      0           3

Шаг 5 и 6 приведут к:

nodeNum nodeId relNum fromNodeId ToNodeId fromNodeNum toNodeNum
0       AAA    1      AAA        DDD      0           3
1       BBB    4      DDD        BBB      3           1
2       CCC    5      DDD        CCC      3           2
3       DDD    5      DDD        CCC      3           2

Так что теперь мы можем вывести это в файл neostore.nodestore.db следующим образом:

1 1 0
1 4 4
1 5 8
1 5 12

Далее мы обрабатываем края

Первые шаги такие же, как нам нужно было выполнить для узлов

1 — объединить узлы и ребра в id и idfrom (свойства можно игнорировать)
2 — объединить узлы и ребра в id и idto (свойства можно игнорировать)
3 —
объединить эти два соединения 4 — отсортировать по возрастанию по узлу, по убыванию по relnum

Для начала мы получим данные с шага 4 узла, и мы можем пропустить идентификатор узла:

nodeNum relNum fromNodeNum toNodeNum
0       1      0           3
0       0      0           1
1       4      3           1
1       3      2           1
1       2      1           3
1       0      0           1
2       5      3           2
2       3      2           1
3       5      3           2
3       4      3           1
3       2      1           3
3       1      0           3

5 — определить последовательность отношений на узел

nodeNum relNum fromNodeNum toNodeNum next previous
0       1      0           3         0    x
0       0      0           1         x    1
1       4      3           1         3    x
1       3      2           1         2    4
1       2      1           3         0    3
1       0      0           1         x    2
2       5      3           2         3    x
2       3      2           1         x    5
3       5      3           2         4    x
3       4      3           1         2    5
3       2      1           3         1    4
3       1      0           3         x    2

6 — создать самостоятельное соединение на relnum, fromNodeNum и toNodeNum, чтобы определить предыдущий / следующий для toNode

nodeNum relNum fromNodeNum toNodeNum next previous nodeNum2 relNum2 fromNodeNum2 toNodeNum2 next2 previous2
0       1      0           3         0    x        0        1       0            3          0     x
0       1      0           3         0    x        3        1       0            3          x     2
0       0      0           1         x    1        0        0       0            1          x     1
0       0      0           1         x    1        1        0       0            1          x     2
1       4      3           1         3    x        1        4       3            1          3     x
1       4      3           1         3    x        3        4       3            1          2     5
1       3      2           1         2    4        1        3       2            1          2     4
1       3      2           1         2    4        2        3       2            1          x     5
1       2      1           3         0    3        1        2       1            3          0     3
1       2      1           3         0    3        3        2       1            3          1     4
1       0      0           1         x    2        1        0       0            1          x     2
1       0      0           1         x    2        0        0       0            1          x     1
2       5      3           2         3    x        2        5       3            2          3     x
2       5      3           2         3    x        3        5       3            2          4     x
2       3      2           1         x    5        2        3       2            1          x     5
2       3      2           1         x    5        1        3       2            1          2     4
3       5      3           2         4    x        3        5       3            2          4     x
3       5      3           2         4    x        2        5       3            2          3     x
3       4      3           1         2    5        3        4       3            1          2     5
3       4      3           1         2    5        1        4       3            1          3     x
3       2      1           3         1    4        3        2       1            3          1     4
3       2      1           3         1    4        1        2       1            3          0     3
3       1      0           3         x    2        3        1       0            3          x     2
3       1      0           3         x    2        0        1       0            3          0     x

7 — вывести дубликаты по id, from, to и relnum (самостоятельные записи)

nodeNum relNum fromNodeNum toNodeNum next previous nodeNum2 relNum2 fromNodeNum2 toNodeNum2 next2 previous2
0       1      0           3         0    x        3        1       0            3          x     2
0       0      0           1         x    1        1        0       0            1          x     2
1       4      3           1         3    x        3        4       3            1          2     5
1       3      2           1         2    4        2        3       2            1          x     5
1       2      1           3         0    3        3        2       1            3          1     4
1       0      0           1         x    2        0        0       0            1          x     1
2       5      3           2         3    x        3        5       3            2          4     x
2       3      2           1         x    5        1        3       2            1          2     4
3       5      3           2         4    x        2        5       3            2          3     x
3       4      3           1         2    5        1        4       3            1          3     x
3       2      1           3         1    4        1        2       1            3          0     3
3       1      0           3         x    2        0        1       0            3          0     x

8 — отфильтровать дубликаты (по relnum, form, to), сохранив те, где nodeNum == fromNodeNum

nodeNum relNum fromNodeNum toNodeNum next previous nodeNum2 relNum2 fromNodeNum2 toNodeNum2 next2 previous2
0       1      0           3         0    x        3        1       0            3          x     2
0       0      0           1         x    1        1        0       0            1          x     2
1       2      1           3         0    3        3        2       1            3          1     4
2       3      2           1         x    5        1        3       2            1          2     4
3       5      3           2         4    x        2        5       3            2          3     x
3       4      3           1         2    5        1        4       3            1          3     x

9 — сортировка по relnum (подготовка к выводу), удаление неиспользуемых вещей, таких как nodeNum, nodenum2, relnum2, formNodeNum2 и toNodeNum2

relNum fromNodeNum toNodeNum fromnext fromprevious tonext toprevious
0      0           1         x        1            x      2
1      0           3         0        x            x      2
2      1           3         0        3            1      4
3      2           1         x        5            2      4
4      3           1         2        5            3      x
5      3           2         4        x            3      x

10 — создать байт-запись за отн.

Так что теперь нам нужно только сделать свойства. Чтобы этот пост стал слишком длинным (я полагаю, уже немного поздно), я оставлю это упражнению для читателя.

Таким образом, эта комбинация заданий длится около 1 часа, но передача файлов на локальный компьютер заняла 6 часов. Что здесь произошло
У нас есть база данных размером 136 ГБ вместо 80 ГБ с пакетным импортером

К сожалению, мы где-то пропустили оптимизацию свойства. Вернуться к доске для рисования

Свойства хранятся в записи с 4 длинными (4 * 8 байт) и для некоторых свойств более чем достаточно места для хранения нескольких свойств в одной записи.
Мы также реализовали это и получили хорошую базу данных на 80 ГБ.

Код для всего этого можно найти на github .

Я использовал этот проект, чтобы лучше познакомиться с парадигмой Map / Reduce, поэтому я закодировал все задания в чистых частях Java Map / Reduce.
Я надеюсь, что это было полезно для вас, и я надеюсь, что код достаточно чистый, чтобы понять части, которые я не описал подробно. Не стесняйтесь связаться со мной, если вы хотите использовать его и вам нужно больше объяснений.

—— Обновление ——
Майкл Хангер из neotechnology прислал мне хорошую картинку, объясняющую немного больше формат хранения файлов. Так как картина говорит больше, чем 1605 слов, которые я использовал, я не хотел скрывать это от вас.

магазин формата-Neo4j
——