Cassandra’s JVM heap size is a delicate balance; too small and you’ll see OutOfMemoryErrors or crippling Garbage Collection pauses, too large and you risk the OS killing your Cassandra process.

Let’s see how Cassandra actually handles memory and how we can tune it.

Imagine a Cassandra node. It’s constantly reading data from disk, processing requests, and writing data back out. All of this active work, plus the data itself being held in memory, needs space. That space is primarily the JVM heap.

Here’s a simplified view of a typical Cassandra memory footprint:

  • JVM Heap: This is where objects live – request data, internal data structures, caches, etc. This is what we’ll be tuning.
  • Off-Heap Memory: This is memory outside the JVM heap. Crucially, it includes the OS page cache. Cassandra relies heavily on the OS page cache to keep frequently accessed data blocks (SSTables) readily available without needing to hit disk every time. This is often more important than the JVM heap size.
  • Direct Byte Buffers: Used for I/O operations.

This is a live nodetool command showing a node’s memory usage. Notice the Heap memory and Off heap memory breakdown.

nodetool tpstats

# Example Output Snippet:
# ...
# Pool Name                     Active      Pending      Completed      Blocked  All blocked
# ...
# Native-Memory-Pool            0          0          458632902      0          0
# ...
# Heap memory (used/max): 15.23 GB / 16.00 GB
# Off heap memory (used/max): 20.50 GB / 20.50 GB
# ...

The Heap memory (used/max) shows the JVM heap. The Off heap memory (used/max) is a bit of a misnomer here; it includes direct byte buffers and also reflects the pressure on the OS page cache. If your off-heap usage is maxed out, it means the OS is struggling to keep data cached, and you might see disk I/O spikes.

The core of tuning Cassandra’s memory is understanding the JVM heap size and how it interacts with the OS page cache. The general recommendation is to allocate 50% of system RAM to the JVM heap, but no more than 31GB. Why 31GB? JVMs use a compressed pointer ("compressed oops") scheme for object pointers when the heap is below 32GB. Above 32GB, pointers become uncompressed, leading to a significant increase in memory usage for the same number of objects, and potentially worse GC performance.

So, if you have 64GB of RAM, you’d aim for a 32GB heap, but cap it at 31GB. If you have 128GB of RAM, you still cap it at 31GB, leaving 97GB for the OS page cache. If you have 16GB of RAM, you’d allocate 8GB to the heap.

To set this, you edit the jvm-server.options file (or jvm.options on older versions) in your Cassandra configuration directory. You’re looking for the -Xmx (maximum heap size) and -Xms (initial heap size) settings. It’s best practice to set them to the same value to avoid heap resizing pauses.

Example jvm-server.options:

# ... other options ...
-Xms8G
-Xmx8G
# ... other options ...

This sets both the initial and maximum heap size to 8GB.

Now, let’s cover the common pitfalls and how to fix them:

1. Heap Too Small (OutOfMemoryError):

  • Diagnosis: You’ll see java.lang.OutOfMemoryError: Java heap space in your Cassandra system log (system.log).
  • Cause: The JVM simply ran out of allocated heap space to create new objects.
  • Fix: Increase -Xmx and -Xms in jvm-server.options. For example, if you have 32GB RAM and a 4GB heap, change to -Xms16G -Xmx16G.
  • Why it works: Provides more room for active data structures and request processing.

2. Heap Too Small (Excessive GC Pauses):

  • Diagnosis: Long GCInspector warnings in system.log (e.g., GCInspector.java:146 - GC for 1.234s, 89% time, concurrent stops 200ms) or nodetool gcstats showing high Total GC time or long Max pause time. The node might become unresponsive.
  • Cause: The JVM is constantly running garbage collection because the heap is too small, leading to frequent, long pauses.
  • Fix: Increase -Xmx and -Xms in jvm-server.options. If you have 32GB RAM and a 4GB heap, change to -Xms16G -Xmx16G.
  • Why it works: A larger heap allows live objects to survive longer between GC cycles, reducing the frequency and duration of pauses.

3. Heap Too Large (OS OutOfMemoryError / OOM Killer):

  • Diagnosis: The Cassandra process is abruptly killed, or the OS logs Out of memory: Kill process ... (java) score ... (check /var/log/syslog or dmesg).
  • Cause: The total memory requested by the JVM heap plus off-heap memory (including OS page cache) exceeds the system’s physical RAM. The OS’s OOM killer intervenes.
  • Fix: Decrease -Xmx and -Xms in jvm-server.options. If you have 64GB RAM and a 48GB heap, change to -Xms31G -Xmx31G.
  • Why it works: Frees up RAM for the OS page cache and other system processes, preventing the OOM killer.

4. Wrong GC Algorithm:

  • Diagnosis: Very high GC pause times even with seemingly adequate heap size, or unusual GC behavior in nodetool gcstats or GC logs.
  • Cause: The default Garbage Collector (often G1GC on modern Java versions) might not be optimal for all Cassandra workloads.
  • Fix: Explicitly configure G1GC and tune its parameters if needed, or consider other collectors if benchmarks suggest it. Add -XX:+UseG1GC to jvm-server.options. For tuning G1GC, consider -XX:MaxGCPauseMillis=200 (a target for pause time) and -XX:G1HeapRegionSize=16M (adjust if you have very large heaps).
  • Why it works: G1GC is generally good for large heaps and aims for predictable pause times, but tuning specific parameters can improve its efficiency for Cassandra’s specific object allocation patterns.

5. Insufficient Off-Heap Memory (OS Page Cache Pressure):

  • Diagnosis: High disk I/O (e.g., iostat shows high %util or await), slow read/write latencies reported by nodetool cfstats, and nodetool info showing Off heap memory (used/max) at its limit, even if the JVM heap is not full.
  • Cause: The OS doesn’t have enough RAM left for its page cache after the JVM heap and other processes are accounted for. Cassandra cannot effectively cache SSTables, leading to constant disk reads.
  • Fix: Reduce JVM heap size (-Xmx, -Xms) in jvm-server.options to leave more RAM for the OS page cache. If you have 64GB RAM and a 31GB heap, you’re leaving ~33GB for the OS, which is usually sufficient. If you see this issue with a 31GB heap on 64GB RAM, investigate other memory-hungry processes or consider if the workload truly requires that much heap.
  • Why it works: Allocating more RAM to the OS page cache allows it to hold more SSTable data in memory, drastically reducing the need for disk seeks.

6. Incorrect MaxDirectMemorySize:

  • Diagnosis: java.lang.OutOfMemoryError: Direct buffer memory in system.log.
  • Cause: Cassandra uses direct byte buffers for I/O. If this limit is too low, it can cause OOMs.
  • Fix: Increase -XX:MaxDirectMemorySize in jvm-server.options. A common starting point is to set it equal to or slightly larger than your heap size, e.g., -XX:MaxDirectMemorySize=16G if your heap is 16GB.
  • Why it works: Provides enough contiguous memory for the JVM to allocate direct buffers for efficient I/O operations.

After making changes to jvm-server.options, you must restart the Cassandra service for them to take effect.

The next common issue you’ll encounter after tuning heap and GC is often related to disk performance or network saturation, as the node becomes more responsive and starts demanding more from its I/O subsystem.

Want structured learning?

Take the full Cassandra course →