[ 
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. 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:
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}}.



> [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. 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)

Reply via email to