Skip to main content

Modeling Hierarchies, Networks, and Feedback Loops

Not all graphs are the same shape. A file system is a tree. A social network is many-to-many. A supply chain with feedback has cycles. Each shape has different query patterns, different failure modes, and different production constraints.

This tutorial covers all three so you can recognize which shape your domain needs and choose the right query approach from the start.


Shape 1: Hierarchies (trees)

Example domain: Organizational chart — COMPANY → DIVISION → DEPARTMENT → TEAM → EMPLOYEE

Ingesting the tree

from rushdb import RushDB

db = RushDB("RUSHDB_API_KEY", base_url="https://api.rushdb.com/api/v1")

company = db.records.create("COMPANY", {"name": "Acme Corp"})
division = db.records.create("DIVISION", {"name": "Engineering"})
dept = db.records.create("DEPARTMENT", {"name": "Platform", "budget": 2000000})
team_infra = db.records.create("TEAM", {"name": "Infra", "size": 6})
lena = db.records.create("EMPLOYEE", {"name": "Lena Müller", "role": "Lead SRE", "level": "L5"})
marco = db.records.create("EMPLOYEE", {"name": "Marco Rossi", "role": "Engineer", "level": "L4"})

db.records.attach(company.id, division.id, {"type": "HAS_DIVISION"})
db.records.attach(division.id, dept.id, {"type": "HAS_DEPARTMENT"})
db.records.attach(dept.id, team_infra.id, {"type": "HAS_TEAM"})
db.records.attach(team_infra.id, lena.id, {"type": "MEMBER_OF", "direction": "in"})
db.records.attach(team_infra.id, marco.id, {"type": "MEMBER_OF", "direction": "in"})

Querying the tree: all employees with their full org path

headcount = db.records.find({
"labels": ["EMPLOYEE"],
"where": {
"TEAM": {
"$alias": "$team",
"$relation": {"type": "MEMBER_OF", "direction": "out"},
"DEPARTMENT": {
"$alias": "$dept",
"DIVISION": {
"$alias": "$div",
"COMPANY": {"name": "Acme Corp"}
}
}
}
},
"select": {
"employeeName": "$record.name",
"role": "$record.role",
"teamName": "$team.name",
"deptName": "$dept.name"
},
"orderBy": {"employeeName": "asc"}
})

Tree query: headcount per department

dept_headcount = db.records.find({
"labels": ["DEPARTMENT"],
"where": {
"TEAM": {
"EMPLOYEE": {"$alias": "$emp", "$relation": {"type": "MEMBER_OF", "direction": "out"}}
},
"DIVISION": {"COMPANY": {"name": "Acme Corp"}}
},
"select": {
"deptName": "$record.name",
"headcount": {"$count": "$emp"}
},
"groupBy": ["deptName", "headcount"],
"orderBy": {"headcount": "desc"}
})

Shape 2: Many-to-many networks

Example domain: Research graph — AUTHOR ↔ PAPER ↔ TOPIC, PAPER → PAPER (citations)

Ingesting the network

lena_author = db.records.create("AUTHOR", {"name": "Lena Müller", "hIndex": 14})
marco_author = db.records.create("AUTHOR", {"name": "Marco Rossi", "hIndex": 9})
topic_gnn = db.records.create("TOPIC", {"name": "Graph Neural Networks"})
paper1 = db.records.create("PAPER", {"title": "GNN Scaling Strategies", "year": 2024, "citations": 87})
paper2 = db.records.create("PAPER", {"title": "Attention is All You Need", "year": 2017, "citations": 90000})

db.records.attach(lena_author.id, paper1.id, {"type": "CO_AUTHORED"})
db.records.attach(marco_author.id, paper1.id, {"type": "CO_AUTHORED"})
db.records.attach(paper1.id, topic_gnn.id, {"type": "COVERS"})
db.records.attach(paper1.id, paper2.id, {"type": "CITES"})

Network query: co-authors on a topic

co_authors = db.records.find({
"labels": ["AUTHOR"],
"where": {
"PAPER": {
"$alias": "$paper",
"$relation": {"type": "CO_AUTHORED", "direction": "out"},
"TOPIC": {"name": "Graph Neural Networks"}
}
},
"select": {
"authorName": "$record.name",
"hIndex": "$record.hIndex",
"paperCount": {"$count": "$paper"}
},
"groupBy": ["authorName", "hIndex", "paperCount"],
"orderBy": {"paperCount": "desc"}
})

Shape 3: Cyclic systems (dependency graphs)

Example domain: Package dependency graph — PACKAGE depends on other PACKAGEs through transitive chains.

SearchQuery does not expose arbitrary-depth recursive traversal. Instead, scope multi-hop traversal explicitly to the depth your product requires. For blast-radius analysis (which packages are affected if crypto-utils has a CVE?), traverse up to a known safe depth.

Ingesting the dependency graph

app_core = db.records.create("PACKAGE", {"name": "app-core", "version": "2.1.0"})
auth_lib = db.records.create("PACKAGE", {"name": "auth-lib", "version": "1.4.2"})
data_client = db.records.create("PACKAGE", {"name": "data-client", "version": "3.0.1"})
crypto_utils = db.records.create("PACKAGE", {"name": "crypto-utils", "version": "0.9.8"})

db.records.attach(app_core.id, auth_lib.id, {"type": "DEPENDS_ON"})
db.records.attach(app_core.id, data_client.id, {"type": "DEPENDS_ON"})
db.records.attach(auth_lib.id, crypto_utils.id, {"type": "DEPENDS_ON"})
db.records.attach(data_client.id, crypto_utils.id, {"type": "DEPENDS_ON"})

Cyclic query: two-hop blast radius for a vulnerable package

Find all packages that depend on crypto-utils directly (hop 1) or through one intermediate package (hop 2):

# Direct dependents of crypto-utils
direct = db.records.find({
"labels": ["PACKAGE"],
"where": {
"PACKAGE": {
"$alias": "$dep",
"$relation": {"type": "DEPENDS_ON", "direction": "out"},
"name": "crypto-utils"
}
},
"select": {
"packageName": "$record.name",
"version": "$record.version"
}
})

# Two-hop: packages that depend on a package that depends on crypto-utils
indirect = db.records.find({
"labels": ["PACKAGE"],
"where": {
"PACKAGE": {
"$alias": "$mid",
"$relation": {"type": "DEPENDS_ON", "direction": "out"},
"PACKAGE": {
"$relation": {"type": "DEPENDS_ON", "direction": "out"},
"name": "crypto-utils"
}
}
},
"select": {
"packageName": "$record.name",
"via": "$mid.name"
}
})

Comparison of the three shapes

ShapeKey propertyQuery patternAmbush
TreeSingle parent per nodeTop-down with full-path selectDeep trees require explicit hop count
Many-to-manyNodes can appear in multiple relationshipsAggregation by relationship typeFan-out can be large without limit
CyclicLoops are possibleExplicit depth boundsUnbounded traversal is not supported

Production caveat

Each shape has a fan-out risk. In trees, deep hierarchies multiply candidates at every hop. In networks, highly-connected hubs (an author with 200 papers) inflate traversal cost. In cyclic graphs, even a two-hop traversal can cover thousands of paths in large dependency graphs.

Apply limit conservatively and filter early on high-selectivity properties (e.g. name, status, version). Measure response times before and after adding hops to your query.


Next steps