Skip to content

Allow InMemoryRedis (L1+L2) without broadcast, for backends without full Redis Streams read support #103

Description

@andreialionte

Problem

AddInMemoryRedis() unconditionally wires up cross-node L1 invalidation via Redis Streams. There is currently no documented way to use the InMemoryRedis provider's L1+L2 tiering without this broadcast mechanism. Against Redis-compatible backends whose Streams support doesn't (yet) cover the consumer-group read side such as Microsoft Garnet, a RESP-compatible cache-store from Microsoft Research, not a fork of Redis this produces a continuous error loop as soon as the provider is used.

Environment

  • Cache store: Microsoft Garnet (RESP-compatible)
  • Provider: InMemoryRedis

Steps to reproduce

builder.Services.AddCaching(builder.Configuration.GetSection("Caching"), cachingBuilder =>
    cachingBuilder
        .AddRedisConnection(connectionOptions =>
        {
            connectionOptions.ConnectionString = redisConnectionString; // pointed at Garnet
            connectionOptions.AbortOnConnectFail = false;
            connectionOptions.WarmUpOnStart = false;
        })
        .AddRedis()
        .AddInMemoryRedis()
        .AddMemory()
        .AddLocalLock()
        .AddRedisDistributedLock()
        .AddResilienceStrategies()
        ,
    options => { /* ... */ });
_cache = cacheFactory.CreateCache("InMemoryRedis");

Observed behavior

Once "InMemoryRedis" is requested via CreateCache, the following error repeats continuously (roughly every 250ms):

fail: UiPath.Caching.Broadcast.Redis.RedisStreamsTopic
Fetch events loop error
StackExchange.Redis.RedisServerException: ERR unknown command
at UiPath.Caching.Broadcast.Redis.RedisStreamSubjectWriter1.FetchBatch() at UiPath.Caching.Broadcast.Redis.RedisStreamSubjectWriter1.FetchLoop()

Commenting out .AddBroadcast() in the builder chain has no effect on this behavior. Per concepts.md, AddInMemoryRedis() wires broadcast internally regardless of whether .AddBroadcast() is called this isn't evident from the builder API itself, and the presence of .AddBroadcast() as a separate chainable method reasonably suggests it controls broadcast for all providers, including InMemoryRedis.

Impact

There is currently no way to use L1+L2 tiering against a backend without full Streams read support. The only available workaround is to request the plain Redis provider instead, which forgoes the in-process L1 tier entirely a real cost even for single-node deployments, where cross-node invalidation provides no benefit in the first place.

Separately, on a Dragonfly backend, we hit a related-but-distinct issue: StackExchange.Redis's automatic CLUSTER topology probe (issued against any server on connect) returned an announced endpoint different from our configured one, causing an unrelated reconnect loop. Not a UiPath.Caching issue, but noted here as another example of assumptions that don't hold uniformly across Redis-compatible backends.

Garnet's Streams support (for context)

Worth noting: Garnet does have some Streams support now (writes/ranges), but it doesn't yet support the read/consumer-group commands (XREAD/XREADGROUP) that UiPath.Caching's broadcast loop actually depends on so this isn't a bug on Garnet's side either, just a gap between what the two projects currently support. (Tracked upstream at microsoft/garnet#1379, if useful.)

Proposed solution

  1. An option to construct InMemoryRedis with broadcast disabled, e.g.:
.AddInMemoryRedis(options => options.BroadcastEnable = false)

so L1+L2 tiering can be used independent of the backend's Streams read support, and independent of whether cross-node invalidation is even needed (e.g. single-node deployments).

  1. Documentation clarifying that .AddBroadcast() does not affect InMemoryRedis behavior, so others don't spend time on the same assumption.

Alternatives considered

  • Plain Redis provider (current workaround) avoids the error, but forgoes in-process (L1) caching entirely, even in cases (single-node, or backends where Streams reads simply aren't available yet) where L1 would otherwise be safe and beneficial to use.
  • Waiting on upstream Garnet Streams support doesn't help today, and doesn't address the single-node case where broadcast isn't needed regardless of backend capability.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions