
This blog is written by AI for SEO
Neo4j Vector Index Memory: Sizing Agent Memory Accurately
Every team building persistent memory for AI agents hits the same wall: embedding vectors eat memory. Store them in Neo4j next to your entity graph and cluster sizing stops being guesswork. Allocate too little RAM and your queries hit disk page faults, latency jumps from milliseconds to seconds, and your agents stall mid-execution.
This guide shows how to calculate your actual Neo4j vector index memory footprint before you provision hardware. You'll start with the exact formula from Neo4j's operations documentation, scale it across realistic agent-memory counts, budget the surrounding Java Virtual Machine heap and graph page cache, and see when a pure in-memory vector architecture forces expensive over-provisioning.
Step 1: Know where a Neo4j vector index lives (Lucene HNSW, OS filesystem cache, not the page cache)
The most common mistake engineers make when provisioning Neo4j for vector search is assuming vectors sit inside the Neo4j page cache. They don't.
Neo4j implements vector search with Apache Lucene's Hierarchical Navigable Small World (HNSW) graphs. The native graph store holds nodes, relationships, and raw properties, and it's managed directly by server.memory.pagecache.size. Lucene vector indexes are different. They live outside the JVM heap and outside the page cache, in memory-mapped files served by the operating system's filesystem cache (the Linux kernel page cache).
This split creates a conflict that's easy to miss. Give 80% of your server RAM to the Neo4j JVM heap and graph page cache, and the operating system has only 20% left. When Lucene traverses its HNSW vector layers, it relies entirely on that remainder. If the index doesn't fit in the OS cache, Lucene falls back to disk I/O for vector distance calculations during approximate nearest neighbor (ANN) scans. Disk reads wreck HNSW performance because every hop is a random lookup across multidimensional arrays. For low retrieval latency, the entire Lucene HNSW index should sit in the operating system's filesystem cache.
Step 2: Apply the operations manual formula (4 bytes x dimensions x vectors, plus 8 bytes x vectors x HNSW M)
Neo4j's operations documentation gives an explicit formula for the index size of an unquantized vector property. Sizing needs two separate allocations: raw vector storage and graph traversal topology.
The formula for the total raw index size in bytes is:
Index Size = (4 * dimensions * vectors) + (8 * vectors * M)
Here's what each term means:
4 * dimensions * vectors: Float32 values each take 4 bytes, but embeddings do not universally use float32; Neo4j supports other coordinate types, and vector index storage precision can vary with quantization. If your model produces 768 dimensions, each vector uses4 * 768 = 3,072 bytesof raw float data. Multiply that by the number of indexed vectors.8 * vectors * M: HNSW connects nodes into a multi-layer navigational graph. The parameterMsets the maximum number of bidirectional links each node keeps per layer (typically between 16 and 64). Each link is an internal pointer of 8 bytes. The 8 × M estimate is an upper estimate of HNSW graph size per indexed vector, not a fixed per-entity allocation; with M = 16 it estimates 128 bytes per vector (128 MB per million vectors), but actual measured graph files can be smaller. RaiseMto 64 to improve recall on dense clusters and you spend 512 bytes per entity on graph routing alone.
The growth is strictly linear. Index size scales proportionally with entity count, dimensions, and link density, and there's no default compression.
Step 3: Work the example: 1 million 768-dimension vectors, then scale it to 10 million and to 1536 dimensions
Plug real numbers into the equation to see how vector workloads behave under typical agent-memory profiles.
Take an agent framework tracking 1,000,000 conversation snippets or tool-call events, embedded with an open-source model like bge-base-en-v1.5 (768 dimensions) at the standard M = 16:
Raw vector floats:
4 bytes * 768 dims * 1,000,000 = 3,072,000,000 bytes (~3.07 GB)HNSW graph links:
8 bytes * 1,000,000 * 16 = 128,000,000 bytes (~0.128 GB)Total Lucene index memory:
~3.2 GB
3.2 GB is modest. But agent memory grows fast as agents log structured dialogue, entity mentions, and external document chunks. Scale the same system to 10,000,000 vectors with the same model:
Raw vector floats:
4 * 768 * 10,000,000 = ~30.72 GBHNSW graph links:
8 * 10,000,000 * 16 = ~1.28 GBTotal Lucene index memory:
~32 GB
Now switch to a commercial embedding model like OpenAI's text-embedding-3-large (1536 dimensions) for 10,000,000 entities:
Raw vector floats:
4 * 1536 * 10,000,000 = ~61.44 GBHNSW graph links:
8 * 10,000,000 * 16 = ~1.28 GBTotal Lucene index memory:
~62.72 GB
Tune M to 32 or 64 to protect recall on small clusters and the graph structure adds another 2.5 GB to 5 GB. And this 63 GB figure covers only the Lucene vector index in OS filesystem memory. It includes no graph nodes, no relationships, no Cypher execution memory, and no database buffers.
Step 4: Add the rest of the budget: JVM heap, page cache for graph data, and OS cache headroom; consider quantization
A Neo4j instance has three independent memory consumers on one machine: JVM Heap, Neo4j Page Cache, and the OS Filesystem Cache.
JVM Heap: Neo4j needs heap to execute Cypher queries, manage client connections, build transaction state, and coordinate clustering. For moderate agent memory workloads, allocate 8 GB to 16 GB. For heavy multi-agent concurrency, 32 GB is common.
Page Cache: This holds the core graph store (node structures, labels, and relationship records). For fast graph traversals, your active graph working set must sit in memory. For an enterprise graph backing 10 million entities, page cache typically runs from 16 GB to 64 GB depending on property density.
OS Cache Headroom: As Step 3 showed, 10 million vectors at 1536 dimensions need ~63 GB of OS cache just to keep the Lucene index in memory.
Add them up for a production 10M-vector graph environment:
32 GB (Heap) + 32 GB (Page Cache) + 63 GB (Lucene OS Cache) + 8 GB (OS kernel headroom) = 135 GB RAM
That pushes you to a 192 GB or 256 GB RAM cloud instance (such as an AWS r6i.8xlarge or r7i.8xlarge) to run this workload without hitting disk paging.
Scalar quantization (SQ) or product quantization (PQ) can cut vector float storage by 50% to 75%, at the cost of recall accuracy. The Lucene HNSW graph connectivity cost stays fixed, and dequantization adds CPU overhead during scoring.
Step 5: Decide what to keep in Neo4j (mature Cypher ecosystem and tooling) and when RAM-bound sizing becomes the constraint
Neo4j is an established graph platform. Cypher is mature, visualization tools like Neo4j Bloom are polished, and the enterprise administration tooling is well tested. If your main workload is deep, complex graph traversals with occasional semantic lookups across small entity sets, keeping your data in Neo4j makes sense.
The friction starts when semantic retrieval becomes central to every step of an AI agent memory architecture. Multi-agent systems, document RAG, and episodic agent logs produce millions of vectors per month. If every vector you write has to live in an in-memory Lucene structure next to an in-memory graph cache, graph complexity stops driving your database sizing. Vector volume does, and you're RAM-bound.
Paying for high-RAM instances to hold cold conversation embeddings or background entity vectors that get read once a week is wasteful. Teams often patch this by bolting a separate vector database (like Pinecone or Qdrant) onto Neo4j. That brings its own synchronization tax: dual writes, two-phase commits, ID mapping bugs, and two infrastructure bills. For the full tradeoffs between split and integrated architectures, read our breakdown of vector database vs graph database requirements for AI.
Step 6: See how HelixDB sizes differently: vectors on nodes and edges, an index tiered across memory, disk and S3-compatible storage, pre-filtering scoped to related nodes; open-source vs Cloud
When sizing becomes an operational bottleneck, look at how other graph-vector engines handle vector memory. HelixDB is an open-source OLTP graph-vector database built in Rust to solve this exact problem.
Neo4j's Lucene indexes have to sit in memory to avoid performance degradation. HelixDB takes a different route: it uses SlateDB as its storage engine and tiers index and vector data across memory, local disk, and S3-compatible object storage. Hot vector index segments stay cached in RAM, warm data sits on fast local disk, and colder historical records settle in durable object storage. You can scale to tens of millions of embeddings without provisioning huge RAM buffers to hold inactive vectors.
HelixDB stores vectors as native, top-level properties on both nodes and edges. Because the graph and vector engines share one storage format, vector pre-filtering can be scoped strictly to related nodes. Instead of running an expensive approximate nearest neighbor search across your entire global index and then post-filtering for relationship relevance, HelixDB runs similarity scans only across the candidate entities found during a graph traversal. An unrestricted vector search in HelixDB caps at 800 effective results, and queries reject rather than silently clamp if candidate streams exceed 1,000,000 entities. That keeps memory usage predictable during heavy multi-hop executions.
You can run HelixDB as an Apache-2.0 open-source engine embedded in your application, as a self-hosted Docker server (backed by in-memory storage, disk, or S3-compatible object storage), or managed on HelixDB Cloud, which uses an object-storage-backed multi-tenant architecture with auto-scaling reader nodes. Compare the approaches directly in our guide on HelixDB vs Neo4j.
Step 7: Try it: helix init local, helix start dev, then star github.com/HelixDB/helix-db and read the quickstart docs
Testing relationship-scoped vector memory doesn't require rewriting your application layer or tuning JVM parameters. You can set up a local HelixDB environment in seconds with the Helix CLI.
First, install the CLI and initialize a local environment:
helix init local
helix start devHelixDB takes JSON queries over POST /v2/query or runs through native SDKs in TypeScript (@helix-db/helix-db), Python, Go, and Rust. This TypeScript SDK example runs a graph traversal with vector search scoped directly to related nodes:
import { g, VectorDistanceMetric } from "@helix-db/helix-db";
// Query entity relationships and run similarity search over target nodes
const query = g
.node("AgentContext", { id: "agent_session_104" })
.out("OBSERVED")
.hasLabel("MemoryEntity")
.vectorSearch("embedding", {
vector: [0.042, -0.018, 0.119, /* ... */],
metric: VectorDistanceMetric.Cosine,
k: 10
});The HelixDB Quickstart has end-to-end examples, and you can star the project on GitHub to follow development.
Conclusion
Getting the RAM math right is what separates agent memory systems that work from ones that buckle as vector volumes grow. Neo4j offers a proven Cypher ecosystem, but its in-memory Lucene vector index puts the full cost of vector growth on your server's RAM budget.
If you're provisioning RAM-bloated instances just to keep embedding indexes in memory, rethink your storage architecture. Star the HelixDB repository on GitHub, test the Rust-native engine locally with helix start dev, and build your agent memory on a storage layer that scales across memory, disk, and object storage in one engine.