Skip to content

Commit 6adc986

Browse files
authored
Merge pull request #7545 from nikolajlauridsen/load-balancing-backoffice
Load balancing backoffice documentation
2 parents b414933 + 37566a9 commit 6adc986

File tree

6 files changed

+200
-0
lines changed

6 files changed

+200
-0
lines changed

17/umbraco-cms/SUMMARY.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
* [Running Umbraco in Docker](fundamentals/setup/server-setup/running-umbraco-in-docker.md)
4545
* [Umbraco in Load Balanced Environments](fundamentals/setup/server-setup/load-balancing/README.md)
4646
* [Load Balancing Azure Web Apps](fundamentals/setup/server-setup/load-balancing/azure-web-apps.md)
47+
* [Load Balancing the Backoffice](fundamentals/setup/server-setup/load-balancing/load-balancing-backoffice.md)
48+
* [SignalR In Load Balanced Environments](fundamentals/setup/server-setup/load-balancing/signalR-in-backoffice-load-balanced-environment.md)
4749
* [Standalone File System](fundamentals/setup/server-setup/load-balancing/file-system-replication.md)
4850
* [Advanced Techniques With Flexible Load Balancing](fundamentals/setup/server-setup/load-balancing/flexible-advanced.md)
4951
* [Logging With Load Balancing](fundamentals/setup/server-setup/load-balancing/logging.md)
@@ -286,6 +288,7 @@
286288
* [Content Settings](reference/configuration/contentsettings.md)
287289
* [Data Types Settings](reference/configuration/datatypes.md)
288290
* [Debug settings](reference/configuration/debugsettings.md)
291+
* [Distributed jobs settings](reference/configuration/distributedjobssettings.md)
289292
* [Examine settings](reference/configuration/examinesettings.md)
290293
* [Exception filter settings](reference/configuration/exceptionfiltersettings.md)
291294
* [FileSystemProviders Configuration](reference/configuration/filesystemproviders.md)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Load Balancing the Backoffice
2+
3+
This article contains specific information about load balancing the Umbraco backoffice. Ensure you read the [Load Balancing Overview](./) and relevant articles about general load balancing principles before you begin.
4+
5+
By default, the Umbraco load balancing setup assumes there is a single backoffice server and multiple front-end servers. From version 17, it's possible to load balance the backoffice. This means there's no need to differentiate between backoffice servers and front-end servers. However, this requires some additional configuration steps.
6+
7+
## Server Role Accessor
8+
9+
Umbraco has the concept of server roles to differentiate between backoffice servers and front-end servers. Since all servers will be backoffice servers, we need to add a custom `IServerRoleAccessor` to specify this.
10+
11+
Start by implementing a custom `IServerRoleAccessor` that pins the role as `SchedulingPublisher`:
12+
13+
```csharp
14+
public class StaticServerAccessor : IServerRoleAccessor
15+
{
16+
public ServerRole CurrentServerRole => ServerRole.SchedulingPublisher;
17+
}
18+
```
19+
20+
You can now register this accessor either in `Program.cs` or via a Composer:
21+
22+
```csharp
23+
umbracoBuilder.SetServerRegistrar(new StaticServerAccessor());
24+
```
25+
26+
This will ensure that all servers are treated as backoffice servers.
27+
28+
## Load Balancing Repository Caches
29+
30+
One of the issues with load balancing the backoffice is that all servers will have their own repository caches. This means that if you make a change on one server, it won't be reflected on the other servers until their cache expires.
31+
32+
To solve this issue, a cache versioning mechanism is used. This is similar to optimistic concurrency control. Each server has a version number for its cache. When a server makes a change, it updates the version identifier. The other servers can then check the version identifier before accessing the cache. If the cache is out of date, they invalidate it.
33+
34+
This means the server needs to check the version identifier before a cache lookup. By default, this behavior is disabled. It's only required when load balancing the backoffice.
35+
36+
You can enable this on the Umbraco builder, either in `Program.cs` or via a Composer:
37+
38+
```csharp
39+
umbracoBuilder.LoadBalanceIsolatedCaches();
40+
```
41+
42+
## SignalR
43+
44+
The Umbraco Backoffice uses SignalR for multiple things, including real-time updates and notifications. When load balancing the backoffice, it's important to ensure that SignalR is configured correctly. See the [SignalR in a Backoffice Load Balanced Environment](./signalR-in-backoffice-load-balanced-environment.md) document for information regarding this.
45+
46+
47+
## Background Jobs
48+
49+
If you have custom recurring background jobs that should only run on a single server, you'll need to implement `IDistributedBackgroundJob`. See [Scheduling documentation](../../../../reference/scheduling.md#background-jobs-when-load-balancing-the-backoffice) for more information.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# SignalR in a Backoffice Load Balanced Environment
2+
When load balancing the backoffice, we also need to take care of the client-to-server communication outside of web requests.
3+
Umbraco uses SignalR to abstract away these types of communication. This also allows us to support load balancing by replacing how the communication is done by introducing a backplane.
4+
5+
## Introducing a SignalR Backplane
6+
A SignalR backplane is akin to a load balancer for direct client-to-server web traffic. It keeps track of which client is connected to which server. So that when a client sends a message, it arrives at the right server. It also allows any connected server to send a message to all clients, even those that are not directly connected to it.
7+
8+
## Choosing the right backplane
9+
Choosing the right backplane comes down to a few factors:
10+
- Message throughput
11+
- Cost
12+
- What infrastructure you already have in place
13+
14+
Microsoft has a good list of available backplanes in its [SignalR load balancing article](https://learn.microsoft.com/en-us/aspnet/core/signalr/scale?view=aspnetcore-10.0), including a list of well known [third party offerings](https://learn.microsoft.com/en-us/aspnet/core/signalr/scale?view=aspnetcore-9.0#third-party-signalr-backplane-providers).
15+
16+
## Code examples
17+
The following code examples show how you can activate SignalR load balancing using an Umbraco composer.
18+
19+
{% hint style="info" %}
20+
Both Umbraco and these composers use `.AddSignalR()`. This duplication isn't a concern as the underlying code registers the required services as singletons.
21+
{% endhint %}
22+
23+
### Using existing infrastructure
24+
It is possible to use your existing database as a backplane. If this database is hosted in Azure it is not possible to enable Service Broker which will have an impact on message throughput. Nevertheless, it might be sufficient to cover your needs.
25+
For more information, check out the [GitHub page](https://github.com/IntelliTect/IntelliTect.AspNetCore.SignalR.SqlServer).
26+
- Add a reference to the `IntelliTect.AspNetCore.SignalR.SqlServer` NuGet package.
27+
- Add the following composer to your project:
28+
```csharp
29+
using Umbraco.Cms.Core.Composing;
30+
31+
namespace Umbraco.Cms.Web.UI.SignalRLoadBalancing;
32+
33+
public class SignalRComposer : IComposer
34+
{
35+
public void Compose(IUmbracoBuilder builder)
36+
{
37+
var connectionString = builder.Config.GetUmbracoConnectionString();
38+
if (connectionString is null)
39+
{
40+
return;
41+
}
42+
43+
builder.Services.AddSignalR().AddSqlServer(connectionString);
44+
}
45+
}
46+
```
47+
48+
### Azure SignalR Service
49+
- Set up a resource as described in the [Microsoft tutorial](https://learn.microsoft.com/en-us/azure/azure-signalr/signalr-quickstart-dotnet-core#create-an-azure-signalr-resource).
50+
- Make sure the `connectionstring` is set up under the following key: `Azure:SignalR:ConnectionString`.
51+
- Add a reference to `Microsoft.Azure.SignalR` NuGet package.
52+
- Add the following composer to your project:
53+
```csharp
54+
using Umbraco.Cms.Core.Composing;
55+
56+
namespace Umbraco.Cms.Web.UI.SignalRLoadBalancing;
57+
58+
public class SignalRComposer : IComposer
59+
{
60+
public void Compose(IUmbracoBuilder builder) => builder.Services.AddSignalR().AddAzureSignalR();
61+
}
62+
```

17/umbraco-cms/reference/configuration/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ A complete list of all the configuration sections included in Umbraco, by defaul
120120
* [Connection strings settings](connectionstringssettings.md)
121121
* [Content settings](contentsettings.md)
122122
* [Debug settings](debugsettings.md)
123+
* [Distributed jobs settings](distributedjobssettings.md)
123124
* [Examine settings](examinesettings.md)
124125
* [Exception filter settings](exceptionfiltersettings.md)
125126
* [Global settings](globalsettings.md)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Distributed jobs settings
2+
3+
The distributed jobs settings allow you to configure how Umbraco handles distributed background jobs in a load-balanced environment.
4+
5+
## Configuration
6+
```json
7+
"Umbraco": {
8+
"CMS": {
9+
"DistributedJobs": {
10+
"Period": "00:00:05",
11+
"Delay": "00:01:00"
12+
}
13+
}
14+
}
15+
```
16+
17+
## Settings
18+
19+
### Period
20+
21+
**Default:** `00:00:05` (5 seconds)
22+
23+
Specifies how frequently each server checks for distributed background jobs that need to be run.
24+
25+
A shorter period means jobs are picked up more quickly, but increases the frequency of database queries. A longer period reduces overhead but may introduce delays in job execution.
26+
27+
### Delay
28+
29+
**Default:** `00:01:00` (1 minute)
30+
31+
Specifies how long the server should wait after initial startup before beginning to check for and run distributed background jobs. This startup delay ensures that the application is fully initialized and stable before participating in distributed job processing.

17/umbraco-cms/reference/scheduling.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,57 @@ switch (_serverRoleAccessor.CurrentServerRole)
332332
return Task.CompletedTask; // We return Task.CompletedTask to try again as the server role may change!
333333
}
334334
```
335+
336+
## Background jobs when load balancing the backoffice
337+
338+
When load balancing the backoffice, all servers will have the `SchedulingPublisher` role. This means the approach described above for restricting jobs to specific server roles will not work as intended. All servers will match the `SchedulingPublisher` role.
339+
340+
Instead, for jobs that should only run on a single server, you should implement an `IDistributedBackgroundJob`.
341+
342+
`IDistributedBackgroundJob` is separate from `IRecurringBackgroundJob`, and is tracked in the database to ensure that only a single server runs the job at any given time.
343+
This also means that you are not guaranteed what server will run the job, but you are guaranteed that only one server will run it.
344+
345+
By default, distributed background jobs are checked every 5 seconds, with an initial delay of 1 minute after application startup. These settings can be changed in appsettings, see [Distributed jobs settings](./configuration/distributedjobssettings.md) for more information.
346+
347+
### Implementing a custom distributed background job
348+
349+
To implement a custom distributed background job, create a class that implements the `IDistributedBackgroundJob` interface. As with `IRecurringBackgroundJob`, dependency injection (DI) is available in the constructor.
350+
351+
```csharp
352+
public class MyCustomBackgroundJob : IDistributedBackgroundJob
353+
{
354+
private readonly ILogger<MyCustomBackgroundJob> _logger;
355+
public string Name => "MyCustomBackgroundJob";
356+
357+
public TimeSpan Period { get; private set; }
358+
359+
public MyCustomBackgroundJob(ILogger<MyCustomBackgroundJob> logger)
360+
{
361+
_logger = logger;
362+
Period = TimeSpan.FromSeconds(20);
363+
}
364+
365+
public Task ExecuteAsync()
366+
{
367+
// Your custom background job logic here
368+
_logger.LogInformation("MyCustomBackgroundJob is executing.");
369+
return Task.CompletedTask;
370+
}
371+
}
372+
```
373+
374+
It's required to give your job a unique name via the `Name` property. This is used to track the job in the database.
375+
376+
The period is specified via the `Period` property, which controls how often the job should run. In this example, it runs every 20 seconds.
377+
378+
It's not required to manually register the job in the database, however, you must register it to DI so Umbraco can find it. This can be done with a composer or in `Program.cs`
379+
380+
```csharp
381+
public class MyComposer : IComposer
382+
{
383+
public void Compose(IUmbracoBuilder builder)
384+
{
385+
builder.Services.AddSingleton<IDistributedBackgroundJob, MyCustomBackgroundJob>();
386+
}
387+
}
388+
```

0 commit comments

Comments
 (0)