Database DesignGraph Databases (Neo4j)Hard⏱️ ~2 min

Production Graph Database Implementation Patterns

Data Modeling Patterns

Make relationships explicit with direction and type. Keep properties on edges when used for filtering or ranking to avoid extra node fetches during traversals (one lookup instead of two per hop). For supernodes, split adjacency into layers: RECENT_FOLLOW (last 10,000 in memory) vs HISTORICAL_FOLLOW (cold storage). Precompute top-K neighborhoods offline using signals like recency or interaction frequency, storing as separate relationship types.

Query Shaping

Enforce hard limits on hop depth (3-4 max) and fan-out at each hop (100-1,000 before sampling). Apply degree-based pruning and time/recency filters early in traversal to minimize visited set. Use path uniqueness and cycle checks to prevent combinatorial blowups. Start from selective anchors: specific indexed nodes (user_id, product_id) rather than broad scans, because graph queries without a starting point degenerate into full scans.

Caching and Locality

Keep hot adjacency lists in memory using LFU (Least Frequently Used: evicts items accessed least often) or LRU (Least Recently Used: evicts oldest items) hybrids tuned for power-law access (most queries hit a small fraction of nodes repeatedly while the majority of nodes are rarely accessed). For distributed deployments, use per-shard caches to preserve locality rather than global caches which suffer from skew. Denormalize small aggregates like degree counts or last-N neighbors to avoid full adjacency scans.

Monitoring

Track: traversal depth and fan-out distributions, visited node counts per query, cache hit ratio on adjacency lists (target 95%+), replication lag (target <1 second), and tail latencies broken down by hop count.

💡 Key Takeaways
Model relationships explicitly with direction and type; keep filter/ranking properties on edges to reduce hop cost from 2 lookups to 1
Split supernode adjacency: RECENT (last 10K, in memory, <5ms), TOP_K (precomputed relevant), HISTORICAL (cold storage, offline-only)
Enforce query limits: max 3-4 hop depth and 100-1,000 fan-out per hop with sampling, plus path uniqueness and cycle detection
Start from selective anchors (indexed nodes like user_id) not broad scans; graph queries without starting point degenerate into full scans
Use per-shard LFU/LRU caches tuned for power-law access (small fraction of hot nodes); global caches suffer from skew
Monitor cache hit ratio (target 95%+), replication lag (<1s), visited nodes per query, and tail latency by hop count
📌 Interview Tips
1Edge property optimization: store "interaction_score" on FOLLOWS edge. Query "top 10 interactions" filters at edge without fetching follower nodes. 50% fewer lookups.
2Selective anchor: query "friends of user_123" starts from indexed user_id. Query "users who like jazz" with no anchor scans all nodes with like_jazz=true. Use index.
3Power-law caching: 5% of users (celebrities) receive 80% of queries. LFU keeps them in cache. LRU would evict them if briefly inactive.
← Back to Graph Databases (Neo4j) Overview