Skip to content

Rename cache_mb to node_cache_mb and count it in heap bytes - #78

Merged
venkat1701 merged 11 commits into
mainfrom
feat/node-cache-mb
Oct 11, 2026
Merged

venkat1701 merged 11 commits into
mainfrom
feat/node-cache-mb

Conversation

@venkat1701

@venkat1701 venkat1701 commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Second half of moving the page cache off the heap, stacked on #69. #77 reads pages through memory maps, so raw bytes sit in the OS page cache. The one cache HStore still sizes itself holds decoded nodes on the Java heap. This renames it to say that, and makes the number mean what it says.

What changed

  • cache_mb becomes node_cache_mb, HSTORE_NODE_CACHE_MB and --node-cache-mb. cache_mb never shipped in a release, so instead of an alias the server refuses it at startup from the config file, the environment or a flag, with a message that names node_cache_mb. The cache_nodes message now points at the new name too.
  • Engine side: EngineOptions.cacheBytes / withCacheBytes become nodeCacheBytes / withNodeCacheBytes, and the Studio stats key cacheBytes becomes nodeCacheBytes.
  • Cached nodes are now counted at an estimate of their heap size (Node.heapBytes) instead of their page size.
  • The default is 256 MB.
  • Docs: configuration, Docker, overview and pages.

Why the count changed

On an Azure D8ds_v5 at scale 4, a setting of 256 kept 1.1 to 1.3 GiB on the heap. A decoded node takes three to six times the size of its page, so counting pages said little about memory.

Node.heapBytes works it out from the node: 144 bytes for the node and its summary, its arrays, then 96 bytes plus the encoded value for each leaf entry, or 128 bytes per child reference in a branch. I got the constants by filling the cache through the benchmark's read workloads and comparing retained heap with what was cached. With the estimate, the cache's own count came out 10 to 40% under the measured heap at scales 1 and 4. Before, it was 3.6 to 5.8 times under.

Why 256

An earlier version of this PR set the default to 64, counted in page bytes, which was really about 250 MB of heap. 256 counted in heap keeps that same memory and the same read speed. On the same VM at scale 4, a cache about twice that size made member reads about 40% faster and two-hop reads about 58% faster, so anyone with a large working set should raise it. The server binary uses the serial collector, and every full collection walks this cache, so it isn't free to raise.

Testing

  • ServerTest: the new setting from the environment, the 256 default, and cache_mb and cache_nodes refused from file, environment and flag, each message naming node_cache_mb. Taking cache_mb out of the refused list fails the test.
  • Full suite including the benchmark module: 98 tests pass, at each commit.
  • The heap estimate was checked as described above, at scales 1 and 4.

Merge order

Ideally after #77, since misses are cheaper with it. Without it, misses still work; they're just slower.

Once pages are read through memory maps, the OS caches the raw bytes and
this budget only covers decoded nodes on the heap. The engine option,
the builder and the Studio stats key now say so. Nothing here has been
released yet, so no alias is kept.
cache_mb read as if it limited all caching, but it only sizes the
decoded nodes kept on the heap. Setting cache_mb in hstore.conf, as
HSTORE_CACHE_MB or as --cache-mb now fails at startup with a message
that names node_cache_mb, the same way cache_nodes does. The cache_nodes
message points to the new name as well.
The server is a native image with the serial collector, so everything
the node cache holds is copied at every full collection. With page reads
going through memory maps, a miss costs a copy out of the OS page cache
and a decode rather than a system call, so a smaller decoded cache costs
much less than it used to. The default drops from 256 to 64 MiB.
The configuration page explains that only decoded nodes live on the
heap, that the serial collector walks all of them on a full collection,
and that both cache_mb and cache_nodes are refused at startup.
node_cache_mb counted each node as the size of its page, but a decoded
node takes three to six times that on the heap. At scale 4 a setting of
256 kept 1.1 to 1.3 GiB, so the setting said little about memory.

Node.heapBytes estimates the decoded size from the node itself: a fixed
144 bytes for the node and its summary, its arrays, 96 bytes plus the
encoded value for each leaf entry, and 128 bytes per child reference.
The constants come from comparing the retained heap with the cache's
contents after the benchmark's read workloads. The estimate came out 10
to 40% under the measured heap at scales 1 and 4, against 3.6 to 5.8
times under before.
With nodes counted at their heap size, 64 would hold about a quarter of
what the old default of 64 held, because that one was really using about
250 MB of heap. 256 keeps the same memory and the same read speed, and the
number now says what it uses. The setting's help text says so too.
@venkat1701 venkat1701 changed the title Rename cache_mb to node_cache_mb and default it to 64 MiB Rename cache_mb to node_cache_mb and count it in heap bytes Oct 10, 2026
Each of the 16 shards got a sixteenth of the budget, and a node heavier than
its shard was never cached. With a small node_cache_mb and large pages that
silently skipped every interior node. The cache now picks as many shards as
leave room for 64 pages each, from 1 up to 16.
A huge value overflowed when it was shifted into bytes, and 0 failed later with
an engine message that didn't name the setting.
Now that nodes count at their heap size, a 16 GiB cap could never bind before a
4 GiB heap ran out. The docs also describe the cap and the estimate as they are
now.
@venkat1701
venkat1701 changed the base branch from feat/cache-mb to main October 11, 2026 09:13
@venkat1701
venkat1701 merged commit 58af9b2 into main Oct 11, 2026
@venkat1701
venkat1701 deleted the feat/node-cache-mb branch October 11, 2026 09:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant