Статьи

Neo4j: общие / неопределенные имена отношений

Подход к моделированию, который я часто вижу при работе с пользователями Neo4j, заключается в создании очень общих отношений (например, HAS, CONTAINS, IS) и фильтрации по свойству отношения или по свойству / метке на конечном узле.

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

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

Я создал 4 разные базы данных, в которых был один узел с 60 000 исходящих отношений — 10 000, которые мы хотели получить, и 50 000, которые не имели отношения к делу.

Я смоделировал «отношения» 4 разными способами …

  • Использование определенного типа отношений
    (Узел) — [: HAS_ADDRESS] -> (адрес)
  • Использование общего типа отношений, а затем фильтрация по метке конечного узла
    (Узел) — [: HAS] -> (адрес: Адрес)
  • Использование общего типа отношения, а затем фильтрация по свойству отношения
    (узел) — [: HAS {тип: «адрес»}] -> (адрес)
  • Использование общего типа отношений, а затем фильтрация по свойству конечного узла
    (узел) — [: HAS] -> (адрес {тип: «адрес»})

… а затем измерили, сколько времени понадобилось, чтобы получить отношения «имеет адрес».

Хотя он явно не такой точный, как микро-тест JMH, я думаю, что он достаточно хорош, чтобы почувствовать разницу между подходами.

Я провел запрос к каждой базе данных 100 раз, а затем взял 50-й, 75-й и 99-й процентили (время указано в мс):

01
02
03
04
05
06
07
08
09
10
11
Using a generic relationship type and then filtering by end node label
50%ile: 6.0    75%ile: 6.0    99%ile: 402.60999999999825
  
Using a generic relationship type and then filtering by relationship property
50%ile: 21.0   75%ile: 22.0   99%ile: 504.85999999999785
  
Using a generic relationship type and then filtering by end node label
50%ile: 4.0    75%ile: 4.0    99%ile: 145.65999999999931
  
Using a specific relationship type
50%ile: 0.0    75%ile: 1.0    99%ile: 25.749999999999872

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

Использование определенного типа отношений

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
neo4j-sh (?)$ profile match (n) where id(n) = 0 match (n)-[:HAS_ADDRESS]->() return count(n);
+----------+
| count(n) |
+----------+
| 10000    |
+----------+
1 row
  
ColumnFilter
  |
  +EagerAggregation
    |
    +SimplePatternMatcher
      |
      +NodeByIdOrEmpty
  
+----------------------+-------+--------+-----------------------------+-----------------------+
|             Operator |  Rows | DbHits |                 Identifiers |                 Other |
+----------------------+-------+--------+-----------------------------+-----------------------+
|         ColumnFilter |     1 |      0 |                             | keep columns count(n) |
|     EagerAggregation |     1 |      0 |                             |                       |
| SimplePatternMatcher | 10000 10000 | n,   UNNAMED53,   UNNAMED35 |                       |
|      NodeByIdOrEmpty |     1 |      1 |                        n, n |          {  AUTOINT0} |
+----------------------+-------+--------+-----------------------------+-----------------------+
  
Total database accesses: 10001

Здесь мы видим, что было 10 002 доступа к базе данных, чтобы подсчитать наши 10 000 отношений HAS_ADDRESS. Мы получаем доступ к базе данных каждый раз, когда загружаем узел, отношение или свойство.

В отличие от других подходов нужно загружать гораздо больше данных только для того, чтобы затем отфильтровать их:

Использование общего типа отношений, а затем фильтрация по метке конечного узла

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
neo4j-sh (?)$ profile match (n) where id(n) = 0 match (n)-[:HAS]->(:Address) return count(n);
+----------+
| count(n) |
+----------+
| 10000    |
+----------+
1 row
  
ColumnFilter
  |
  +EagerAggregation
    |
    +Filter
      |
      +SimplePatternMatcher
        |
        +NodeByIdOrEmpty
  
+----------------------+-------+--------+-----------------------------+----------------------------------+
|             Operator |  Rows | DbHits |                 Identifiers |                            Other |
+----------------------+-------+--------+-----------------------------+----------------------------------+
|         ColumnFilter |     1 |      0 |                             |            keep columns count(n) |
|     EagerAggregation |     1 |      0 |                             |                                  |
|               Filter | 10000 10000 |                             | hasLabel(  UNNAMED45:Address(0)) |
| SimplePatternMatcher | 10000 60000 | n,   UNNAMED45,   UNNAMED35 |                                  |
|      NodeByIdOrEmpty |     1 |      1 |                        n, n |                     {  AUTOINT0} |
+----------------------+-------+--------+-----------------------------+----------------------------------+
  
Total database accesses: 70001

Использование общего типа отношения, а затем фильтрация по свойству отношения

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
neo4j-sh (?)$ profile match (n) where id(n) = 0 match (n)-[:HAS {type: "address"}]->() return count(n);
+----------+
| count(n) |
+----------+
| 10000    |
+----------+
1 row
  
ColumnFilter
  |
  +EagerAggregation
    |
    +Filter
      |
      +SimplePatternMatcher
        |
        +NodeByIdOrEmpty
  
+----------------------+-------+--------+-----------------------------+--------------------------------------------------+
|             Operator |  Rows | DbHits |                 Identifiers |                                            Other |
+----------------------+-------+--------+-----------------------------+--------------------------------------------------+
|         ColumnFilter |     1 |      0 |                             |                            keep columns count(n) |
|     EagerAggregation |     1 |      0 |                             |                                                  |
|               Filter | 10000 20000 |                             | Property(  UNNAMED35,type(0)) == {  AUTOSTRING1} |
| SimplePatternMatcher | 10000 | 120000 | n,   UNNAMED63,   UNNAMED35 |                                                  |
|      NodeByIdOrEmpty |     1 |      1 |                        n, n |                                     {  AUTOINT0} |
+----------------------+-------+--------+-----------------------------+--------------------------------------------------+
  
Total database accesses: 140001

Использование общего типа отношений, а затем фильтрация по свойству конечного узла

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
neo4j-sh (?)$ profile match (n) where id(n) = 0 match (n)-[:HAS]->({type: "address"}) return count(n);
+----------+
| count(n) |
+----------+
| 10000    |
+----------+
1 row
  
ColumnFilter
  |
  +EagerAggregation
    |
    +Filter
      |
      +SimplePatternMatcher
        |
        +NodeByIdOrEmpty
  
+----------------------+-------+--------+-----------------------------+--------------------------------------------------+
|             Operator |  Rows | DbHits |                 Identifiers |                                            Other |
+----------------------+-------+--------+-----------------------------+--------------------------------------------------+
|         ColumnFilter |     1 |      0 |                             |                            keep columns count(n) |
|     EagerAggregation |     1 |      0 |                             |                                                  |
|               Filter | 10000 20000 |                             | Property(  UNNAMED45,type(0)) == {  AUTOSTRING1} |
| SimplePatternMatcher | 10000 | 120000 | n,   UNNAMED45,   UNNAMED35 |                                                  |
|      NodeByIdOrEmpty |     1 |      1 |                        n, n |                                     {  AUTOINT0} |
+----------------------+-------+--------+-----------------------------+--------------------------------------------------+
  
Total database accesses: 140001

Итак, в заключение … конкретные отношения #ftw!