Overview
UModel offers powerful graph query capabilities for complex operations based on entity relationships. This document describes basic graph query methods, including the graph-match and graph-call features. You can use these features to query relationships and paths between entities.
Basic concepts of graph query
Node and edge description structure
In graph queries, you can use a specific syntax to describe nodes and edges:
Nodes: Use parentheses
().Edges: Use brackets
[].Description format:
<variable_name>:<label> {key-value_property_pairs}.
Basic syntax examples
// Any node
()
// A node with a specific label
(:"apm@apm.service") // graph-match syntax
(:`apm@apm.service`) // Cypher syntax
// A node with a label and properties
(:"apm@apm.service" { __entity_type__: 'apm.service' })
// A node with a named variable
(s:"apm@apm.service" { __entity_id__: '123456' })
// Any edge
[]
// A named edge
[edge]
// An edge with a type
[e:calls { __type__: "calls" }]Syntax differences
graph-match vs. Cypher:
In the Structured Process Language (SPL) context, graph-match requires that you enclose special characters in double quotation marks.
As a standalone syntax, Cypher uses backticks to enclose labels. Note that if you use a backtick string format in Cypher, you must escape internal backticks with another backtick.
// graph-match syntax
.topo | graph-match (s:"apm@apm.service" {__entity_id__: '123'})-[e]-(d)
project s, e, d
// Cypher syntax (In the backtick string format ``apm@apm.service``, use double backticks to enclose the string)
.topo | graph-call cypher(`
MATCH (s:``apm@apm.service`` {__entity_id__: '35af918180394ff853be6c9b458704ea'})-[e]-(d)
RETURN s, e, d
`)Graph query path syntax
Graph query paths use ASCII characters to describe the direction of a relationship:
Path expression | Direction description |
| A directed relationship from A to B |
| A directed relationship from B to A |
| A bidirectional relationship, regardless of direction |
Return value structure
Node JSON format
{
"id": "apm@apm.service:347150ad7eaee43d2bd25d113f567569",
"label": "apm@apm.service",
"properties": {
"__domain__": "apm",
"__entity_type__": "apm.service",
"__entity_id__": "347150ad7eaee43d2bd25d113f567569",
"__label__": "apm@apm.service"
}
}
Edge JSON format
{
"startNodeId": "apm@apm.service:347150ad7eaee43d2bd25d113f567569",
"endNodeId": "apm@apm.service.host:34f627359470c9d36da593708e9f2db7",
"type": "contains",
"properties": {
"__type__": "contains"
}
}graph-match query
Basic syntax
.topo | graph-match <path>
project <output>
| [additional SPL operations]
Core attributes
A starting point is required: You must provide a label and the
__entity_id__property.Intermediate node restriction: For intermediate nodes, you can specify only a label, not properties.
Multi-level jumps are not supported: The
[*2..3]syntax is not supported.Loop handling: Each edge can be traversed multiple times.
Practical application examples
1. Full-link path query
Find the complete service invocation path that starts from a specific operation:
.topo |
graph-match (s:"apm@apm.service.operation" {__entity_id__: '6f0bb4c892effff81538df574a5cfcd9'})
<-[e1]-(v1)-[e2:runs_on]->(v2)-[e3]->(v3)
project s,
"e1.__type__",
"v1.__label__",
"e2.__type__",
"v2.__label__",
"e3.__type__",
"v3.__label__",
v3
Returned result:
s: The starting operation node.
e1.type: The relationship type of the first segment.
v1.label: The label of the intermediate node.
v2, v3: Information about subsequent nodes.
2. Neighbor node statistics
Collect statistics about the neighbor distribution for a specific service:
.topo |
graph-match (s:"apm@apm.service" {__entity_id__: 'd6385f422b457a59cce1f0f725ac3e14'})
-[e]-(d)
project eType="e.__type__", dLabel="d.__label__"
| stats cnt=count(1) by dLabel, eType
| sort cnt desc
| limit 203. Conditional path query
Find the endpoint of a path that meets specific conditions:
.topo |
graph-match (s:"apm@apm.service.operation" {__entity_id__: '6f0bb4c892effff81538df574a5cfcd9'})
<-[e1]-(v1)-[e2:runs_on]->(v2)-[e3]->(v3)
project s,
"e1.__type__",
"v1.__label__",
"e2.__type__",
"v2.__label__",
"e3.__type__",
destId="v3.__entity_id__",
v3
| where destId='9a3ad23aa0826d643c7b2ab7c6897591'
| project s, v3graph-match limitations
Limitation type | Description | Solution |
Starting point restriction | A label and __entity_id__ must be specified. | Use a known entity ID as the starting point. |
Intermediate node restriction | Properties cannot be specified for intermediate nodes. | Use SPL for subsequent filtering. |
Multi-level jumps are not supported | The | Use Cypher or step-by-step queries. |
Loop handling | Edges can be traversed multiple times, which may create loops. | Use SPL to deduplicate results or limit the query depth. |
graph-call query
getNeighborNodes function
Retrieves the neighbor nodes for a specified list of nodes and returns the neighbor nodes and their relationship types.
Syntax format
.topo | graph-call getNeighborNodes(type, depth, nodeList)Parameter description
Parameter | Type | Description | Valid values |
type | string | Traversal type | sequence, sequence_in, sequence_out, full |
depth | int | Traversal depth | A positive integer. A value of 5 or less is recommended. |
nodeList | array | Starting node list | An array of node descriptions |
Return format
Field | Description | Example |
srcNode | Source node JSON | {"id":"app@app.service:347150…", "label":"app@app.service", …} |
destNode | Destination node JSON | {"id":"app@app.operation:73ef19…", "labels":"app@app.operation", …} |
relationType | Relationship type | "contains", "calls", "runs_on" |
srcPosition | Source node position | -1, 0, 1, 2… |
Traversal type description
sequence: A directed sequence traversal that returns relationships in a specific order.
sequence_in: A subset of sequence. This is a directed sequence traversal that maintains only the order of incoming edges. In the returned result set, all nodes ultimately point to the starting node.
sequence_out: A subset of sequence. This is a directed sequence traversal that maintains only the order of outgoing edges. In the returned result set, all nodes ultimately originate from the starting node.
full: A full-direction traversal.
Traverses all nodes without considering the direction of edges.
Practical example
.topo | graph-call getNeighborNodes(
'sequence', 1,
[
(:"app@app.operation" {__entity_id__: '73ef19770998ff5d4c1bfd042bc00a0f'})
]
)getDirectRelations function
This function returns the direct relationships between a specified list of nodes and excludes indirect relationships. It is typically used for batch upstream and downstream queries for multiple nodes.
Syntax format
.topo | graph-call getDirectRelations(nodeList)Parameter description
Parameter | Type | Description |
nodeList | array | Node list |
Return format
Field | Description |
relations | A JSON array of relationships. Each object contains startNodeId, endNodeId, type, and properties. |
Practical example
.topo | graph-call getDirectRelations(
[
(:"app@app.service" {__entity_id__: '347150ad7eaee43d2bd25d113f567569'}),
(:"app@app.operation" {__entity_id__: '73ef19770998ff5d4c1bfd042bc00a0f'})
]
)
{
"startNodeId": "app@app.service:347150ad7eaee43d2bd25d113f567569",
"endNodeId": "app@app.operation:73ef19770998ff5d4c1bfd042bc00a0f",
"type": "contains",
"properties": {"__type__": "contains"}
}Scenarios
Get the complete neighbor relationships of a service
-- Get all neighbors of a service (within 5 hops)
.topo | graph-call getNeighborNodes(
'full', 3,
[(:"apm@apm.service" {__entity_id__: 'user-service-id'})]
)
| stats cnt=count(1) by relationType
| sort cnt descAnalyze a service invocation chain
-- Analyze the invocation pattern of a specific service
.topo |
graph-match (s:"apm@apm.service" {__entity_id__: 'payment-service'})
-[e:calls]-(d:"apm@apm.service")
project
source_service="s.service_name",
target_service="d.service_name",
call_type="e.__type__"
| stats call_count=count(1) by source_service, target_service
| sort call_count descPod-to-node relationship chain
-- Trace the complete deployment chain of a pod
.topo |
graph-match (pod:"k8s@k8s.pod" {__entity_id__: '347150ad7eaee43d2bd25d113f567569'})
<-[r1:contains]->(node:"k8s@k8s.node")
<-[r2:contains]-(cluster:"k8s@k8s.cluster")
project
pod_name="pod.pod_name",
node_name="node.node_name",
cluster_name="cluster.cluster_name",
"r1.__type__",
"r2.__type__"Cloud resource dependency analysis
-- Analyze the network dependencies of an ECS instance
.topo | graph-call getNeighborNodes(
'sequence_out', 2,
[(:"acs@acs.ecs.instance" {__entity_id__: 'i-bp1234567890'})]
)
| extend relation_category = CASE
WHEN relationType in ('belongs_to', 'runs_in') THEN 'infrastructure'
WHEN relationType in ('depends_on', 'uses') THEN 'dependency'
WHEN relationType in ('connects_to', 'accesses') THEN 'network'
ELSE 'other' END
| stats cnt=count(1) by relation_category
| sort cnt desc
| limit 0, 100Analyze the impact of an upstream failure
-- Find upstream services that may affect the target service
.topo | graph-call getNeighborNodes(
'sequence_in', 3,
[(:"apm@apm.service" {__entity_id__: '029ea8f3f65dbc8da226f2c8d55bb8c6'})]
)
| where relationType in ('calls', 'depends_on')
| extend impact_level = CASE
WHEN srcPosition = '-1' THEN 'direct'
WHEN srcPosition = '-2' THEN 'secondary'
ELSE 'indirect' END
| extend parsed_service_name = json_extract_scalar(srcNode, '$.properties.service_name')
| project
upstream_service = parsed_service_name,
impact_level,
relation_type = relationType
| stats cnt=count(1) by impact_level, relation_typeAnalyze the impact of a downstream failure
-- Find downstream services affected by a failure in the target service
.topo | graph-call getNeighborNodes(
'sequence_out', 3,
[(:"apm@apm.service" {__entity_id__: 'failing-service-id'})]
)
| where relationType in ('calls', 'depends_on')
| extend affected_service = json_extract_scalar(destNode, '$.properties.service_name')
| stats impact_count=count(1) by affected_service
| sort impact_count desc
| limit 20
Trace a permission chain
-- Trace the access path from a user to a resource
.topo |
graph-match (user:"identity@user" {__entity_id__: 'user-123'})
-[auth:authenticated_to]->(app:"apm@apm.service")
-[access:accesses]->(resource:"acs@acs.rds.instance")
project
user_id="user.user_id",
app_name="app.service_name",
resource_id="resource.instance_id",
auth_method="auth.auth_method",
access_level="access.permission_level"Performance optimization suggestions
1. Control the query scope
Time range optimization: Use time fields to limit the query scope.
Limit traversal depth: A traversal depth greater than 5 significantly affects performance.
Precise starting point: Use a specific entity_id instead of a fuzzy match.
Choose the correct traversal type: Select sequence or full based on your requirements.
2. Control the result set
Use SPL to filter: Filter unwanted results immediately after the graph query.
Batch processing: For large graph queries, consider batch processing.
Result caching: For frequently queried paths, consider caching the results.