Skip to content

Reduce per-container memory: cache by GUID instead of Container, share JSON keys - #8134

Merged
labkey-jeckels merged 16 commits into
developfrom
fb_containerAndJsonObjectMemory
Oct 10, 2026
Merged

labkey-jeckels merged 16 commits into
developfrom
fb_containerAndJsonObjectMemory

Conversation

@labkey-jeckels

@labkey-jeckels labkey-jeckels commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Tasks


Rationale

Lower the server's memory footprint on servers with many containers and concurrent bulk JSON API requests. A heap dump from a production server with about 74,000 workbooks and 41 concurrent bulk list inserts showed two main costs:

Source In the dump Estimated after this PR
Bulk JSON payloads: the parse trees of 41 concurrent insertRows requests 1.88 GB, about 108 B per cell about half of that, ~0.95 GB saved, because each distinct key is stored once per request instead of once per row
Caches holding one entry per container 0.39 GB across all caches ~175–230 MB saved
Container objects 194,496 for 74,338 containers; stale copies are kept alive as keys in Container-keyed caches one per container

Savings are estimates from object layouts and the dump's entry counts, not measurements of the patched code. The dump came from the outage in GH Issue 1639.

BVT-EHR currently runs develop's specimen module rather than this branch's, so expect container-delete failures there until GH Issue 1643 is resolved.

Related Pull Requests

Changes

  • Container-scoped caches use the container's GUID as their key instead of the Container object, and the cache layer now rejects Container keys
  • Container cache invalidation removes only the affected entries instead of clearing whole caches, and reuses cached Container objects
  • The folder-type and reports usage metrics and the required-modules calculation query the database directly instead of filling a cache entry for every container
  • The pipeline-root and file-root tables are each cached as a single map, and the WebDAV folder cache is capped at a fixed size
  • The search crawler indexes only a workbook's own lists, not its parent's
  • Bulk JSON request parsing stores one copy of each repeated object key
  • Optimize some metrics (folder type counts, etc) to do a single SQL query to get results instead of iterating all containers

…ull containers in query uncache, use a set for property URI uncaching
labkey-jeckels added a commit to LabKey/targetedms that referenced this pull request Oct 6, 2026
## Rationale
Companion to LabKey/platform#8134, which rejects `Container` objects as
cache keys to reduce per-container memory.

## Related Pull Requests
- LabKey/platform#8134

## Changes
- Container-scoped caches use the container's GUID as their key instead
of the `Container` object
…nObjectMemory

# Conflicts:
#	experiment/src/org/labkey/experiment/PropertyQueryChangeListener.java
#	query/src/org/labkey/query/persist/QueryDefCache.java
@labkey-jeckels
labkey-jeckels marked this pull request as ready for review October 7, 2026 16:28
@labkey-jeckels
labkey-jeckels requested a review from a team October 7, 2026 16:28
@labkey-adam

Copy link
Copy Markdown
Contributor

Coming to this idea a little late, but did you consider leaving all the Container key caches and code in place, but translating those Containers to GUIDs (and vice versa) at a lower level? I think we could accomplish this easily with a SimpleCache<Container, V> that wraps an EhSimpleCache<GUID, V> and does the Container <-> GUID translations.

labkey-adam added a commit that referenced this pull request Oct 9, 2026
## Rationale
Using `Container` objects as cache keys can be very expensive,
especially on servers with a large number of containers and workbooks.
We want to cache using each container's `GUID` instead of the Container
object itself. See analysis here:
#8134

## Changes
The above referenced PR and companion PRs switch every Container-keyed
cache to a GUID-keyed cache. Each cache then implements new
get/put/remove and other behaviors that map Containers to GUID and back,
as needed.

This PR takes a different approach. Cache factory methods now optionally
receive the cache key's class. In the case of a Container key, the
factory returns a special cache whose methods take Containers but whose
underlying cache uses GUID keys. Passing in Container.class is the only
change needed to switch to this more efficient cache.

`SimpleKeyMappingCache` implements the five critical cache methods,
mapping keys using the passed in `Function`s. This general-purpose class
can be used to map any cache key to a more efficient alternative.
CacheManager has a registry that maps key classes to specialized caches
that can be a SimpleKeyMappingCache (or some other specialized
implementation).

`ContainerKeySimpleCache` is a one-line class that extends
SimpleKeyMappingCache and provides the simple Container <-> GUID mapping
functions.

## Tasks
- [x] Claude Code Review
- [x] Test Automation - junit tests added
…Cache and DomainPropertyManager. Move key validation to EhSimpleCache (CacheWrapper validation is too early since it hits before keys are mapped by SimpleKeyMappingCache).
…Cache and DomainPropertyManager. Move key validation to EhSimpleCache (CacheWrapper validation is too early since it hits before keys are mapped by SimpleKeyMappingCache). Pass debugName to EhSimpleCache for better logging.
* JSONTokener that returns one shared String instance per distinct object key, so a bulk payload's rows don't each
* hold their own copy of every column name.
*/
public class KeySharingJSONTokener extends JSONTokener

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to contribute this to the JSONObject project? That way, they'd maintain it. And others could benefit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue opened. If the maintainer likes the idea I'll create a PR. stleary/JSON-java#1091

@labkey-jeckels
labkey-jeckels merged commit 5271ec4 into develop Oct 10, 2026
12 checks passed
@labkey-jeckels
labkey-jeckels deleted the fb_containerAndJsonObjectMemory branch October 10, 2026 14:55
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.

2 participants