Neo4j Architecture & Native Graph Storage
Module Duration: 6-8 hours
Learning Style: Deep Technical + Implementation Details + Performance Analysis
Outcome: Understand how Neo4j achieves O(1) traversals and ACID guarantees at the storage layer
Introduction: What Makes Neo4j “Native”?
Most databases were built for tables, then retrofitted for graphs. Neo4j was designed from day one for graphs. Key Difference: Index-free adjacencyPart 1: High-Level Architecture
System Layering
Core Components
1. Cypher Parser- Converts Cypher query string → Abstract Syntax Tree (AST)
- Validates syntax
- Generates execution plan (like SQL EXPLAIN)
- Cost-based optimization (estimates row counts, cardinality)
- Rule-based optimization (predicate push-down, etc.)
- Executes query plan using operators (Scan, Filter, Expand, etc.)
- Pipelined execution (streaming results)
- Ensures ACID properties
- Write-Ahead Log (WAL) for durability
- Locking for isolation
- Native graph storage (nodes, relationships, properties)
- Page cache (in-memory buffer pool)
- Indexes (for lookups)
Part 2: Storage Layout
Store Files
Neo4j stores graph data in multiple fixed-size record stores:Node Store
Node Record Format (15 bytes in Neo4j 4.x):- In Use (1 bit): Is this record active? (0 = deleted, 1 = active)
- Next Rel (35 bits): Pointer to first relationship (ID)
- Next Prop (36 bits): Pointer to first property (ID)
- Labels (5 bytes): Inline label storage or pointer to label array
Relationship Store
Relationship Record Format (34 bytes in Neo4j 4.x):- First Node: Source node ID
- Second Node: Target node ID
- Rel Type: Relationship type ID (index into relationship type store)
- First Prev/Next Rel: Doubly-linked list for source node’s relationships
- Second Prev/Next Rel: Doubly-linked list for target node’s relationships
- Next Prop: Pointer to first property
- Read Node A’s record → get
Next Rel = 100 - Read Relationship 100 → check if
First Node == AorSecond Node == A - Follow appropriate
Next Relpointer (100 → 101 → NULL)
- In relational DB: O(log N) per relationship (index scan across all edges)
- In Neo4j: O(D) (only scan node’s own relationships)
- Graph with 1 billion relationships
- Node A has 10 relationships
- Relational: O(log 10⁹) ≈ 30 operations
- Neo4j: O(10) = 10 operations
Property Store
Property Record Format (25 bytes):
Property Chain:
Properties form a singly-linked list:
String Store
Stores strings > 12 characters: String Record (128 bytes, stores 120 chars + metadata):Dense Node Optimization
Problem: Nodes with many relationships (high degree) slow down traversals. Example: Celebrity node with 1M followers- Read celeb node → Get
Next Rel Group = 7000 - Scan relationship groups for
FOLLOWED_BY(typically < 10 types) - Follow
First Outgoingpointer - Scan only FOLLOWED_BY relationships (not all 1M!)
Part 3: Indexes
Label Indexes (Default)
When you create a label, Neo4j automatically creates an index for lookups:neostore.labeltokenstore.db.
Property Indexes
Create Index:schema/index/lucene-*/1/ (despite “lucene” in path, it’s native in 4.x+)
B+Tree Structure:
- Look up “Bob” in index → O(log P)
- Get node ID (20)
- Read node 20 → O(1)
Composite Indexes
Create:(name, age) → ("Alice", 30)
Benefit: Single index lookup instead of two separate lookups + intersection.
Full-Text Indexes
Create:- Fuzzy search
- Tokenization (split “software engineer” → [“software”, “engineer”])
- Relevance scoring (TF-IDF)
Vector Indexes (Neo4j 5.x)
Create:- Recommendation systems (user/item embeddings)
- Semantic search (document embeddings)
- Image similarity (CNN features)
Part 4: Transaction Management
ACID Guarantees
Neo4j provides full ACID transactions:- Atomicity: All-or-nothing (commit or rollback)
- Consistency: Constraints enforced (uniqueness, existence)
- Isolation: Transactions don’t interfere (locking)
- Durability: Committed transactions survive crashes (WAL)
Write-Ahead Log (WAL)
Purpose: Durability (survive crashes) Location:neostore.transaction.db.0, neostore.transaction.db.1, …
Process:
CREATE_NODE: Node ID, labels, propertiesCREATE_RELATIONSHIP: Rel ID, type, start/end nodes, propertiesSET_PROPERTY: Node/Rel ID, key, valueDELETE_NODE: Node ID
Locking
Neo4j uses pessimistic locking (lock before modify). Lock Types:-
Read Locks (Shared locks):
- Multiple transactions can hold read locks simultaneously
- Prevents writes while reading
-
Write Locks (Exclusive locks):
- Only one transaction can hold a write lock
- Blocks reads and writes
- TX1 locks Alice, waits for Bob
- TX2 locks Bob, waits for Alice
- Deadlock!
Isolation Levels
Neo4j provides Read Committed isolation by default: Behavior:- Reads see committed data only (no dirty reads)
- Phantom reads possible (another TX inserts/deletes between reads)
Part 5: Page Cache
Memory Architecture
Page Cache = Neo4j’s in-memory buffer pool (like PostgreSQL’s shared buffers). Purpose:- Cache frequently accessed pages (node, relationship, property records)
- Reduce disk I/O
pagecache.size = 0.5 × (RAM - heap - OS)
Example (64GB RAM):
Page Cache Structure
Page Size: 8KB (default, configurable) Cache Entry:- Find least recently used page
- If dirty (modified), flush to disk
- Evict page
- Load new page into cache
Monitoring Page Cache
- Hits: Requests served from cache (fast!)
- Faults: Requests requiring disk read (slow)
- Evictions: Pages evicted to make room
hits / (hits + faults)
Target: > 95% hit rate
Example:
Part 6: Query Execution
Explain and Profile
Explain (planning only, no execution):Query Operators
NodeIndexSeek:Example Query Analysis
Query:-
NodeIndexSeek: Look up Alice in
Person.nameindex- Cost: O(log P)
- DB Hits: 2
- Rows: 1
-
Expand: Traverse
:KNOWSrelationships- Cost: O(D), D = Alice’s degree (e.g., 10 friends)
- DB Hits: 10 (relationship records)
- Rows: 10
-
Filter: Check
friend.age > 25- Cost: O(10)
- DB Hits: 10 (read
ageproperty) - Rows: 6 (assume 6 friends > 25)
-
Projection: Extract
nameandage- Cost: O(6)
- DB Hits: 6 (read
nameproperty) - Rows: 6
-
Sort: Order by
age DESC- Cost: O(6 log 6) ≈ 15
- DB Hits: 0 (in-memory)
- Rows: 6
-
ProduceResults: Return to client
- Rows: 6
Optimization Tips
1. Use Indexes:- Missing indexes (NodeByLabelScan instead of NodeIndexSeek)
- High DB Hits
- Cartesian products (avoid!)
Part 7: Performance Characteristics
Time Complexity
Typical Values:
- N = 1,000,000 (total nodes)
- D = 50 (average degree)
- Neo4j: 50³ = 125,000
- Relational: (10⁶)³ = 10¹⁸ (not feasible!)
Benchmark: Social Network Queries
Setup:- 1M users
- 50M friendships (average 50 friends/user)
Query 2: Friends of friends
Query 3: Friends up to 3 hops
Key Takeaway: Neo4j shines for deep traversals (2+ hops).
Part 8: Hands-On Exercises
Exercise 1: Explore Store Files
Task: Inspect Neo4j store files and calculate sizes- How many nodes?
150M / 15 bytes = 10M nodes - How many relationships?
2.1G / 34 bytes ≈ 61.7M relationships - Average degree?
61.7M × 2 / 10M ≈ 12.3
Exercise 2: Analyze Query Plans
Query:- Identify bottleneck operator (highest DB Hits)
- Check if index is used (NodeIndexSeek vs NodeByLabelScan)
- Optimize query (add indexes, limit results)
Exercise 3: Measure Page Cache Hit Rate
Summary
Native Graph Storage:- Fixed-size records (O(1) access by ID)
- Pointers for relationships (no JOINs!)
- Index-free adjacency (O(D) traversal, not O(N))
- Label indexes (filter by node type)
- Property indexes (B+tree, O(log N))
- Full-text indexes (Lucene)
- Vector indexes (HNSW for ANN)
- Full ACID guarantees
- Write-Ahead Log (durability)
- Pessimistic locking (read/write locks)
- Deadlock detection
- 10-1000x faster than relational for traversals
- Scales with degree (D), not graph size (N)
- Page cache critical for performance (aim > 95% hit rate)
What’s Next?
Module 4: Cypher Query Language Mastery
Master Cypher syntax, pattern matching, aggregations, and advanced query techniques