Skip to content

Scaling out with Redis

Redis is optional. A service that has a redis connection string shares its state with the other instances through it; without one, everything stays in the process and works the same for a single instance.

What With Redis Without
SignalR backplane: an event reaches the connections of every instance the connections of this instance
Presence a user counts once, whichever instances hold connections per instance
Permission, setting, feature and text caches a change drops the entries on every instance at once other instances notice within the cache lifetime
Session stamps and session changes shared stamp cache, pub/sub to every instance (Clients and sessions) own instance, the others within a minute
Topics after permission changes every instance checks its connections again (Realtime) own instance
Sign-in sessions of the web app session store and the key ring of the cookie in Redis: shared by the instances, they survive restarts memory and the local key ring
Distributed lock Coworkee:DistributedLock:Provider = Redis (Events and locks) Postgres advisory locks

Aspire

The app host gives every service that references Coworkee.Realtime (directly or through Coworkee.Identity) a Redis container, and the web app as well. Use your own Redis, or none:

builder.AddCoworkeeApp("shop", options =>
{
    // production: an existing Redis
    options.UseRedis(builder.AddConnectionString("redis"));

    // or: one instance per service, no container
    options.WithoutRedis();
});

coworkee new --redis false generates the app host with WithoutRedis(). With Redis (the default), the generated service defaults also call AddRedisClient("redis") from Aspire.StackExchange.Redis: a health check and traces for the connection, which the Coworkee services reuse.

How it works

  • Caches. HybridCache keeps the tagged caches (permissions, settings, features, texts, cached queries) in each instance, because a removal by tag does not reach a second level other instances read. Every removal by key or tag also goes out on the channel coworkee:cache-invalidation; the other instances drop the same entries from their local cache.
  • Presence. Each instance writes the users it holds connections for into its own hash coworkee:presence:<instance>, which expires after 30 seconds unless the instance writes it again (every 10 seconds). A user who connects is announced only when no other instance has them online; a user who leaves is announced offline only when no other instance still holds a connection. The users of a crashed instance drop out with its hash.
  • Web app. The data protection keys of the session cookie are kept under coworkee:bff:data-protection, so any instance reads a cookie another one wrote.

A Redis that does not answer is not fatal: the services log a warning and fall back to what they know locally.

Limits

The presence query reads the hash of every instance; that is fine for a handful of instances. Users of a crashed instance turn offline in the presence list, but no offline event is sent for them.