#45 Add purge-by-prefix cache maintenance helper - #60
Merged
Merged
Conversation
Add INatsCacheMaintenance.PurgeByPrefixAsync so multi-tenant users can evict
every entry beneath a key prefix. It composes the caller's sub-prefix with the
configured CacheKeyPrefix exactly as GetEncodedKey composes a full key, encodes
it, and enumerates matching keys with a NATS subject filter ({encodedPrefix}.>)
via GetKeysAsync, then PurgeAsync each. The encoder leaves the '.' separator
unescaped and escapes NATS wildcards, so the encoded prefix stays a leading
segment of every encoded full key beneath it.
Implement the interface on NatsCache and forward both IDistributedCache and
INatsCacheMaintenance to the one NatsCache singleton in AddNatsDistributedCache,
so a purge and a cache read share the same KV store, prefix, and key encoder.
Reject an empty/whitespace/dot-only prefix so a scoped purge cannot collapse
into a full-bucket purge; snapshot the matching keys before purging to keep
enumeration and mutation separated.
Add unit tests (guard + DI single-instance) and integration tests (prefix
isolation, count, idempotency, and keys that require encoding), plus a README
section on multi-tenant prefix purging.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthew DeVenny <matt@codecargo.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a maintenance surface for bulk cache eviction by key prefix to support multi-tenant scenarios where a shared KV bucket is partitioned via CacheKeyPrefix.
Changes:
- Introduces
INatsCacheMaintenanceand implementsPurgeByPrefixAsynconNatsCache. - Updates
AddNatsDistributedCacheto register a singleNatsCachesingleton and forward bothIDistributedCacheandINatsCacheMaintenanceto the same instance. - Adds unit + integration coverage for prefix purging and DI behavior, plus README documentation for multi-tenant prefix purging.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/UnitTests/Extensions/NatsDistributedCacheExtensionsTests.cs | Adds DI registration tests for INatsCacheMaintenance and verifies it resolves to the same singleton as IDistributedCache. |
| test/UnitTests/Cache/PurgeByPrefixUnitTests.cs | Adds guard/validation unit tests for invalid prefixes. |
| test/IntegrationTests/Cache/PurgeByPrefixTests.cs | Adds integration tests validating purge isolation, counts, idempotency, and encoding edge cases. |
| src/NatsDistributedCache/NatsDistributedCacheExtensions.cs | Registers NatsCache once and forwards both public service surfaces to that singleton. |
| src/NatsDistributedCache/NatsCache.Maintenance.cs | Implements the purge-by-prefix logic using encoded-prefix subject filtering and snapshots keys before purging. |
| src/NatsDistributedCache/INatsCacheMaintenance.cs | Defines the new maintenance interface contract and docs. |
| README.md | Documents multi-tenant key prefixing and how to use PurgeByPrefixAsync. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The `using NATS.Client.KeyValueStore;` directive is unused in this partial file (the KV store is reached through GetKvStore()/var, with no KV type named here). Flagged in PR review; removed for cleanliness. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Matthew DeVenny <matt@codecargo.com>
mtmk
reviewed
Jul 29, 2026
Address review: replace the per-key GetKeysAsync + PurgeAsync loop with one subject-filtered JetStream stream purge of the bucket's backing KV_<bucket> stream. That is a single server round-trip instead of N and removes the messages outright rather than leaving purge-marker tombstones that linger until a PurgeDeletes() compaction. The prefix composition, encoding, and empty/dot-only guard are unchanged; the returned count is now response.Purged (one message per live key under the bucket's History = 1). Documents the added stream-purge permission requirement (on the KV_<bucket> stream) in the interface XML docs and the README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Matthew DeVenny <matt@codecargo.com>
The count returned is the number of stream messages purged, not strictly the number of live keys. For the cache's History = 1 buckets that equals the live entries removed, but a stream purge also counts any not-yet-compacted delete markers left by earlier evictions. Document it as an approximate count in the interface XML docs and the README so callers do not treat it as an exact live-entry total. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Matthew DeVenny <matt@codecargo.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #45 (clear-by-prefix / purge maintenance helper). One of three independent PRs splitting that issue.
What
Add
INatsCacheMaintenance.PurgeByPrefixAsyncso multi-tenant users (who partition a shared bucket withCacheKeyPrefix) can evict every entry beneath a key prefix.INatsCacheMaintenanceinterface, implemented onNatsCache.AddNatsDistributedCachenow registers the concreteNatsCacheonce and forwards bothIDistributedCacheandINatsCacheMaintenanceto that single instance, so a purge and a cache read share the same KV store, key prefix, and key encoder.How it works
Composes the caller's sub-prefix with the configured
CacheKeyPrefixexactly asGetEncodedKeycomposes a full key, encodes it, and enumerates matching keys with a NATS subject filter{encodedPrefix}.>viaGetKeysAsync, thenPurgeAsynceach. The key encoder leaves the.separator unescaped (RFC 3986 unreserved) and escapes NATS wildcards, so the encoded prefix stays a byte-for-byte leading segment of every encoded full key beneath it, and subject-wildcard filtering isolates on token boundaries.GetKeysAsync).Testing
dotnet build -p TreatWarningsAsErrors=true: clean, net8.0 + net10.0.tenant#1vs atenant#2control) — confirming the subject-filter approach against real NATS.🤖 Generated with Claude Code