Skip to main content
Identity Layer Design

Choosing Between a Recursive and a Flat Identity Graph Without Losing Context

You're staring at a whiteboard, marker in hand. On one side you've sketched a graph—nodes for people, nodes for devices, edges for relationships. On the other, a flat table: user_id, device_id, timestamp. Both are identity graphs, but they'll age very differently. The wrong choice means context loss in six months, and by then your data is a mess you can't untangle. I've seen teams bet on recursion because it felt powerful, then watch queries crawl. Others went flat for speed, only to realize they'd flattened away the connections that made identity valuable. There's no one-size answer. This field guide breaks down the trade-offs so you can pick—and defend—your approach. Where This Decision Hits Real Work Ad Tech and Audience Segmentation — Where the Graph Either Pays for Itself or Bleeds Cash You run a three-second lookback window for a travel retargeting campaign.

You're staring at a whiteboard, marker in hand. On one side you've sketched a graph—nodes for people, nodes for devices, edges for relationships. On the other, a flat table: user_id, device_id, timestamp. Both are identity graphs, but they'll age very differently. The wrong choice means context loss in six months, and by then your data is a mess you can't untangle.

I've seen teams bet on recursion because it felt powerful, then watch queries crawl. Others went flat for speed, only to realize they'd flattened away the connections that made identity valuable. There's no one-size answer. This field guide breaks down the trade-offs so you can pick—and defend—your approach.

Where This Decision Hits Real Work

Ad Tech and Audience Segmentation — Where the Graph Either Pays for Itself or Bleeds Cash

You run a three-second lookback window for a travel retargeting campaign. The bid request lands — two devices, same IP, same checkout cart at 2:47 AM. A flat identity graph says “merge them into one profile” and you serve one ad. The seat gets the impression. Conversion stays zero. Meanwhile, the recursive graph sees the shared IP, flags it as a hotel booking terminal — two separate travelers, same reservation session — and keeps the profiles distinct. The difference? One approach burns budget on merged noise. The other respects signal. I have watched teams burn entire Q4 budgets on exactly this mistake — flattening what should have stayed split. The shape of your graph isn't abstract. It's cost-per-acquisition, live in production.

The catch is that ad platforms love flat graphs because they inflate reach numbers. That looks good on a dashboard. But the seam blows out when you try to explain to an advertiser why a single household is getting served the same "welcome back" variant seven times in one hour. Not great. Recursive structures introduce latency — every merge decision requires traversing edges, checking confidence scores — but they also prevent the kind of identity collapse that makes audiences look bigger and perform worse. Trade-off: speed for fidelity. Most teams pick wrong on the first build.

Fraud Detection Across Devices — A Graph That Can't Disagree Is a Graph That Can't Catch Liars

A fraud team I worked with had a flat identity graph that tied every device fingerprint to one canonical ID. Worked fine until a bot farm used five hundred emulated Android instances behind a single VPN. The graph happily assigned all of them to one person. "High-value user," the system said. Actually: high-value fraud. The recursive approach would have flagged the clustering — too many device profiles, too few behavioral signals diverging — and kept the graph fragmented. Flat graphs trust their merges. Recursive graphs question them. That hesitation is exactly what you want when the cost of a false positive is a payout.

'A flat graph is a decision engine that never second-guesses itself. That's its feature. And its failure.'

— lead engineer, fraud detection team, post-mortem

Not every fraud scenario needs recursion. If you're blocking payment card tests from known bad IPs, flat works. But cross-device synthetic identity rings? Those thrive inside flat graphs like mold in a dark closet. Recursion adds the overhead of maintaining edge weights — "how strongly do these two profiles relate?" — but that weight becomes the difference between blocking a fraud ring and onboarding it as a customer. The hardest part is that fraud patterns drift faster than you can tune your graph. Recursive structures handle drift better because they don't collapse ambiguous signals into certain ones. They hold multiple hypotheses. That's expensive. So is a chargeback.

Customer 360 for Support Teams — When Context Collapse Wastes People's Time

Support agent opens a ticket. The flat graph says "one customer, one history." Great — except the graph merged two family members who share a credit card and a last name. Now the agent is reading chat transcripts from someone else's billing dispute. Context collapse. The recursive graph would preserve two profiles, linked by a household edge, and let the agent see both without confusing them. Honestly, I have seen a $40M support operation redesign its identity layer twice in three years because flat merges kept corrupting case histories. The fix wasn't better tooling. It was admitting that not every shared attribute means same person.

The trade-off here is operational: recursive graphs require support tools that can traverse relationships, not just dump a flat profile. That means training agents to read graphs — which sounds heavy. But the alternative is agents who spend twenty minutes untangling someone else's data. Which cost do you prefer?

Foundations People Mix Up

Entity resolution vs. deduplication — not the same muscle

Most teams use these terms interchangeably. That's a mistake that costs you the first three sprints. Deduplication asks a narrow question: 'Are these two records identical?' Same email, same phone — merge them. Entity resolution asks something harder: 'Do these two records refer to the same person even when nothing matches cleanly?' One profile says 'Jon Smith, [email protected]'; another says 'Jonathan Smith, 555-0123'. No overlap. A dedup engine walks away. Resolution demands inference — device graphs, household links, probabilistic scoring. I have seen teams build a flat graph based on dedup logic and then wonder why a single user appears as three separate nodes the moment their email changes. The catch is simple: dedup is a subset of resolution, not a synonym. Treat them as equal and you will rebuild your identity layer within six months.

Not every social checklist earns its ink.

Not every social checklist earns its ink.

Not every social checklist earns its ink.

Not every social checklist earns its ink.

Not every social checklist earns its ink.

Recursive vs. hierarchical vs. flat — pick the wrong shape and context leaks

A flat graph stores every identity claim on a single node. Simple. Fast. But what happens when a person uses two browsers, three devices, and a shared family iPad? Flat says 'one node, all attributes'. That sounds clean until you try to answer 'which device belongs to the user versus the household'. Context evaporates. Recursive graphs nest nodes — a person node containing device nodes containing session nodes. That preserves hierarchy but introduces traversal cost: query depth grows, and one bad join cascades. Hierarchical sits in the middle — parent-child relationships with strict levels — but rigid depth breaks when a sub-identity spans two levels unexpectedly. Which shape do you need? The answer depends on whether you query by person, by device, or by relationship across both. Most teams skip this analysis. They pick recursive because it sounds sophisticated. Then they discover that every analytical query requires six joins and a prayer.

“A graph’s shape is a bet on how your queries will evolve. Bet wrong, and context doesn’t leak — it evaporates.”

— architect post-mortem on a failed identity rebuild, internal engineering blog

Graph depth and its hidden costs

Depth sounds abstract until your identity graph takes 400 milliseconds to resolve a single user. That's what happens when a recursive structure nests five levels deep and every level requires a permission check, a merge rule, and a timestamp comparison. The hidden cost is not storage — it's scatter. Deep graphs scatter attributes across nodes, forcing traversal for every read. Writes become worse: inserting a new device under a person node triggers reconciliation across all sibling nodes. I worked on a system where a flat graph handled 10,000 lookups per second. The team 'upgraded' to recursive to capture household context. Lookups dropped to 1,200 per second. The causal factor was depth — four nested hops for every request. The fix was not abandoning recursion; it was caching resolved paths and materializing the top two levels into a flat cache. That's the trade-off: depth buys relational fidelity but sells query speed. Without a throttling strategy — pre-computed edges, limited recursion depth, or indexed path tables — you bleed latency on every user touch. The smart teams prototype both shapes on real query patterns before committing to an identity storage schema. They simulate month-6 traffic, not day-1 traffic. That reveals the cost before the graph is built.

Patterns That Usually Hold Up

Star schema for flat graphs

Most teams start here because it maps directly to how JOINs work. You put a single identity row in the center—person ID, device ID, email hash—then fan out to fact tables: sessions, purchases, support tickets. I have seen this hold up well when every touchpoint resolves to one canonical entity. The trick is the grain. If your central table allows multiple rows per customer, the model folds. One team I worked with stored both user_id and anonymous_id in the same star column. That blew out their session attribution by 30% inside a week. Stick to single-type keys per row. A colleague calls this the “one boss rule”: every peripheral table reports to exactly one row in the identity hub. The catch is performance—star schemas punish deep traversal. You can’t walk a customer’s device graph three hops deep without ugly recursive CTEs. That’s fine if your queries never need to ask “which five devices did this person use last month?”. Most do.

Property graph model for recursive

Recursive identity graphs thrive when you model edges as first-class objects. Each device, email, or cookie becomes a node; each merge or login event becomes an edge with a timestamp and a confidence score. The pattern that holds: store edges in a separate table with source_id, target_id, relationship, and weight. No attributes on nodes except a type tag. Why? Because drift happens on the connections, not the things. I watched a team rebuild six months of pipeline work because they buried merge events inside user rows instead of edges. You lose the ability to say “this link expired.” Real deployment note: batch your edge inserts. One row per event per second kills write throughput above 50K events/day. We fixed this by windowing edges into 15-minute buckets and collapsing duplicates—lost 2% precision, gained 40x ingest speed. That trade-off is worth it. Property graphs shine when identity chains stretch across months. The cost is query complexity—every path traversal becomes a series of self-JOINs that DBAs hate.

Time-windowed identity stitching

Here is the pattern nobody talks about in architecture blogs: don’t resolve everything forever. Run identity stitching inside sliding windows—say, 30 days for cookie-to-email, 90 days for device-to-login. Outside the window, collapse the chain into a single placeholder node labeled “stale.” Why bother? Because recursive graphs rot. Old edges from abandoned devices create false positives that pull in wrong data. I have seen a dormant tablet from 2019 resurrect a dead persona and pollute a quarterly report. Time windows prevent that. The implementation is cheap: add a valid_until column to every edge and a scheduled job that marks expired links as inactive. Most teams skip this and pay later in manual cleanup sprints. A rhetorical question: would you rather delete three months of edge data or write the window rule upfront? The pattern holds when your identity resolution runs daily and your user base churns naturally. Not for login-required apps—those need permanent stitching. But for open-web tracking? Windows save you.

The real differentiator between flat and recursive isn’t philosophy—it’s the query your product team asks first. “Show me this user’s journey last week” works on flat. “Show me every device that touched this account before the password reset” forces recursive. Pattern-match to the question, not the hype.

“We spent four sprint cycles flattening a recursive graph. Six weeks later we rebuilt recursive because nobody could debug the merge logic.”

— data lead, series B identity platform

Anti-Patterns That Make Teams Rebuild

Over-normalization in recursive graphs

I once watched a team model every single device cookie interaction as a separate node in their recursive identity graph. Every click, every session token, every browser fingerprint—all vertices. The theory was pristine: total fidelity, no data loss, maximum relationship traceability. The reality? Seven seconds to resolve a single user lookup. The graph traversal depth hit twelve hops before they could answer "who is this person?" The recursive structure promised elegance but delivered a query nightmare—each JOIN chain became a disaster of exponential cost. That sounds fine until your API latency budget is 200 milliseconds and you're burning 4,000 on a profile merge.

The fix hurt: they collapsed 80% of those nodes into property arrays on a single identity vertex. Relationships preserved; query time dropped to 40ms. The mistake? Treating normalization as a moral virtue rather than a performance trade-off. Recursive graphs need ruthless pruning—ask yourself: does this entity earn its own node, or is it just metadata dressed up as a relationship?

Not every social checklist earns its ink.

Not every social checklist earns its ink.

Not every social checklist earns its ink.

Not every social checklist earns its ink.

Not every social checklist earns its ink.

"We spent six months building a graph that perfectly modeled reality. Then reality changed, and the graph refused to bend."

— Senior engineer, ad-tech platform migration postmortem

Premature flattening of known relationships

Another team flattened everything on day one—email, phone, device ID, loyalty card—all crammed into one flat identity row. Lookups were fast, sure. But then a household scenario hit: same credit card, different shipping addresses, two distinct people sharing one purchase history. The flat row couldn't express "these two profiles are linked by payment method but not by person." So they tried to unify them. Wrong call. Returns spiked because loyalty points merged between strangers. The seam blew out when a fraud detection model flagged a false positive—two siblings flagged as synthetic identity because the flat graph merged their signals.

The catch is this: flattening early kills the ability to represent weak, intermediate, or temporary relationships. A flat graph forces you into binary decisions—merge or don't merge—while reality runs on shades of gray. I have seen teams rebuild from scratch because they couldn't represent a "shared device but separate people" pattern without corrupting their entire identity cluster. Flat structures demand you know all your relationship types before you build. Which nobody does.

Ignoring query patterns when choosing shape

Most teams pick their graph shape based on how data arrives, not how it gets queried. Wrong order. If your primary use case is "show me all orders for this user in the last 30 days," a deep recursive graph with five levels of event nodes is suicide. But if your query is "find every identity that touched this IP address in the last hour," a flat table that buries IP relationships inside a JSON blob is equally broken. What usually breaks first is the JOIN that looked innocent during prototyping—three tables, indexed, fine. Then traffic doubles. Then triples. Then the query planner gives up and your dashboard loads blank white pages.

We fixed this once by building two views: a recursive graph for traversal-heavy operations (fraud scoring across devices) and a flat materialized table for OLTP lookups (profile page loads). Yes—two shapes. One write path, two read paths. The recursive side handles the "how are these related?" questions; the flat side handles the "give me this one person's data fast" questions. That duplication hurts. But not as much as the rewrite you'll face when your single-shape graph collapses under real query load. Honest—I have never seen a team regret building a read-optimized secondary layer. I have seen plenty regret not doing it.

Maintenance, Drift, and Long-Term Costs

Schema migrations in recursive vs. flat

You ship a new identity source — a second email provider, a hardware device ID, a guest checkout flow that now creates profiles. In a flat graph, that means a new column or a new table join. I have seen teams add three columns in one sprint, then six more the next quarter, until a single identity row spans 47 nullable fields. The recursive approach handles this differently: you add a new node type and a new edge label. That sounds cleaner until you realize every query now needs a new traversal pattern. What usually breaks first is the loading state — your frontend expects user.email but the graph returns four email nodes with different confidence scores, and nobody wrote the resolver for that.

The real cost isn't the first migration. It's the fifth. Flat schemas accumulate null-heavy columns that every team member must remember exist. Recursive schemas accumulate traversal paths that every query must explicitly choose. Which hurts more? Flat, honestly — because you can't delete a column once it's in production queries, but you can stop traversing a path you don't need. That said, recursive teams pay a tax most forget: every new identity source requires updating the graph traversal library, the cache invalidation logic, and the merge conflict resolver. Flat teams just update an ORM model and a migration file. Pick your poison.

Stale edges and decay

Edges rot. A flat graph stores a single snapshot — the current best email, the latest phone. When the data source goes dark, the flat record just sits there. Wrong value, but at least it's present. Recursive graphs keep historical edges, which is great for audit trails and terrible for trust. I fixed a production bug once where a user had thirty-seven edges to an old device token that hadn't sent a ping in fourteen months. Every traversal pulled that dead weight, degraded confidence scores, and slowed the merge step by 200 milliseconds per request. At scale that's not a blip — that's a revenue leak.

The maintenance trick most teams skip: set a half-life on every edge. Recursive graphs need a background sweeper that demotes or removes edges older than a threshold. Flat graphs need nothing — they decay by overwrite. That sounds like a win for flat until you realize you just lost the history that could tell you why the identity merged the wrong way. Trade-off is brutal: flat gives you cheap current state and silent history loss; recursive gives you expensive current state and complete history. Most teams rebuild because they pick one, then discover they needed the other.

'We spent four months building the perfect recursive graph. Then we spent six more explaining to product why a simple 'update email' took 900 milliseconds.'

— Senior engineer, identity platform postmortem

Not every social checklist earns its ink.

Not every social checklist earns its ink.

Not every social checklist earns its ink.

Not every social checklist earns its ink.

Not every social checklist earns its ink.

That 900 milliseconds came from traversing stale edges, not from graph complexity. The team hadn't budgeted for decay. They assumed edges lasted forever. They don't.

Operational cost of graph traversal at scale

Traversal depth kills you. A flat graph query is one index lookup — SELECT * FROM identities WHERE primary_key = ?. That's O(1) in practice, maybe O(log n) with a bad index. A recursive graph query for the same identity might walk five, ten, or forty nodes depending on merge history. At 100,000 lookups per minute, the difference between 2 milliseconds and 50 milliseconds is a whole server tier. I have watched flat graph deployments run on two machines while recursive graphs for the same volume needed twelve — not because the data was bigger, but because the query pattern was wider.

The hidden operational cost: caching is worse in recursive graphs. A flat identity has one canonical representation; you cache the row key and you're done. A recursive graph has multiple valid representations — partial merges, unmerged siblings, resolved composites — so your cache must store traversal results, not raw nodes. Every invalidation becomes a subtree delete. Teams often skip this and pay in cold-start latency spikes during traffic surges.

That said, flat graphs have their own scaling trap: join explosion. When you flatten every identity source into columns, you eventually run into the 1,600-column limit on some databases, or you start sharding by column group, which is just a flat implementation of a recursive idea. The maintenance cost cycle never ends — you just choose which bill comes due. One team I consulted rebuilt twice in three years: flat first (too many columns), then recursive (too slow), then a hybrid that stored flat records for active identities and recursive for merge history. That hybrid was the right call but took nine months to build. Most teams don't have that time.

When Not to Use Either Approach

Hybrid graphs that combine both

I watched a team rebuild their identity system three times in eighteen months. First they went fully recursive—every relationship traced back to a primordial node. The graph grew elegant and completely unusable for real-time lookups. Then they flattened everything into one giant table. Lookups got fast, but they lost the ability to ask "which profile belonged to which household last year?" The fix was neither pure approach. They kept a recursive graph for historical stitching and a flat view for daily matching. Two stores, same source data, different access patterns. That sounds like extra work—it's—but the alternative is choosing which failure mode you prefer.

Most teams skip this: you can materialize a flat index from a recursive graph every hour. The recursive layer resolves chains, the flat layer serves reads. The catch is sync latency. If your matching happens in seconds and your batch window is an hour, you serve stale connections. We fixed this by writing the flat index incrementally—every merge event pushed a delta. Wrong order? Start with the writes, then optimize reads. The hybrid only works if you define which side owns truth. Otherwise you maintain two systems that slowly disagree.

Purpose-built stores for fast lookups

Sometimes neither graph fits because the problem isn't graph-shaped. A mobile ad platform needed to match anonymous device IDs to known users within 50 milliseconds. Recursive traversal killed that budget. Flat graphs required pre-computing every possible path—combinatorial explosion, dead in weeks. They dumped the whole thing into a key-value store. Device ID as key, resolved profile ID as value. Zero traversal, zero joins. The trade-off: they lost all relationship history. But the product didn't need history. It needed speed and a 95% match rate. Probabilistic scoring handled the rest. The mistake would have been forcing identity into a graph structure because "everyone does identity graphs." Purpose-built stores win when your query pattern is one-dimensional and your context window is shallow.

"We spent three months building a beautiful recursive graph. Then we deleted it and used Redis. The graph was for us. The Redis was for the customer."

— lead engineer, real-time matching pipeline, after the rewrite

That hurts because it's true. The recursive graph let the team understand their data. The flat key-value store made the product work. Honest—if your primary operation is "give me the resolved ID for this token" and nothing else, you don't need a graph. You need a hash table with an expiry policy. The cost is losing the ability to lazily ask "why did these two profiles merge?" But you can log the merge decisions elsewhere and run the analysis as a batch job. Don't build a museum when you need a checkout counter.

When probabilistic matching is enough

Here is where most engineers overthink it. A flat graph assumes deterministic merges—this phone number equals that person. A recursive graph assumes you can trace any relationship. Both assumptions break in messy data. Duplicate emails. Typed names. Shared devices. The harder you push deterministic matching, the more manual overrides you create. I have seen teams with 40,000 manual merge rules. That's not an identity system. That's a spreadsheet with a latency problem. Probabilistic matching—scoring similarity across signals and grouping by threshold—sidesteps the whole recursive-versus-flat debate. You never store edges. You store feature vectors and run clustering on query. No graph, no traversal, no maintenance of relationship chains. The cost: you can't answer "show me the path between these two profiles" because there is no path. But if your use case is deduplication for campaign targeting, the path doesn't matter. The cluster matters. One team I consulted replaced a six-month-old recursive graph with a three-line cosine similarity function. Their match rate improved by 12%. The recursive graph had been collecting dust—beautiful, ignored, and wrong.

Open Questions and FAQ

Can you migrate from flat to recursive mid-project?

Yes—but expect to lose a week, maybe two. I have seen teams try this swap while live-traffic flows, and the seam blows out where they least expect it: the join logic. A flat graph stores every identity as a single row with columns like customer_id, device_id, email_hash. A recursive graph stores edges—pairs of identifiers with direction and weight. Migrating means you have to rebuild every profile from those edges, and if your original flat table had any manual merges or merges-by-fiat (someone clicked 'link these two'), those decisions vanish. The trick is to run both systems in parallel for three full sync cycles. Keep the flat graph as your source of truth; let the recursive graph prove it can reproduce the same clusters before you kill the old stack. Honest—most teams abandon the migration midway because they discover orphan records that the flat graph never surfaced.

How do privacy laws (GDPR, CCPA) affect graph design?

They hit recursive graphs harder. A flat graph lets you delete a user by wiping one row. A recursive graph requires you to traverse every edge that touches that identity—and edges can span tables, shards, even separate databases. The catch is that deletion under GDPR must be 'without undue delay.' When your recursive graph has five hops between a session ID and the canonical user, tracking down all those edges feels like pulling a thread through a sweater. One team I consulted stored edges in a triples store; they needed three engineers and a custom script to delete a single user. Their flat-graph counterpart did it in one SQL statement. That said, flat graphs struggle with the 'right to portability'—they often lack the provenance data to prove which devices belong to which person. Recursive graphs, by keeping edge metadata (timestamp, source system, confidence score), can hand over a clean audit trail. Choose your pain.

A graph that can't be pruned is a liability, not an asset.

— former privacy officer at a mid-market analytics firm, after a GDPR audit that cost €40,000 in engineering time

What's the right depth limit for a recursive graph?

Three hops. Four if you trust your deduplication logic absolutely. Beyond that, you risk what engineers call 'graph bleed'—where a single bad edge connects two entirely separate households. I have debugged a case where a recursive graph with depth-six rules merged a grandmother with her grandson's school-issued tablet because they shared a Wi-Fi network for one afternoon. The fix? Hard-cap depth at three, then run a weekly audit script that flags any cluster spanning more than ten identifiers. Wrong order: teams set the limit first, then discover their data has longer chains than they modeled. Do the audit first, then set the cap one hop above your median path length. Most production graphs settle at depth two. And if someone on your team says 'unlimited depth,' show them the door—that graph will drift into chaos inside six months. No exceptions.

Share this article:

Comments (0)

No comments yet. Be the first to comment!