Skip to content

Sharing Levels cache

Sharing-level resolution is used by many queries, mutations, filters, and federated service calls. The cache design reduces repeated sharing-level database reads while keeping behavior correct across multiple service replicas.

Cache layers

Sharing Levels uses two cache layers with different responsibilities:

  • SharingLevelsDistributedCacheStore caches raw store data in the distributed cache through ISuiteHybridCache. It disables the hybrid local-memory layer with UseLocalCache = false, so the store cache is shared across replicas without keeping another copy of the raw collections in every process.
  • MemoizationSharingLevelsResolver uses IMemoryCache for derived resolver calculations. This is per-process memoization for values such as parent chains, branches, lookup levels, destination levels, parent ids, and the list of sharing-level ids.

ASP.NET HybridCache can provide local-memory caching, but Sharing Levels does not use that local layer in the store because the resolver already owns the feature-specific memoization. This keeps the distributed raw-data cache and the in-memory derived-result cache from duplicating the same responsibility.

Cached data

The store-level cache contains raw data loaded from the Sharing Levels database:

Data Source method Empty collection behavior
Sharing levels GetSharingLevels Not cached, because an empty result can mean the store is not ready yet.
Entity sharings GetEntitySharings Not cached, because an empty result can mean the store is not ready yet.
Sharings GetSharings Cached, because an empty sharings set can be a valid result.
Sharing-level ready marker IsSharingLevelsReady Written after sharing levels are loaded with values.
Entity-sharing ready marker IsEntitySharingsReady Written after entity sharings are loaded with values.

The resolver-level memoization contains derived data only. It is cleared by moving to a new memoization generation, so old in-memory entries become unreachable without scanning and removing each individual key.

Invalidation

ISharingLevelsResolver.ClearAsync invalidates both layers:

  1. The resolver changes its memoization generation.
  2. Each ISharingLevelsStoreCacheInvalidator clears its store-level entries.
  3. SharingLevelsDistributedCacheStore writes a short-lived distributed write-suspension marker.
  4. The store removes raw collection keys and readiness markers from the distributed cache.

While the write-suspension marker exists, replicas still read from the database when needed, but they do not repopulate the distributed cache. This avoids a race where one replica clears the cache and another replica immediately writes old data back while invalidation is still settling.

Options

SharingLevelsStoreModuleOptions controls the store-level distributed cache:

C#
1
2
3
4
5
6
builder.DependsOn<SharingLevelsStoreModule, SharingLevelsStoreModuleOptions>(
    options =>
    {
        options.DistributedCacheExpirationInSeconds = 600;
        options.DistributedCacheWriteSuspensionInSeconds = 5;
    });
  • DistributedCacheExpirationInSeconds sets the store cache entry duration. The default is 600 seconds. A value of 0 or less omits the per-entry expiration override.
  • DistributedCacheWriteSuspensionInSeconds sets how long distributed cache writes are suspended after invalidation. The default is 5 seconds. A value of 0 or less disables write suspension.

SharingLevelsClientModuleOptions.MemorizationTimeInSeconds controls the resolver IMemoryCache expiration for derived calculations.

Measuring impact

Use the Suite CLI manual performance command to compare the current branch with a baseline branch or commit:

Bash
suite perf run sharing-levels --iterations 100 --compare main

The report includes elapsed time, database command counters, cache counters, and memory measurements. See the Suite CLI documentation for report paths and command options.