Подход к моделированию, который я часто вижу при работе с пользователями Neo4j, заключается в создании очень общих отношений (например, HAS, CONTAINS, IS) и фильтрации по свойству отношения или по свойству / метке на конечном узле.
Интуитивно понятно, что это не позволяет наилучшим образом использовать графовую модель, поскольку это означает, что вам нужно оценить множество связей и узлов, которые вас не интересуют.
Однако я никогда не проверял различия в производительности между подходами, поэтому решил попробовать.
Я создал 4 разные базы данных, в которых был один узел с 60 000 исходящих отношений — 10 000, которые мы хотели получить, и 50 000, которые не имели отношения к делу.
Я смоделировал «отношения» 4 разными способами …
- Использование определенного типа отношения
(узла) — [: HAS_ADDRESS] -> (адрес) - Использование общего типа отношений, а затем фильтрация по метке конечного узла
(узел) — [: HAS] -> (адрес: адрес) - Использование общего типа отношения, а затем фильтрация по свойству отношения
(узел) — [: HAS {тип: «адрес»}] -> (адрес) - Использование общего типа отношений, а затем фильтрация по свойству конечного узла
(узел) — [: HAS] -> (адрес {тип: «адрес»})
… а затем измерили, сколько времени понадобилось, чтобы получить отношения «имеет адрес».
Код на GitHub , если вы хотите посмотреть.
Хотя он явно не такой точный, как микро-тест JMH, я думаю, что он достаточно хорош, чтобы почувствовать разницу между подходами.
Я провел запрос к каждой базе данных 100 раз, а затем взял 50-й, 75-й и 99-й процентили (время указано в мс):
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
Мы можем подробнее изучить причину различий во времени для каждого из подходов, профилировав эквивалентный запрос шифра. Начнем с того, которое использует конкретное имя отношения
Использование определенного типа отношений
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. Мы получаем доступ к базе данных каждый раз, когда загружаем узел, отношение или свойство.
В отличие от других подходов нужно загружать гораздо больше данных только для того, чтобы затем отфильтровать их:
Использование общего типа отношений, а затем фильтрация по метке конечного узла
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
Использование общего типа отношения, а затем фильтрация по свойству отношения
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
Использование общего типа отношений, а затем фильтрация по свойству конечного узла
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!