02_property_graph

command
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 3, 2026 License: MIT Imports: 14 Imported by: 0

README

Example 02 — Property graph

What it demonstrates

Building a labelled property graph (LPG) with an optional type schema, attaching labels and typed properties spanning all four scalar kinds (string, int64, float64, bool), running label- and property-indexed MATCH-style queries through the graph/query engine, and — the other half of the round trip — reading the typed properties back out of a matched node with lpg.Graph.GetNodeProperty.

It is the property-graph counterpart to the scale benchmark in example 26: a seeded, scale-parametrised dataset that reports build throughput, index-backed query latency, live heap and bytes per node, with a deterministic data shape pinned by a regression test.

Domain / scenario

A realistic employee directory. A seeded generator produces:

  • :Person nodes, each carrying a name (string), age (int64), salary (float64), active (bool), dept (string, one of a fixed set) and a stable id (p<NNN>);
  • a configurable fraction of those persons additionally carrying the :Manager label;
  • :Org nodes, each carrying a name (string), founded year (int64) and revenue (float64), with a stable id (o<NNN>);
  • one (:Person)-[:WORKS_AT]->(:Org) edge per person, to a randomly chosen org.

An optional schema.Schema declares the labels and the typed property keys and is installed as the graph's runtime validator, so every property write is type-checked at the boundary before it lands (disable it with -schema=false).

Fixing -seed fixes the data shape exactly — and therefore every indexed match count and every read-back value.

How to run

go run ./examples/02_property_graph                      # small deterministic default
go run ./examples/02_property_graph -persons 2000000 -orgs 5000 -seed 7  # observable-scale run

Scale and flags

Flag Meaning Default Large value
-persons number of :Person nodes 2000 2000000
-orgs number of :Org nodes 50 5000
-manager-pct percentage of persons that are managers (0..100) 20 20
-active-pct percentage of persons that are active (0..100) 75 75
-seed RNG seed (fixes the data shape) 1 7
-schema install the type schema as a runtime validator true true

The per-person value ranges (age 21–65, salary 35 000–180 000) are fixed constants rather than flags: they shape the values a node carries, not the scale of the dataset.

Expected output

At the default config the deterministic fact lines are:

config.persons=2000
config.orgs=50
config.manager_pct=20
config.active_pct=75
config.seed=1
config.schema=true
nodes.persons=2000
nodes.orgs=50
edges.works_at=2000
q.persons=2000
q.managers=417
q.dept_engineering=348
q.active=1496
q.manager_orgs=50
sample.key=p0
sample.name=Charlotte Anderson
sample.age=58
sample.salary=71360.53
sample.active=true
sample.dept=Operations

Interleaved with the facts the run also prints # -prefixed telemetry, for example:

# build.elapsed=10.724ms
# build.node_rate=191156 nodes/s
# mem.heap_alloc=1.79 MiB
# bytes_per_node=815.0
# q.dept_engineering.latency=399µs

Telemetry varies per run and per machine and is never pinned by the test — only the bare fact lines above are.

Evidence it collects

This is a graph-structure (lpg) plus indexed-query example, so it reports the dimensions from that row of the evidence taxonomy:

  • Build throughput# build.node_rate (nodes/s) and # build.elapsed.
  • Live heap and bytes per node# mem.heap_alloc, # mem.heap_growth, # mem.total_alloc, # mem.num_gc, # bytes_per_node.
  • Index-backed query latency — one # q.<name>.latency line per query.
  • Count of nodes matching each indexed predicate — the q.persons, q.managers, q.dept_engineering, q.active, q.manager_orgs fact lines.

When you scale it up, watch how the bytes-per-node figure settles and how the index-backed point-lookup latencies stay flat as the population grows — that is the label/property index doing its job instead of a full scan.

Key APIs

  • graph/lpg.New — build a labelled property graph over the mutable adjacency-list backend.
  • graph/lpg/schema.New / Schema.RegisterLabel / Schema.RegisterProperty and lpg.Graph.SetValidator — declare and install the optional type schema enforced on the write path.
  • graph/lpg.Graph.SetNodeLabel / SetNodeProperty / AddEdgeLabeled — attach labels, typed property values, and a typed edge.
  • graph/lpg.StringValue / Int64Value / Float64Value / BoolValue — construct the four scalar typed property values.
  • graph/lpg.Graph.GetNodeProperty and PropertyValue.String / Int64 / Float64 / Bool — read the typed properties back off each matched node.
  • graph/csr.BuildFromAdjList — freeze the live graph into the immutable CSR snapshot the query engine reads.
  • graph/query.New / Engine.Match / Pattern.Vertex / Pattern.Out / Pattern.Cardinality — run the index-backed MATCH-style queries.
  • graph/query.WithLabel / WithProperty — the label and property predicates that drive the indexed match (Roaring label bitmaps + per-property value index).

Further reading

Documentation

Overview

Example 02_property_graph — build a labelled property graph (LPG) with an optional type schema, then run label- and property-indexed MATCH-style queries and read the typed properties back out.

This example is the property-graph counterpart to the scale benchmark in example 26: it models a realistic employee directory at a configurable scale, drives it through the graph/query engine's index-backed predicates, and reports the evidence that matters for an LPG — build throughput, index-backed query latency, live heap and bytes per node — while pinning the deterministic shape with a regression test.

Model

(:Person {id, name, age, salary, active, dept})   // id is a stable "p<NNN>" key
(:Person:Manager {…})                             // a fraction of persons are managers
(:Org    {id, name, founded, revenue})            // id is a stable "o<NNN>" key
(:Person)-[:WORKS_AT]->(:Org)                      // every person works at one org

Each :Person carries five typed properties spanning the four scalar kinds: a string name, an int64 age, a float64 salary, a bool active flag, and a string dept (one of a fixed set of departments). A configurable fraction of persons additionally carry the :Manager label. Each :Org carries a string name, an int64 founded year, and a float64 revenue. All values are drawn from a seeded RNG, so fixing -seed fixes the data shape — and therefore every indexed match count and every read-back value — exactly.

Schema

An optional schema.Schema is declared (the labels and the typed property keys) and installed as the graph's validator, so every property write is type-checked at the boundary: writing a value whose kind disagrees with its declaration is rejected before it lands. The schema is the optional half of the LPG contract this example demonstrates; disable it with -schema=false to see the same data built without validation.

Queries

The label index (Roaring bitmaps over labels) and the per-property value index back four representative point lookups, each reported as a deterministic match count plus a volatile latency line:

MATCH (p:Person)                            -> count of persons
MATCH (p:Person:Manager)                    -> count of managers (label intersection)
MATCH (p:Person {dept:'Engineering'})       -> count by string property
MATCH (p:Person {active:true})              -> count by bool property
MATCH (m:Manager)-->(o:Org)                 -> orgs reachable one hop from a manager

For a fixed sample person the example then reads its five typed properties back out through lpg.Graph.GetNodeProperty, demonstrating typed property RETRIEVAL — the other half of the round trip.

Scale

Run with no flags the example builds a small, deterministic default (2000 persons, 50 orgs) that the regression test pins and that builds in well under a second. Every dimension is a flag, so the same binary scales up to a size where the heap and latency figures become interesting:

go run ./examples/02_property_graph -persons 2000000 -orgs 5000 -seed 7

The deterministic data shape is reproducible for a fixed -seed; only the telemetry (lines prefixed with "# ") varies between runs and machines.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL