Skip to content

fix(ENGKNOW-3933): MAP/MULTIMAP cache key, single shared load and lower memory - #147

Merged
gmagnu merged 3 commits into
mainfrom
ENGKNOW-3933-gor-map-cache-key-fix-and-memory-quick-wins
Oct 1, 2026
Merged

gmagnu merged 3 commits into
mainfrom
ENGKNOW-3933-gor-map-cache-key-fix-and-memory-quick-wins

Conversation

@gmagnu

@gmagnu gmagnu commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Changes to MapAndListUtilities (the loaders behind MAP, MULTIMAP and INSET):

  1. Cache-key bug (correctness). The per-request cache key was built as "map"+filename+ic+oc.mkString(",")+asSet (and the same without asSet for multimap).
    • It left out caseInsensitive, so MAP -cis and a plain MAP of the same file in one request shared one cached map, and one of them returned wrong results.
    • It left out skipEmpty, so MAP -e and a plain MAP of the same file shared one cached map, although -e changes how values of duplicate keys are merged.
    • It had no separator between ic and oc, so ic=1, oc=[23] and ic=12, oc=[3] collided.
    • The key is now delimited and includes every parameter that affects the loaded map.
  2. Single load per session cache. Concurrent pipelines of a request share the session cache (for example pgor partitions), and each one missed the cache and built its own full copy of the map. Now the first caller registers a CompletableFuture per (session cache, key) and loads; the others close their own iterator and wait for that future.
    • Waiting is interruptible, so a cancelled query does not wait for the load to finish.
    • A failed load fails all of its waiters with the same error, instead of each retrying it in turn. The entry is removed afterwards, so a later call loads again.
    • Loads are keyed on the session cache instance, not the request id, so unrelated sessions that share a request id (e.g. -1) do not wait for each other.
  3. Value dedup. Equal value strings share one instance while a map loads. MAP value columns repeat heavily. The pool is capped at 100k distinct values, so maps with mostly unique values don't pay for a large pool. Values merged from duplicate keys are not pooled, as later duplicates replace them.
  4. MULTIMAP peak. Entries are moved into a presized output map instead of copied, so the ListBuffer map and the Array map are never both fully held.

Measurements

Heap retained after GC. Synthetic 1M-line files with values drawn from 50 distinct strings.

before after
Single map, 1M keys 160.8 MB 98.4 MB (−39%)
Multimap, 200k keys × 5 values, retained 90.4 MB 28.0 MB (−69%)
Multimap, peak during load 350.1 MB 305.9 MB (−13%)

Tests

  • New UTestMapAndListUtilities, 10 tests:
    • These fail on main and pass with this change:
      • -cis vs plain map and multimap caching
      • -e (skipEmpty) vs plain map caching
      • the ic/oc key collision
      • concurrent loads reading the file once: main read it 4,000 times with 4 threads instead of 1,000
      • a failed load failing its waiters once: 4 load attempts instead of 1 before the shared future
      • value string sharing for map and multimap, with multimap value order kept
    • These cover the shared load: a waiting caller can be interrupted, and sessions with the same request id but separate caches don't wait for each other. Both failed with the earlier per-request lock in this PR.
  • Map tests pass: UTestMapAndListUtilities, UTestGorMapMultimap, UTestDAGMap, UTestMultiMapLookup and the *UTestMapCommand* tests (91 tests). The new class passed 5 repeated runs.
  • :model:test passed on the first commit: 1,548 tests, 17 skipped.

🤖 Generated with Claude Code

…d lower memory

- Cache key did not include caseInsensitive and had no separator between
  ic and oc, so MAP -cis and plain MAP of the same file shared one cached
  map, and ic=1,oc=[23] collided with ic=12,oc=[3]. Use a delimited key
  with all parameters.
- Concurrent pipelines of a request (e.g. pgor partitions) sharing the
  session cache each built their own copy of the same map. Load under a
  per request and key lock, re-checking the cache after acquiring it.
- Share repeated value strings while loading a map (bounded pool), map
  value columns repeat heavily.
- MULTIMAP: move entries into a presized output map instead of copying,
  so both maps are not fully held at the same time.

Measured heap retained after GC (1M line files, values from 50 distinct
strings): single map 160.8 MB -> 98.4 MB, multimap 90.4 MB -> 28.0 MB,
multimap peak during load 350.1 MB -> 305.9 MB.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Junit Tests - Summary

4 863 tests  +12   4 692 ✅ +13   18m 33s ⏱️ - 2m 39s
  506 suites + 1     171 💤  -  1 
  506 files   + 1       0 ❌ ± 0 

Results for commit 7aaba85. ± Comparison against base commit 9536c2f.

♻️ This comment has been updated with latest results.

Test and others added 2 commits October 1, 2026 11:45
MAP -e and plain MAP of the same file and columns shared one cache entry,
although skipEmpty changes how values of duplicate keys are merged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Replace the per-request lock in loadOnce with a CompletableFuture per
(session cache, key), so callers waiting for a load:
- can be interrupted, and close their own iterator instead of holding it
- get the result or the failure of that load, rather than each retrying
  a failed load in turn
- only wait for loads into the same session cache, not for unrelated
  sessions that happen to share a request id

Also stop pooling merged values of duplicate map keys, they are replaced
by later duplicates, and make the concurrent load test wait for all
threads to start.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gmagnu gmagnu changed the title fix(ENGKNOW-3933): MAP/MULTIMAP cache key, single load per request and lower memory fix(ENGKNOW-3933): MAP/MULTIMAP cache key, single shared load and lower memory Oct 1, 2026
@gmagnu
gmagnu marked this pull request as ready for review October 1, 2026 11:59
Comment on lines +121 to +123
private def cacheKey(kind: String, filename: String, ic: Int, oc: Array[Int], asSet: Boolean,
caseInsensitive: Boolean, skipEmpty: Boolean): String =
s"$kind|$filename|$ic|${oc.mkString(",")}|$asSet|$caseInsensitive|$skipEmpty"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice!

@cesarvp-gdx cesarvp-gdx left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM!

@gmagnu
gmagnu merged commit d70d7f4 into main Oct 1, 2026
14 checks passed
@gmagnu
gmagnu deleted the ENGKNOW-3933-gor-map-cache-key-fix-and-memory-quick-wins branch October 1, 2026 15:24
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