График моделирования в Neo4j, показывающий рабочий процесс и влияние

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

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

Системная архитектура и реальность

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

Тестовые случаи

Я придумал 4 дизайна, 3 из которых, кажется, работают, но не уверен, что лучше.

Дизайн 1

Дизайн 2

Дизайн 3

Дизайн 4

Буду признателен за любую помощь или совет по этому вопросу.

Используемый код:

---------------------------------------------------------------------------
-- Design Experiments
---------------------------------------------------------------------------

// 1. Combination of the Workflows with shared nodes where they interact
      with same Process or DataStore
---------------------------------------------------------------------------

MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n, r

CREATE (p1:Provider {name: "Provider 1"})
CREATE (p2:Provider {name: "Provider 2"})
CREATE (f1:File {name: "File 1"})
CREATE (f2:File {name: "File 2"})
CREATE (f3:File {name: "File 3"})
CREATE (pp:PreProcess {name: "PreProcess"})
CREATE (p:Process {name: "Process"})
CREATE (d:DataStore {name: "DataStore"})
CREATE (rA:Report {name: "Report A"})
CREATE (rB:Report {name: "Report B"})
CREATE (p1)-[:PROVIDES{}]->(f1)
CREATE (p1)-[:PROVIDES{}]->(f2)
CREATE (p2)-[:PROVIDES{}]->(f3)
CREATE (f1)-[:DELIVERS_TO{}]->(pp)
CREATE (pp)-[:DELIVERS_TO{}]->(p)
CREATE (f2)-[:DELIVERS_TO{}]->(p)
CREATE (f3)-[:DELIVERS_TO{}]->(p)
CREATE (p)-[:DELIVERS_TO{}]->(d)
CREATE (d)-[:DELIVERS_TO{}]->(rA)
CREATE (d)-[:DELIVERS_TO{}]->(rB)

// Show impacted reports if Provider 1 is down
MATCH (a:Provider {name:"Provider 1"})-[r*]->(rp:Report) RETURN rp

// Show impacted reports if Provider 2 is down
MATCH (a:Provider {name:"Provider 2"})-[r*]->(rp:Report) RETURN rp


// 2. Same node relationship design as #1, but assign a workflow property
      to each node and relationship as a property array
---------------------------------------------------------------------------

MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n, r

CREATE (p1:Provider {name: "Provider 1", workflow: ["workflow1","workflow2"]})
CREATE (p2:Provider {name: "Provider 2", workflow: ["workflow3"]})
CREATE (f1:File {name: "File 1", workflow: ["workflow1"]})
CREATE (f2:File {name: "File 2", workflow: ["workflow2"]})
CREATE (f3:File {name: "File 3", workflow: ["workflow3"]})
CREATE (pp:PreProcess {name: "PreProcess", workflow: ["workflow1"]})
CREATE (p:Process {name: "Process", workflow: ["workflow1","workflow2","workflow3"]})
CREATE (d:DataStore {name: "DataStore", workflow: ["workflow1","workflow2","workflow3"]})
CREATE (rA:Report {name: "Report A", workflow: ["workflow1","workflow3"]})
CREATE (rB:Report {name: "Report B", workflow: ["workflow2"]})
CREATE (p1)-[:PROVIDES{workflow: ["workflow1"]}]->(f1)
CREATE (p1)-[:PROVIDES{workflow: ["workflow2"]}]->(f2)
CREATE (p2)-[:PROVIDES{workflow: ["workflow3"]}]->(f3)
CREATE (f1)-[:DELIVERS_TO{workflow: ["workflow1"]}]->(pp)
CREATE (pp)-[:DELIVERS_TO{workflow: ["workflow1"]}]->(p)
CREATE (f2)-[:DELIVERS_TO{workflow: ["workflow2"]}]->(p)
CREATE (f3)-[:DELIVERS_TO{workflow: ["workflow3"]}]->(p)
CREATE (p)-[:DELIVERS_TO{workflow: ["workflow1","workflow2","workflow3"]}]->(d)
CREATE (d)-[:DELIVERS_TO{workflow: ["workflow1","workflow3"]}]->(rA)
CREATE (d)-[:DELIVERS_TO{workflow: ["workflow2"]}]->(rB)

// Show individual workflows
MATCH (p) WHERE filter(x in p.workflow WHERE x = "workflow1") RETURN p
MATCH (p) WHERE filter(x in p.workflow WHERE x = "workflow2") RETURN p
MATCH (p) WHERE filter(x in p.workflow WHERE x = "workflow3") RETURN p

// Show impacted reports if Provider 1 is down
MATCH (a:Provider {name:"Provider 1"}) WITH a.workflow AS workflows 
MATCH (r:Report) WHERE filter(x in r.workflow WHERE x in workflows)
RETURN r

// Show impacted reports if Provider 2 is down
MATCH (a:Provider {name:"Provider 2"}) WITH a.workflow AS workflows 
MATCH (r:Report) WHERE filter(x in r.workflow WHERE x in workflows)
RETURN r


// 3. Same node relationship design as #1, but create a relationship
      with a workflow property for each workflow, resulting in multiple
      relatinships between nodes.
---------------------------------------------------------------------------

MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n, r

CREATE (p1:Provider {name: "Provider 1"})
CREATE (p2:Provider {name: "Provider 2"})
CREATE (f1:File {name: "File 1"})
CREATE (f2:File {name: "File 2"})
CREATE (f3:File {name: "File 3"})
CREATE (pp:PreProcess {name: "PreProcess"})
CREATE (p:Process {name: "Process"})
CREATE (d:DataStore {name: "DataStore"})
CREATE (rA:Report {name: "Report A"})
CREATE (rB:Report {name: "Report B"})
CREATE (p1)-[:PROVIDES{workflow: "workflow1"}]->(f1)
CREATE (p1)-[:PROVIDES{workflow: "workflow2"}]->(f2)
CREATE (p2)-[:PROVIDES{workflow: "workflow3"}]->(f3)
CREATE (f1)-[:DELIVERS_TO{workflow: "workflow1"}]->(pp)
CREATE (pp)-[:DELIVERS_TO{workflow: "workflow1"}]->(p)
CREATE (f2)-[:DELIVERS_TO{workflow: "workflow2"}]->(p)
CREATE (f3)-[:DELIVERS_TO{workflow: "workflow3"}]->(p)
CREATE (p)-[:DELIVERS_TO{workflow: "workflow1"}]->(d)
CREATE (p)-[:DELIVERS_TO{workflow: "workflow2"}]->(d)
CREATE (p)-[:DELIVERS_TO{workflow: "workflow3"}]->(d)
CREATE (d)-[:DELIVERS_TO{workflow: "workflow1"}]->(rA)
CREATE (d)-[:DELIVERS_TO{workflow: "workflow3"}]->(rA)
CREATE (d)-[:DELIVERS_TO{workflow: "workflow2"}]->(rB)


// Show impacted reports if Provider 1 is down
MATCH (a:Provider {name:"Provider 1"})-[j]->(n)-[r*]->(g)-[t]->(rp:Report) WHERE j.workflow=t.workflow RETURN rp

// Show impacted reports if Provider 2 is down
MATCH (a:Provider {name:"Provider 2"})-[j]->(n)-[r*]->(g)-[t]->(rp:Report) WHERE j.workflow=t.workflow RETURN rp


// 4. Distinct set of nodes and relationships for each workflow, but all
      with same node type so can still be matched
---------------------------------------------------------------------------

MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n, r

CREATE (p1:Provider {name: "Provider 1"})
CREATE (p2:Provider {name: "Provider 1"})
CREATE (p3:Provider {name: "Provider 2"})
CREATE (f1:File {name: "File 1"})
CREATE (f2:File {name: "File 2"})
CREATE (f3:File {name: "File 3"})
CREATE (pp1:PreProcess {name: "PreProcess"})
CREATE (pc1:Process {name: "Process"})
CREATE (pc2:Process {name: "Process"})
CREATE (pc3:Process {name: "Process"})
CREATE (d1:DataStore {name: "DataStore"})
CREATE (d2:DataStore {name: "DataStore"})
CREATE (d3:DataStore {name: "DataStore"})
CREATE (rA1:Report {name: "Report A"})
CREATE (rB2:Report {name: "Report B"})
CREATE (rA3:Report {name: "Report A"})
CREATE (p1)-[:PROVIDES{workflow: "workflow1"}]->(f1)
CREATE (p2)-[:PROVIDES{workflow: "workflow2"}]->(f2)
CREATE (p3)-[:PROVIDES{workflow: "workflow3"}]->(f3)
CREATE (f1)-[:DELIVERS_TO{workflow: "workflow1"}]->(pp1)
CREATE (pp1)-[:DELIVERS_TO{workflow: "workflow1"}]->(pc1)
CREATE (f2)-[:DELIVERS_TO{workflow: "workflow2"}]->(pc2)
CREATE (f3)-[:DELIVERS_TO{workflow: "workflow3"}]->(pc3)
CREATE (pc1)-[:DELIVERS_TO{workflow: "workflow1"}]->(d1)
CREATE (pc2)-[:DELIVERS_TO{workflow: "workflow2"}]->(d2)
CREATE (pc3)-[:DELIVERS_TO{workflow: "workflow3"}]->(d3)
CREATE (d1)-[:DELIVERS_TO{workflow: "workflow1"}]->(rA1)
CREATE (d2)-[:DELIVERS_TO{workflow: "workflow3"}]->(rB2)
CREATE (d3)-[:DELIVERS_TO{workflow: "workflow2"}]->(rA3)


// Show impacted reports if Provider 1 is down
MATCH (a:Provider {name:"Provider 1"})-[j*]->(rp:Report) RETURN rp

// Show impacted reports if Provider 2 is down
MATCH (a:Provider {name:"Provider 2"})-[j*]->(rp:Report) RETURN rp

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

Дизайн 1а

// 1a. Combination of the Workflows with shared nodes where they interact
   with same Process or DataStore. 
---------------------------------------------------------------------------

MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n, r

CREATE (p1:Provider {name: "Provider 1"})
CREATE (p2:Provider {name: "Provider 2"})
CREATE (f1:File {name: "File 1"})
CREATE (f2:File {name: "File 2"})
CREATE (f3:File {name: "File 3"})
CREATE (pp:PreProcess {name: "PreProcess"})
CREATE (p:Process {name: "Process"})
CREATE (d:DataStore {name: "DataStore"})
CREATE (rA:Report {name: "Report A"})
CREATE (rB:Report {name: "Report B"})
CREATE (p1)-[:PROVIDES{}]->(f1)
CREATE (p1)-[:PROVIDES{}]->(f2)
CREATE (p2)-[:PROVIDES{}]->(f3)
CREATE (f1)-[:DELIVERS_TO{}]->(pp)
CREATE (pp)-[:DELIVERS_TO{}]->(p)
CREATE (f2)-[:DELIVERS_TO{}]->(p)
CREATE (f3)-[:DELIVERS_TO{}]->(p)
CREATE (p)-[:DELIVERS_TO{}]->(d)
CREATE (d)-[:DELIVERS_TO{}]->(rA)
CREATE (d)-[:DELIVERS_TO{}]->(rB)
CREATE (f1)-[:USED_BY{}]->(rA)
CREATE (f2)-[:USED_BY{}]->(rB)
CREATE (f3)-[:USED_BY{}]->(rA)

// Show impacted reports (and path) if Provider 1 is down
MATCH path = (:Provider{name:'Provider 1'})-[:PROVIDES|USED_BY*]->(r:Report)
RETURN path, r.name AS report

// Show impacted reports (and path) if Provider 2 is down
MATCH path = (:Provider{name:'Provider 2'})-[:PROVIDES|USED_BY*]->(r:Report)
RETURN path, r.name AS report

1 ответ

Решение

Вы провели здесь тщательное исследование, вы нашли проекты, для которых работают ваши запросы. Однако, для них есть цена.

В Design 2 вообще не используются отношения, поэтому решение кажется не слишком графичным. Также требуется, чтобы списки рабочих процессов на соответствующих узлах были синхронизированы и обновлены. Это, кажется, имеет более высокую стоимость обслуживания.

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

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

Дизайн 1 интересен тем, что он дает правильные ответы, но только на определенные вопросы... вопросы о воздействии процессоров, препроцессоров и хранилищ данных на пути, что происходит, когда эти аппаратные и программные компоненты выходят из строя.

Однако это не работает для линии данных / зависимости. Еще нет. Возможно, вы захотите изменить схему 1, чтобы для зависимости от данных были рассмотрены отдельные пути по сравнению с тем, что у вас уже есть для процесса конвейера.

Зависимость данных может быть другой вещью. Если вы задаете вопросы по этому поводу, то в основном вас интересуют входы и выходы, файлы для отчетов. В этом случае вы можете подумать о создании отношения:DEPENDS_ON между соответствующими файлами и узлами отчетов.

Попробуйте добавить это в сценарий создания дизайна 1 в конце:

match (f:File), (r:Report{name:'Report A'})
where f.name in ['File 1', 'File 3']
create (r)<-[:USED_BY]-(f)

а также

match (f:File), (r:Report{name:'Report B'})
where f.name in ['File 2']
create (r)<-[:USED_BY]-(f)

Для вопросов о происхождении данных ваши запросы могут использовать только соответствующие отношения, в данном случае:PROVIDES и:USED_BY.

match path = (:Provider{name:'Provider 1'})-[:PROVIDES|USED_BY*]->(r:Report)
return path, r.name as report

Или наоборот, на какие источники опирается отчет?

match path = (p:Provider)-[:PROVIDES|USED_BY*]->(r:Report{name:'Report A')
return path, p.name as report

И если ваша модель изменяется так, что моделируются промежуточные отчеты (выходные данные операций предварительной обработки и процесса), то вы можете создать отношения: USED_BY с теми в цепочке из:File to the:Report (вместо непосредственно между: File и:Report), так что вы увидите цепочку зависимостей во время обработки.

Другие вопросы по тегам