[
https://issues.apache.org/jira/browse/SLING-13302?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Martin Schulze updated SLING-13302:
-----------------------------------
Description:
h2. Problem
{{DefaultQueryExecutor}} already caches the parsed {{TypeDefinitionRegistry}}
(keyed by SDL hash, bounded by {{schemaCacheSize}}), but still calls
{{makeExecutableSchema}} / {{RuntimeWiring}} on every execute/validate.
For large schemas this is expensive in CPU and heap. Under concurrent load many
Jetty threads rebuild the same executable {{GraphQLSchema}} in parallel and can
OOM ("death by a thousand cuts").
Historically the executable schema was left uncached on purpose:
{{RuntimeWiring}} closed over the request {{Resource}}, so a shared
{{GraphQLSchema}} would pin request-scoped resources across requests (see
SLING-10085 / schema cache comments).
h2. Proposed change
# Put the request {{Resource}} into {{GraphQLContext}} and resolve it (and live
OSGi {{DataFetcher}} / {{TypeResolver}} services) at fetch/resolve time, so
wiring no longer embeds a request {{Resource}}.
# Optionally cache {{GraphQLSchema}} by SDL hash, with per-key single-flight
({{ConcurrentHashMap}} + {{CompletableFuture}}) so concurrent misses share one
build.
# Gate the new cache with an OSGi metatype flag (e.g.
{{executableSchemaCacheEnabled}}, default {{false}}) so behaviour can be rolled
out / rolled back without a code change. Reuse {{schemaCacheSize}} as the LRU
bound.
No Caffeine/ehcache dependency — stay on JDK collections, consistent with the
existing registry LRU.
h2. Invalidation
Same content-addressed model as the registry cache: key = SHA-256(SDL). New SDL
→ new hash → miss. Optional clear on component reactivation.
h2. Related
Adobe SITES-48793 / production OOM under GraphQL load with large CF model
schemas.
h2. Testing
* Unit tests: cache hit reuses same {{GraphQLSchema}} instance; disabled path
builds independently; concurrent misses coalesce to a single build.
* Existing {{DefaultQueryExecutor}} / IT coverage for Resource-backed fetchers
still passes with {{Resource}} supplied via {{GraphQLContext}}.
was:
## Problem
DefaultQueryExecutor already caches the parsed TypeDefinitionRegistry (keyed by
SDL hash, bounded by schemaCacheSize), but still calls makeExecutableSchema /
RuntimeWiring on every execute/validate.
For large schemas this is expensive in CPU and heap. Under concurrent load many
Jetty threads rebuild the same executable GraphQLSchema in parallel and can OOM
("death by a thousand cuts").
Historically the executable schema was left uncached on purpose: RuntimeWiring
closed over the request Resource, so a shared GraphQLSchema would pin
request-scoped resources across requests (see SLING-10085 / schema cache
comments).
## Proposed change
1. Put the request Resource into GraphQLContext and resolve it (and live OSGi
DataFetcher / TypeResolver services) at fetch/resolve time, so wiring no longer
embeds a request Resource.
2. Optionally cache GraphQLSchema by SDL hash, with per-key single-flight
(ConcurrentHashMap + CompletableFuture) so concurrent misses share one build.
3. Gate the new cache with an OSGi metatype flag (e.g.
executableSchemaCacheEnabled, default false) so behaviour can be rolled out /
rolled back without a code change. Reuse schemaCacheSize as the LRU bound.
No Caffeine/ehcache dependency — stay on JDK collections, consistent with the
existing registry LRU.
## Invalidation
Same content-addressed model as the registry cache: key = SHA-256(SDL). New SDL
→ new hash → miss. Optional clear on component reactivation.
## Related
Adobe SITES-48793 / production OOM under GraphQL load with large CF model
schemas.
## Testing
- Unit tests: cache hit reuses same GraphQLSchema instance; disabled path
builds independently; concurrent misses coalesce to a single build.
- Existing DefaultQueryExecutor / IT coverage for Resource-backed fetchers
still passes with Resource supplied via GraphQLContext.
> [GraphQL] Cache executable GraphQLSchema in DefaultQueryExecutor to avoid OOM
> under concurrent load
> ---------------------------------------------------------------------------------------------------
>
> Key: SLING-13302
> URL: https://issues.apache.org/jira/browse/SLING-13302
> Project: Sling
> Issue Type: Bug
> Components: GraphQL
> Reporter: Martin Schulze
> Priority: Major
>
> h2. Problem
> {{DefaultQueryExecutor}} already caches the parsed {{TypeDefinitionRegistry}}
> (keyed by SDL hash, bounded by {{schemaCacheSize}}), but still calls
> {{makeExecutableSchema}} / {{RuntimeWiring}} on every execute/validate.
> For large schemas this is expensive in CPU and heap. Under concurrent load
> many Jetty threads rebuild the same executable {{GraphQLSchema}} in parallel
> and can OOM ("death by a thousand cuts").
> Historically the executable schema was left uncached on purpose:
> {{RuntimeWiring}} closed over the request {{Resource}}, so a shared
> {{GraphQLSchema}} would pin request-scoped resources across requests (see
> SLING-10085 / schema cache comments).
> h2. Proposed change
> # Put the request {{Resource}} into {{GraphQLContext}} and resolve it (and
> live OSGi {{DataFetcher}} / {{TypeResolver}} services) at fetch/resolve time,
> so wiring no longer embeds a request {{Resource}}.
> # Optionally cache {{GraphQLSchema}} by SDL hash, with per-key single-flight
> ({{ConcurrentHashMap}} + {{CompletableFuture}}) so concurrent misses share
> one build.
> # Gate the new cache with an OSGi metatype flag (e.g.
> {{executableSchemaCacheEnabled}}, default {{false}}) so behaviour can be
> rolled out / rolled back without a code change. Reuse {{schemaCacheSize}} as
> the LRU bound.
> No Caffeine/ehcache dependency — stay on JDK collections, consistent with the
> existing registry LRU.
> h2. Invalidation
> Same content-addressed model as the registry cache: key = SHA-256(SDL). New
> SDL → new hash → miss. Optional clear on component reactivation.
> h2. Related
> Adobe SITES-48793 / production OOM under GraphQL load with large CF model
> schemas.
> h2. Testing
> * Unit tests: cache hit reuses same {{GraphQLSchema}} instance; disabled path
> builds independently; concurrent misses coalesce to a single build.
> * Existing {{DefaultQueryExecutor}} / IT coverage for Resource-backed
> fetchers still passes with {{Resource}} supplied via {{GraphQLContext}}.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)