DEV Community

shubham shaw
shubham shaw

Posted on

Redesigning Organizational Hierarchy Queries in HR Platforms

When our HR system's manager dashboard began taking twelve seconds to load during morning shift changes, the culprit was not database hardware. It was recursive permission evaluation.

Every time a team leader logged in, the system dynamically traversed the organizational tree to figure out who reported to whom. In technical terms, we suffered from the N+1 query problem, where fetching a single list of employees triggered hundreds of follow-up database requests just to build the reporting chain.

We replaced dynamic recursion with a materialized path pattern, which means storing the full reporting hierarchy directly on each employee record as a simple searchable string of manager identifiers.

Evaluating permissions dropped from hundreds of database calls to a single indexed lookup, bringing page load times under two hundred milliseconds. However, the trade-off was write complexity. Whenever an executive changed departments, updating their entire sub-tree required writing hundreds of rows at once, creating temporary write-locks during reorganizations.

We accepted that rare structural changes could take two seconds to process so that millions of daily worker reads remained instantaneous. But it makes me question whether enterprise platforms jump to graph databases too quickly when relational path patterns offer simpler guarantees.

How do you balance read performance against batch update costs when designing reporting hierarchies in your applications?

architecture #sql #backend #database

Top comments (1)

Collapse
 
mads_hansen_27b33ebfee4c9 profile image
Mads Hansen

The key question changes when the hierarchy is also an authorization boundary: readers must see either the old tree or the new tree, never a half-moved subtree.

If the whole materialized-path rewrite fits in one transaction, MVCC can preserve that property even if the write takes two seconds. I’d add a lock budget and retry policy, then test concurrent permission reads and competing moves. If the subtree is too large for one transaction, give the hierarchy a version: build the next version in batches, validate it, then atomically flip a tenant/root pointer so all readers switch together.

Useful invariants are cycle-free ancestry, exactly one parent, path prefix matching the actual ancestor chain, and no orphaned descendants. A graph database does not remove those correctness requirements; the relational design can remain simpler if the cutover semantics are explicit.