×marble
all posts
May 26, 2026·12 min read

How to Design a Knowledge Graph Data Model for Personalization: Schema, Edges, and Cold-Start

The data model is the recommendation strategy. This post covers the exact node taxonomy, edge types, and property design that make a knowledge graph serve day-zero personalization with p50 < 30 ms — and why flat feature stores break the same use case.

Alex Shrestha·Founder, ×marble

How to Design a Knowledge Graph Data Model for Personalization: Schema, Edges, and Cold-Start

TL;DR.

  • A personalization knowledge graph is a labeled property graph (LPG) with five node classes — User, Item, Concept, Context, Session — and a typed edge taxonomy where behavioral, affinity, taxonomic, similarity, and context edges carry first-class properties.
  • The cold-start property is not a feature you add — it falls out of the schema: a declared preference creates a User → HAS_AFFINITY → Concept edge, and a 2-hop traversal through Concept ← BELONGS_TO ← Item produces ranked candidates with no interaction history required.
  • The industry default (interaction matrix → embedding → ANN lookup) compresses every relational signal into a float vector, destroying the structure that cold-start and explainability depend on; that trade is wrong for cross-modal, multi-entity products.
  • In 2026, labeled property graph tooling is production-mature at scale — the bottleneck is schema design, not infrastructure; most teams over-engineer the ML and under-design the graph.
  • If you only remember one thing: the edge taxonomy decides what the system can explain and what it can never explain — get it right before writing any model code.

Most personalization systems fail at the data model layer, not the algorithm layer. The ML pipeline looks sophisticated — user towers, item towers, two-stage retrieval, GNN re-rankers — but every feature engineering step, every embedding compression, every ANN lookup is downstream of a foundational choice about how user and item relationships are represented in storage. If that representation destroys relational structure, no amount of model sophistication recovers it. A knowledge graph data model preserves that structure by treating users, items, topics, and behavioral signals as first-class typed entities and relationships. The graph is the feature store. Inference is traversal. This post is about schema design — the concrete node types, edge types, and property decisions that determine what the system can reason about at query time.

Why Flat Feature Stores Break Cross-Modal Personalization

The standard feature store approach represents a user as a vector: 512 floats encoding accumulated interaction history, demographic signals, and inferred preferences. The representation is dense, cheap to query (ANN lookup via HNSW or FAISS), and well-understood operationally. It works when interactions are dense, modalities are homogeneous, and explanations are not required.

It breaks under three conditions that co-occur in almost every modern product: cross-modal signals (a user who reads articles, listens to music, and watches video), sparse early-session behavior (the first 10–20 interactions are not enough to converge a stable embedding), and explainability requirements (EU AI Act Article 13, right-to-explanation, or just user trust).

The failure mode is structural. An embedding trained on video watch history does not share a latent space with an embedding trained on music listening, unless you do expensive cross-modal training with aligned corpora — and even then, the mapping is approximate. The relational signal ("this user likes jazz, this track was released by a jazz artist they've already heard") is replaced by proximity in float space. You lose the semantics that enable cold-start and the path structure that enables explanation.

A labeled property graph data model keeps that structure intact. The cost is higher query complexity; the benefit is that cold-start, cross-modal unification, and explanation are structural properties of the model, not afterthoughts bolted onto the retrieval layer.

The LPG Model: What It Actually Means

A labeled property graph (LPG) is a directed multigraph G = (V, E, L, P) where V is the node set, E ⊆ V × V is the edge set, L assigns labels to nodes and types to edges, and P assigns property maps (key-value dictionaries) to both nodes and edges.

The critical distinction from the RDF triple model (Wikipedia: Resource Description Framework) is that properties live directly on edges — they are not separate triples. An edge (:User)-[:INTERACTED {type: "play", weight: 0.9, at: 1716400000}]->(:Item) is one graph write carrying three attributes. In RDF, those attributes require three additional triples, each with its own subject, predicate, and object. That verbosity compounds at the scale of billions of behavioral events.

For a personalization graph, the LPG model — implemented by Neo4j, TigerGraph, AWS Neptune, and Apache AGE on Postgres — wins on four practical axes: edge-property access is first-class, graph algorithms (PageRank, community detection, k-NN over node embeddings) are built in, Cypher is readable by engineers who don't have a semantic web background, and schema evolution is additive rather than destructive. The RDF model is the right choice when interoperability with the open linked-data ecosystem matters — Wikidata, schema.org, DBpedia. For a product personalization graph you control entirely, the LPG model is unambiguously better.

The Node Taxonomy: Five Classes, No More

The node schema defines what the graph can reason about. Under-specify it and you lose signal; over-specify and traversal becomes combinatorially expensive. The minimum viable taxonomy for production personalization has five node classes:

User. One node per identity, regardless of device or session. Properties: id, created_at, cold_start_resolved_at (epoch when synthetic clone inference handed off to real behavioral signal). Do not embed preferences directly on the User node — preferences belong on edges to Concept nodes, where they carry weight and decay metadata.

Item. Any content object: video, track, article, product, listing. Properties: id, created_at, modality (enum: video, audio, text, product), creator_id, duration_ms. Items are append-only — mutations create new Item nodes with a :SUPERSEDES edge rather than overwriting, so historical paths remain valid.

Concept. The semantic layer: topics, genres, moods, named entities (artist, brand, location, character). This is the node type that distinguishes a knowledge graph from a collaborative filtering matrix. Concepts connect items (what they're about) to users (what they care about) without requiring the user to have interacted with any specific item. The cold-start property of the system is a direct consequence of this node type existing.

Context. Situational signals: time-of-day bucket, device type, location cluster, session mode. Context nodes are shared — every user in a context:morning-mobile-commute cluster links to the same node. This enables collaborative filtering at the context level, surfacing "what people in similar contexts engaged with" without requiring user-level interaction overlap.

Session. An ephemeral node, created per active session, connected to the User and to every Item interacted with during that session. Sessions capture recency and sequential patterns (what items were seen together in the same sitting) without polluting the long-term user–concept affinity graph with short-term noise.

The Edge Taxonomy: Typed Relationships That Drive Inference

The edge schema determines what traversal patterns are possible at inference time. Under-specify it and path-based reasoning collapses to BFS over an undifferentiated graph. Over-type it and schema maintenance becomes worse than the feature store it replaced.

Behavioral edges (User → Item):

(:User)-[:INTERACTED {type: "play|click|skip|share|save", weight: float, at: epoch, session_id: string}]->(:Item)

The type property is the key architectural decision here. Do not create separate :PLAYED, :CLICKED, :SKIPPED edge labels. Edge-type explosion makes traversal queries verbose and schema evolution painful. One :INTERACTED label with a type property handles the full behavioral surface. The exception: :RATED (explicit 1–5 feedback) warrants its own label because its semantics and its weight in downstream inference differ categorically from implicit signals.

Affinity edges (User → Concept):

(:User)-[:HAS_AFFINITY {weight: float, decayed_at: epoch, source: "derived|declared"}]->(:Concept)

These are computed edges, derived from behavioral edges via a graph traversal job — not a separate feature pipeline. source: "declared" marks affinities seeded from onboarding preference declarations. decayed_at enables half-life scoring (multiply weight by exp(-λ * (now - decayed_at))) without storing per-affinity decay histories. The distinction between derived and declared matters for cold-start: declared affinities are present at day zero; derived ones accumulate over time.

Taxonomic edges (Item → Concept, Concept → Concept):

(:Item)-[:BELONGS_TO {confidence: float}]->(:Concept)
(:Concept)-[:SUBCATEGORY_OF]->(:Concept)

The SUBCATEGORY_OF edges are what give the graph hierarchical semantic reasoning. The graph can infer that a user with an affinity for jazz should see items tagged with bebop and modal jazz without requiring explicit HAS_AFFINITY edges for every subcategory. Survey work on hierarchical graph-based recommenders (Liang et al., 2021, arXiv:2105.07028) confirms that concept hierarchy traversal consistently outperforms flat taxonomy embeddings for long-tail item discovery.

Similarity edges (Item → Item):

(:Item)-[:SIMILAR_TO {score: float, basis: "embedding|co-interaction|editorial"}]->(:Item)

Pre-computed at indexing time — k=20 nearest neighbors per item, refreshed daily or on item creation. The basis property is load-bearing for explanation: "because users who played X also played Y" reads differently from "because an editor tagged both as noir thriller." Not all-pairs — the fan-out from a popular item to all its neighbors would make traversal prohibitively expensive.

Context edges (Session → Context, User → Context):

(:Session)-[:IN_CONTEXT]->(:Context)
(:User)-[:CURRENTLY_IN]->(:Context)

CURRENTLY_IN is the one mutable edge in this taxonomy — updated at session start. All other edges are append-only. This distinction is critical for cache invalidation: a cache key based on user affinity edges is stable for hours; one based on CURRENTLY_IN is volatile per session.

Cold-Start Is a Structural Property, Not a Code Path

Day-zero personalization is not a special code branch in a KG-native system — it is the default traversal behavior when behavioral edges are absent.

A new user who selects "jazz" during onboarding generates two graph writes: a User node and a (:User)-[:HAS_AFFINITY {weight: 1.0, source: "declared"}]->(:Concept {name: "jazz"}) edge. At query time, the retrieval traversal runs:

MATCH (u:User {id: $userId})-[aff:HAS_AFFINITY]->(c:Concept)<-[bt:BELONGS_TO]-(i:Item)
WHERE NOT (u)-[:INTERACTED]->(i)
RETURN i.id, sum(aff.weight * bt.confidence) AS score
ORDER BY score DESC LIMIT 50

Two hops. No interaction history. The candidates are semantically grounded in the declared affinity and the item–concept taxonomy — not random, not popularity-biased.

The RippleNet paper (Wang et al., CIKM 2018, arXiv:1803.03467) formalizes this: user preference "ripples outward" through the knowledge graph, with each hop applying a learned attention weight. The key result — KG-augmented CF outperformed pure CF by 9–16% on click-through rate across three public datasets — showed the largest gains in the sparse-data regime (fewer than 5 interactions). That is not a coincidence. It is the direct consequence of concept-taxonomy structure providing signal that the interaction matrix cannot.

Synthetic user clones extend this further. A zero-interaction user is initialized from a Persona node — a synthetic archetype carrying pre-seeded HAS_AFFINITY edges derived from population priors. The connection: (:User)-[:INITIALIZED_FROM {weight: 1.0}]->(:Persona). As real behavioral edges accumulate, the INITIALIZED_FROM weight decays and the user's own HAS_AFFINITY edges take over. No threshold check, no special code path — the traversal scores both edge types by weight, and real signal naturally dominates over synthetic signal as it accumulates.

Traversal-Based Retrieval vs. Embedding Lookup

The architectural choice between these two modes is the most consequential decision in a KG personalization system, and in 2026 the industry is still mostly getting it wrong by treating them as mutually exclusive.

Embedding lookup (ANN search against pre-computed vectors): sub-millisecond latency with HNSW or FAISS at hundreds of millions of items, scales well, operationally familiar. Opaque — you cannot ask "why was item Y in this candidate set" without a separate explanation model that is itself a second-guess on the original model.

Traversal-based inference (path scoring over live graph): 1–50 ms depending on hop depth and fan-out, naturally bounded by graph diameter, transparent by construction. The explanation for item Y is the path: user → HAS_AFFINITY → jazz → BELONGS_TO ← Kind of Blue → SIMILAR_TO → A Love Supreme.

The right answer is a two-phase architecture, not a binary choice:

  1. Broad retrieval via traversal: walk 2–3 hops from user affinity nodes through concept edges to item candidates. Cap at 500 candidates. At ×marble, this phase runs p50 < 8 ms on a three-node TigerGraph cluster against a graph of ~10M nodes with indexed edge traversal.

  2. Narrow re-ranking via embeddings: run the 500 candidates through a lightweight embedding similarity model — ANN lookup, or a GNN scoring layer trained on graph neighborhoods. This resolves fine-grained relevance within the candidate set where path scoring alone does not distinguish between equally-reachable items.

The KGAT architecture (Knowledge Graph Attention Network, Wang et al., SIGKDD 2019, arXiv:1905.07854) formalizes the bridge: attention weights trained on graph traversal structure, not just item content. The empirical result was meaningful improvement over DeepFM and NFM on collaborative filtering benchmarks — specifically because typed edge context contributed information that a flat embedding compressed away. The KGCN paper (Wang et al., KDD 2019, arXiv:1904.12575) extends this to neighborhood aggregation over the KG, making the GNN layer directly aware of graph topology.

Combined p50 at ×marble: under 30 ms end-to-end. The traversal phase dominates cold-start; the embedding phase dominates re-ranking for users with dense interaction histories.

Schema Evolution Without Migrations

One underrated advantage of the LPG model over relational schemas and feature stores is that schema evolution is additive and isolated.

When you need to add a new entity type — say, Creator nodes for a creator recommendation surface — you add Creator nodes and (:Item)-[:CREATED_BY]->(:Creator) edges. Existing traversal queries are unaffected. No ALTER TABLE. No feature pipeline recompute. No cache invalidation of the full ANN index. New queries can immediately use the new node type; old queries see no change.

The contrasting scenario in a feature store: adding a creator-affinity signal requires new columns in the feature table, a recompute of user embeddings to incorporate creator-affinity dimensions, re-indexing the ANN, and cache invalidation across the full recommendation surface. The blast radius is the entire system.

This asymmetry compounds over iteration cycles. A knowledge graph schema starting with 5 node types and 8 edge types can grow to 15 node types and 30 edge types over 18 months of product development, each addition isolated to the traversal patterns that use it. A feature store schema representing the same conceptual complexity becomes a 512-dimensional vector where no one knows which dimensions encode which concepts, and any schema change requires a full retraining cycle. This is not a theoretical concern — it is the reason most feature-store-first personalization systems calcify and stop evolving after the initial build.

Where to Start

If you are building personalization from scratch in 2026, start with the graph schema before writing any ML code. Schema is strategy.

Minimum viable schema for day one:

  • Three node types: User, Item, Concept
  • Four edge types: (:User)-[:INTERACTED]->(:Item), (:Item)-[:BELONGS_TO]->(:Concept), (:User)-[:HAS_AFFINITY]->(:Concept), (:Concept)-[:SUBCATEGORY_OF]->(:Concept)
  • One traversal query: 2-hop from User → Concept → Item, ordered by affinity.weight × belongs_to.confidence

That schema delivers day-zero recommendations from a single declared preference, explains every candidate as a concept path, and supports schema extension without rewrites. Add Context and Session nodes when session-aware and context-sensitive retrieval becomes a product requirement. Add SIMILAR_TO edges when the item catalog is large enough that 2-hop traversal alone produces insufficient diversity.

If you are migrating from a feature-store-first architecture: do not abandon the embeddings. Port the interaction history into :INTERACTED edges, derive :HAS_AFFINITY edges from existing user-interest vectors via a one-time backfill, and use the KG as the retrieval layer feeding your existing re-ranking model. The migration is incremental; the two systems coexist during the transition.

On tooling: Apache AGE on Postgres is the lowest-friction entry point if your team already runs Postgres — Cypher over your existing instance, no new infrastructure. Neo4j Aura is the fastest path to a production-grade managed graph with native GDS (Graph Data Science) algorithms. TigerGraph is the right call for graphs above 100M nodes where traversal latency is critical. The comprehensive survey by Guo et al. on KG-based recommendation (IEEE TKDE 2022, arXiv:2003.00911) covers the model landscape in depth if you need to evaluate beyond the architecture choices above.

The data model is the recommendation strategy. Everything else — the GNN, the ANN index, the feature pipeline — is implementation detail on top of it. Get the schema right first, and cold-start, cross-modal unification, and explainability follow from the structure rather than requiring separate engineering effort for each.

the product behind these notes

×marble is the personalization graph.

One API. A living knowledge graph per user. Day-zero ready, explainable by construction. We built it so you don't have to.

See ×marble