Skip to content

fix: memoize JsonSchemaGenerator output per connector - #103

Closed
hongwei1 wants to merge 2 commits into
develop-obpfrom
fix/jsonschema-generator-cache
Closed

hongwei1 wants to merge 2 commits into
develop-obpfrom
fix/jsonschema-generator-cache

Conversation

@hongwei1

@hongwei1 hongwei1 commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Summary

JsonSchemaGenerator.messageDocsToJsonSchema walks every message type's field tree via Scala runtime reflection on every call, with no cache anywhere in the file -- pure waste, since the schema is static per connector for the life of the JVM. The caller's Redis-backed cache (Http4s600) also silently falls through to a full recompute on any Redis GET failure, so this path has no cache at all when Redis is having a bad day.

Not the production incident's cause (see #102): JsonSchemaGenerator doesn't appear in that incident's JFR samples, and there were zero Redis failures in the log window. This PR is independent hardening for a real gap in this file, not a fix for that incident -- an earlier version of this description claimed otherwise.

Change

Memoize the result in-process, keyed by connector name, using the same memoizeSyncWithImMemory + CacheKeyFromArguments pattern already proven correct by Helper.getRequiredFieldInfo. Guava-backed, no Redis dependency -- a second, independent cache alongside the existing Redis one, not a replacement.

Test plan

  • Compile -- BUILD SUCCESS
  • MessageDocsJsonSchemaTest (8/8) -- identical output with caching enabled
  • JsonSchemaGeneratorCacheTest (3/3) -- cache-hit output matches, measurable speedup, per-connector isolation

Superseded -- squashed together with #102 and #104 into one combined commit, PR'd upstream directly: OpenBankProject#2920

messageDocsToJsonSchema walks every message type's full field tree via
Scala runtime reflection (recursive <:</=:= subtype checks) to build
the schema, with no caching anywhere in this file. The caller in
Http4s600 does have a Redis-backed cache in front of it, but that one
silently falls through to a full recompute whenever Redis is
unreachable or slow -- which is exactly what happens once the reflection
cost itself starts causing GC pressure, turning this into a positive
feedback loop under sustained polling.

For a given connector the message docs, and therefore the schema, are
static for the life of the JVM, so memoize the result in-process
(Guava-backed, no Redis dependency) keyed by connector name, using the
same memoizeSyncWithImMemory + CacheKeyFromArguments pattern already
proven out by Helper.getRequiredFieldInfo. This is a second, independent
line of defense alongside the existing Redis-backed cache, not a
replacement for it.

Verified against the existing MessageDocsJsonSchemaTest suite (8/8
scenarios passing across rabbitmq/rest/akka connectors, including
draft-07 schema validation), confirming the cached path returns
identical output.
MessageDocsJsonSchemaTest already covers output correctness end to
end via HTTP, but a broken cache (wrong key derivation, wrong TTL
handling) could still pass a correctness-only check by silently
recomputing every time -- which is exactly what happened once already
while writing this fix, caught only by the compiler rejecting a
cache-key placeholder with the wrong tuple arity. This adds a direct,
non-HTTP test of the cache itself: identical output on a hit, a
measurable speedup on a hit (median of several warm calls against a
cold call, sized against a case-class tree wide/deep enough that the
reflection cost isn't lost in JIT/GC noise), and that different
connector names get independent cache entries.
@sonarqubecloud

Copy link
Copy Markdown

@hongwei1

Copy link
Copy Markdown
Owner Author

Superseded — squashed together with #102 and #104 into one combined commit, PR'd upstream directly: OpenBankProject#2920

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