Graph Databases for Beneficial Ownership: Mapping UBO Networks with Neo4j
Every KYB regulation on earth asks one deceptively short question: which natural person ultimately owns or controls this company? I have built this kind of plumbing twice, in KYB work at Agregar and in AfricaPEP, where Neo4j is the database of record for political-exposure networks. This post is the case for modeling ownership as a graph, made honestly: the math the regulation implies, the query shapes that follow, and the architecture I would defend, without the fake benchmark table this post used to carry.
The question is a path, so the data is a graph
FATF's standard sets the common test: identify natural persons holding roughly 25 percent or more of a company, directly or indirectly. That single word does all the damage. Direct ownership is a row in a table. Indirect ownership is a walk across several of them, usually crossing a corporate registry boundary at every step.
Notice what the regulation quietly implies mathematically. Effective ownership is a product over a path: a person holding 50 percent of a holding company that owns 60 percent of the customer holds 30 percent effectively, and with multiple routes between the same two entities you sum over paths. The data structure whose native operations are “walk paths of unknown length” and “aggregate along the way” is a graph. That, and not query speed, is the real argument for a graph database here: the model matches the question. People who design opaque structures are exploiting exactly this mismatch, betting that whoever screens them thinks in single-table rows.
The honest SQL comparison
An earlier version of this post included a benchmark table showing PostgreSQL timing out where Neo4j answered in milliseconds. I have removed it: I never ran that benchmark rigorously, and the literature is full of tuned-relational-vs-untuned-graph comparisons in both directions. The defensible claim is about the shape of the code, and it is damning enough on its own. Here is “find every natural person with effective ownership of at least 25 percent, through up to eight layers” in both systems:
-- PostgreSQL: recursive CTE
WITH RECURSIVE chain AS (
SELECT o.owner_id, o.company_id,
o.pct / 100.0 AS eff_pct,
ARRAY[o.company_id] AS visited
FROM ownership o
WHERE o.company_id = :customer
UNION ALL
SELECT o.owner_id, c.company_id,
c.eff_pct * o.pct / 100.0,
c.visited || o.company_id
FROM ownership o
JOIN chain c ON o.company_id = c.owner_id
WHERE NOT o.owner_id = ANY(c.visited) -- manual cycle guard
AND array_length(c.visited, 1) < 8 -- manual depth guard
)
SELECT p.name, SUM(chain.eff_pct) AS effective
FROM chain JOIN persons p ON p.id = chain.owner_id
GROUP BY p.name
HAVING SUM(chain.eff_pct) >= 0.25;// Cypher: the same question
MATCH path = (p:Person)-[r:OWNS*1..8]->(c:Company {id: $customer})
WITH p, reduce(pct = 1.0, o IN relationships(path) |
pct * o.pct / 100.0) AS eff
RETURN p.name, sum(eff) AS effective
ORDER BY effective DESC
// >= 0.25 filter applied on the aggregated resultBoth work. But look at where the intelligence lives. In SQL, the traversal machinery (the recursion, the visited-array cycle guard, the depth cap) is code you write, test, and maintain, and it is where the bugs live; forget the cycle guard and your first circular structure hangs the query. In Cypher, traversal is the language's native verb, and what remains is the actual domain logic: a path pattern and a fold over it. When the compliance team asks next month for “the same thing but through trusts and nominee directors too,” that is one pattern change in Cypher and a rewrite in SQL. In a domain where the questions mutate constantly, the maintainability gap matters more than any latency chart I could print.
Cycles are not the edge case. They are the domain.
Ownership data has a property that surprises engineers coming from org charts and category trees: it contains cycles, put there on purpose. A owns some of B, B owns some of C, C owns some of A. Cycles defeat naive recursion (the traversal never terminates), complicate the math (effective ownership around a loop is a converging series, not a product), and, most usefully, they are a signal in themselves: cross-shareholding exists in legitimate conglomerates, but in a shell network a cycle usually means the structure is doing work its paperwork will not admit to. Finding every cycle through a customer is one Cypher pattern:
MATCH path = (c:Company {id: $customer})-[:OWNS*2..8]->(c)
RETURN [n IN nodes(path) | n.name] AS cycle,
length(path) AS hopsI learned to respect cycles the embarrassing way, in AfricaPEP rather than in ownership data: an early relationship-traversal query on family networks assumed tree-shaped data, and a marriage between two politically-connected families produced a loop that pinned a CPU. Graph databases do not make cycle handling automatic, Cypher's default trail semantics and explicit depth bounds are still your responsibility, but they make it a visible, one-line decision instead of bookkeeping buried in a WHERE clause.
Modeling: control is more than ownership
The schema I use is small, because in graph modeling the power is in the traversal, not the attributes. Entities: people, companies, trusts, foundations. Relationships: OWNS with a percentage, effective dates, and, critically, a source property; DIRECTS for officers and nominees; CONTROLS for voting rights and veto mechanisms that confer control without equity; BENEFITS_FROM for trust relationships; and family or association edges, because FATF's definition of exposure extends to relatives and close associates, which is precisely the edge type AfricaPEP's graph carries in production.
Two modeling rules I hold with some conviction. First, provenance lives on the edge: every ownership claim carries where it came from and when it was seen, because in a compliance review “who says so” is as important as “what is said.” I made this mistake once in AfricaPEP in a different costume, storing position dates on a shared node where every holder of an office inherited the same term, and the fix was the same principle: facts about a relationship belong on the relationship. Second, model control channels as distinct edge types rather than one generic “linked to,” because the interesting query is often “who can direct this company's actions through any channel,” and that is a union over typed edges, not a blur over untyped ones.
The architecture: a projection, not a second truth
The wrong lesson to take from all this is “move the compliance stack to a graph database.” Case files, documents, screening logs, and audit trails are relational workloads, and auditors expect them where auditors expect them. The pattern that has served me is the projection: PostgreSQL stays the system of record, a scheduled job projects just the entity-and-edge subset into Neo4j, traversal questions run there, and the answers (UBO candidates, computed effective percentages, cycle flags) are written back as ordinary rows. The graph is disposable by design; if it drifts or the schema needs to change, you rebuild it from the source tables.
It is worth saying that this is a choice with two sane settings, and I run both. In AfricaPEP the graph is the system of record, because the product is the network, and PostgreSQL is the projection (a search index for fast fuzzy screening). In KYB work the relational store owns truth and the graph is the index. The rule that generalizes: whichever store answers your dominant question owns the truth, and the other should be rebuildable from it at any time. What you should not build is two databases that both believe they are authoritative.
The part the vendors skip: the data
Neo4j makes traversal elegant, and none of that elegance survives contact with bad edges. The genuinely hard part of beneficial ownership in African markets is upstream of any database. Many corporate registries on the continent are partially digitized or pay-per-search; Ghana's beneficial ownership register, launched under the Companies Act of 2019, is real progress but young; filings lag reality; and the same director appears as “Emeka Adeyemi,” “E. Adeyemi,” and “Adeyemi Emeka” across three filings. Which means every edge in your ownership graph is only as good as the entity resolution that decided those three strings are one person; graph quality is entity resolution wearing a different hat. Open datasets help more every year (OpenOwnership's register and OpenCorporates are the places to start), but for most African jurisdictions you are assembling evidence, not downloading truth, and your schema should admit that: source and date on every edge, and “unknown” as a first-class ownership value rather than a guess.
What I would tell you if you were building this
- Write the questions before the schema. Who is the UBO, are there cycles, who controls without owning, which customers share officers: the model in this post is just those four queries, reverse-engineered.
- Treat effective ownership as path math. Product along a path, sum over paths, converging series around cycles. If your implementation cannot state which of the three it is doing, it is doing none of them.
- Bound every traversal. Depth caps and cycle semantics are your job in every database; the graph just makes the decision legible.
- Put provenance on edges and keep the graph rebuildable. One store owns truth; the other is an index with a rebuild script.
- Budget for the data, not the queries. The Cypher in this post is an afternoon. The registry wrangling and entity resolution behind it are the project.
The graph side of this thinking runs live in AfricaPEP, where Neo4j holds political-exposure networks for all 54 African countries, and the story of building it is in the AfricaPEP case study. For where ownership graphs plug into the wider monitoring picture, see why rule-based AML systems fail.
Sources and further reading
- FATF, Guidance on Beneficial Ownership of Legal Persons (2023) (the Recommendation 24 revision behind modern UBO rules)
- OpenOwnership and OpenCorporates (the open beneficial-ownership and corporate registries)
- ICIJ Offshore Leaks Database (the Panama and Pandora Papers investigations that made ownership graphs famous, and were themselves built on Neo4j)
- Neo4j Cypher manual: patterns and variable-length traversal