Skip to content

Slice 3/5: feat(calm-hub): add a GitHub API response cache - #3064

Merged
jpgough-ms merged 4 commits into
mainfrom
slice/3001-s3-generic-primitives
Sep 9, 2026
Merged

jpgough-ms merged 4 commits into
mainfrom
slice/3001-s3-generic-primitives

Conversation

@jpgough-ms

@jpgough-ms jpgough-ms commented Sep 8, 2026 •

Copy link
Copy Markdown
Member

Description

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)

Affected Components

  • CALM Hub (calm-hub/)

Testing

  • I have tested my changes locally
  • I have added/updated unit tests
  • All existing tests pass

Checklist

  • My commits follow the conventional commit format
  • I have added tests for my changes (if applicable)
  • My changes follow the project's coding standards

@github-actions github-actions Bot added the calm-hub Affects `calm-hub` label Sep 8, 2026
@jpgough-ms jpgough-ms changed the title feat(calm-hub): add generic Caffeine-backed cache service Slice 3/5: feat(calm-hub): add generic Caffeine-backed cache service Sep 8, 2026
@jpgough-ms

Copy link
Copy Markdown
Member Author

Checked one item carried over from #3001's review: a prior finding said calm.cache.ttl.* was read by nothing. Not applicable to this cache service as it landed here — CaffeineCacheService.put(key, value, ttl) takes TTL as a per-call Duration argument, not a config property, so there's no calm.cache.ttl.* to be dead. No action needed.

@jpgough-ms
jpgough-ms marked this pull request as draft September 8, 2026 10:26
@rocketstack-matt
rocketstack-matt force-pushed the slice/3001-s3-generic-primitives branch from 9dbc88c to 55c78a2 Compare September 8, 2026 16:48
@jpgough-ms
jpgough-ms force-pushed the slice/3001-s3-generic-primitives branch from 55c78a2 to c6b58dd Compare September 9, 2026 06:51
Base automatically changed from slice/3001-s2-observability-deps to main September 9, 2026 08:57
byrash and others added 2 commits September 9, 2026 09:57
Extracted from #3001. CalmCacheService/CaffeineCacheService is a
generic TTL cache with no GitHub types — unused until a later slice
wires a consumer.

Original-PR: #3001
- Collapse CalmCacheService/CaffeineCacheService into a single concrete
  CalmCacheService bean. calm-hub puts an interface in front of a
  service only where multiple backends are selected at runtime (see
  store/ and its Mongo/Nitrite producers); a cache with one
  implementation and no near-term second one doesn't fit that pattern.
- Add getList(key, elementType), so the one known consumer
  (GitHubVersionService, landing in slice 5) doesn't need
  get(key, List.class) plus an unchecked cast to use a typed list.
- Document the class contract: null values are ignored on put, a type
  mismatch on get/getList returns empty rather than throwing, and TTL
  is not refreshed on read.
- Reject a null ttl in put() with a clear NPE at the call site instead
  of failing later inside Caffeine's Expiry callback.
- Make maximumSize configurable via calm.cache.max-size (default
  10000, constructor-injected) instead of hardcoded, following the
  module's @ConfigProperty convention.
- Make the Caffeine Ticker injectable via a package-private
  constructor, matching the LongSupplier pattern already used by
  SchemaMigrationInProgressFilter, and use it to make TTL expiry
  tests deterministic instead of Thread.sleep.
- Rewrite the concurrent-access test to assert on final cache state
  via the submitted Futures, instead of only checking a
  CountDownLatch that a finally-block would trip even if every task
  had thrown.
@rocketstack-matt
rocketstack-matt force-pushed the slice/3001-s3-generic-primitives branch from b046305 to 444bb5c Compare September 9, 2026 08:57
@jpgough-ms
jpgough-ms marked this pull request as ready for review September 9, 2026 10:12
@jpgough-ms
jpgough-ms marked this pull request as draft September 9, 2026 10:49
…ric primitive

CalmCacheService was framed as a generic, calm-hub-wide TTL cache
(org.finos.calm.cache package, generic get/put/getList/evict API).
That's a real trap in a multi-instance deployment: it's a per-JVM,
in-memory cache with no cross-instance coordination, and a
generic-shaped, generic-packaged, generic-Javadoc'd class invites
being reached for to cache a Mongo/Nitrite-backed read, where a write
on one instance would never invalidate another instance's cached
read. It's safe for its actual sole use (GitHub API responses) only
because that backend is read-only through calm-hub and already
tolerates per-instance eventual consistency by design. See #3073 for
the full writeup.

- Move + rewrite as org.finos.calm.store.github.util.GitHubApiResponseCache
  (the established package for GitHub-only helpers), with
  @LookupIfProperty(calm.database.mode=github) matching every sibling.
- Replace the generic get/put/getList API with purpose-built methods —
  getVersions/putVersions, getContentAtSha/putContentAtSha — baking
  both TTLs (5 min, 365 days) and both key formats in as private
  constants. This is the real structural barrier: reusing this for
  Mongo-backed data now requires editing the class, not just calling
  it differently.
- Drop evict/evictByPrefix: unused by all production code, and
  unnecessary once both TTLs are fixed rather than caller-supplied.
- Drop the custom Expiry/CacheEntry machinery (it existed specifically
  to support a variable per-call TTL) for two plain Caffeine caches
  with expireAfterWrite. Keep the injectable Ticker for deterministic
  expiry tests.
- Rename calm.cache.max-size to calm.github.cache.max-size.
- Full Javadoc rewrite stating the cross-instance limitation plainly,
  why it's safe here, and an explicit prohibition on reuse for
  Mongo/Nitrite data.

Test file rewritten to match: the type-mismatch and evict tests no
longer apply under the new API; added independent-expiry coverage for
the two caches.
@jpgough-ms jpgough-ms changed the title Slice 3/5: feat(calm-hub): add generic Caffeine-backed cache service Slice 3/5: feat(calm-hub): add a GitHub API response cache Sep 9, 2026
- getVersions/putVersions aliased the cache's internal List to
  whatever the caller passed in or read back — a caller mutating
  either reference would silently corrupt the shared cache entry for
  every other concurrent reader until TTL expiry. putVersions now
  stores an immutable List.copyOf(...), so both directions are safe.
- Consolidated the four near-identical get/put method bodies into
  private generic read/write helpers, and the two near-identical
  Caffeine.newBuilder() chains into a private buildCache helper.
@jpgough-ms
jpgough-ms marked this pull request as ready for review September 9, 2026 11:22
cache.put(key, value);
}

private static String versionsKey(String repoFullName, String filePath) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Cache keys are built by string concatenation with : as delimiter. repoFullName can't contain : (GitHub forbids it in owner/repo names), so the only reachable collision is a : inside filePath — not reachable today since CALM document paths don't have those. Still, since this class has no consumer yet, worth closing off for free: a record VersionsKey(String repo, String path) (and similarly for content) as the cache key type removes the delimiter question entirely instead of relying on paths never containing :. (non-blocking nit)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Merged as-is — good nit, but agreed it's non-blocking. Tracked to pick up on the next slice: replace the :-delimited string keys with typed VersionsKey/ContentKey records so the collision question goes away structurally rather than relying on GitHub's repo-naming rules.

@jpgough-ms
jpgough-ms merged commit 09f9f23 into main Sep 9, 2026
22 checks passed
@rocketstack-matt
rocketstack-matt deleted the slice/3001-s3-generic-primitives branch September 9, 2026 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

calm-hub Affects `calm-hub`

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants