Skip to content

Commit

Permalink
Add warmup documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
tobias-tengler committed Dec 2, 2024
1 parent 1910a98 commit 1f25ea7
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 10 deletions.
4 changes: 4 additions & 0 deletions website/src/docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,10 @@
"path": "dependency-injection",
"title": "Dependency injection"
},
{
"path": "warmup",
"title": "Warmup"
},
{
"path": "global-state",
"title": "Global State"
Expand Down
12 changes: 2 additions & 10 deletions website/src/docs/hotchocolate/v15/performance/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,9 @@ In this section we will look at some ways of how we can improve the performance

# Startup performance

The first GraphQL request issued against a Hot Chocolate server will most of the time take a little longer than subsequent requests. This is because Hot Chocolate has to build up the GraphQL schema and prepare for the execution of requests.
Instead of the schema being created lazily, you can move its creation to the server startup and also specify warmup tasks.

We can however delegate this task to the startup of the application instead of the first request, by call `InitializeOnStartup()` on the `IRequestExecutorBuilder`.

```csharp
builder.Services
.AddGraphQLServer()
.InitializeOnStartup()
```

This will create the schema and warmup the request executor as soon as the app starts. This also brings the added benefit that schema errors are surfaced at app startup and not on the first request.
[Learn more server warmup](/docs/hotchocolate/v15/server/warmup)

# Persisted operations

Expand Down
78 changes: 78 additions & 0 deletions website/src/docs/hotchocolate/v15/server/warmup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
title: Warmup
---

By default the creation of Hot Chocolate's schema is lazy. If a request is about to be executed against the schema or the schema is otherwise needed, it will be constructed on the fly.

Depending on the size of your schema this might be undesired, since it will cause initial requests to run longer than they would, if the schema was already constructed.

In an environment with a load balancer, you might also want to utilize something like a Readiness Probe to determine when your server is ready (meaning fully intialized) to handle requests.

# Initializing the schema on startup

If you want the schema creation process to happen at server startup, rather than lazily, you can chain in a call to `InitializeOnStartup()` on the `IRequestExecutorBuilder`.

```csharp
builder.Services
.AddGraphQLServer()
.InitializeOnStartup()
```

This will cause a hosted service to be executed as part of the server startup process, taking care of the schema creation. This process is blocking, meaning Kestrel won't answer requests until the construction of the schema is done. If you're using standard ASP.NET Core health checks, this will already suffice to implement a simple Readiness Probe.

This also has the added benefit that schema misconfigurations will cause errors at startup, tightening the feedback loop while developing.

# Warming up the executor

Creating the schema at startup is already a big win for the performance of initial requests. Though, you might want to go one step further and already intialize in-memory caches like the document and operation cache, before serving any requests.

For this the `InitializeOnStartup()` method contains an argument called `warmup` that allows you to pass a callback where you can execute requests against the newly created schema.

```csharp
builder.Services
.AddGraphQLServer()
.InitializeOnStartup(
warmup: async (executor, cancellationToken) => {
await executor.ExecuteAsync("{ __typename }");
});
```

The warmup process is also blocking, meaning the server won't start answering requests until both the schema creation and the warmup process is finished.

Since the execution of an operation could have side-effects, you might want to only warmup the executor, but skip the actual execution of the request. For this you can mark an operation as a warmup request.

```csharp
var request = OperationRequestBuilder.New()
.SetDocument("{ __typename }")
.MarkAsWarmupRequest()
.Build();

await executor.ExecuteAsync(request);
```

Requests marked as warmup requests will be able to skip security measures like persisted operations and will finish without actually executing the specified operation.

Keep in mind that the operation name is part of the operation cache. If your client is sending an operation name, you also want to include that operation name in the warmup request, or the actual request will miss the cache.

```csharp
var request = OperationRequestBuilder.New()
.SetDocument("query testQuery { __typename }")
.SetOperationName("testQuery")
.MarkAsWarmupRequest()
.Build();
```

# Keeping the executor warm

By default the warmup only takes place at server startup. If you're using [dynamic schemas](/docs/hotchocolate/v15/defining-a-schema/dynamic-schemas) for instance, your schema might change throughout the lifetime of the server.
In this case the warmup will not apply to subsequent schema changes, unless you set the `keepWarm` argument to `true`.

```csharp
builder.Services
.AddGraphQLServer()
.InitializeOnStartup(
keepWarm: true,
warmup: /* ... */);
```

If set to `true`, the schema and its warmup task will be executed in the background, while requests are still handled by the old schema. Once the warmup is finished requests will be served by the new and already warmed up schema.

0 comments on commit 1f25ea7

Please sign in to comment.