Skip to content

#45 Add purge-by-prefix cache maintenance helper - #60

Merged
matthewdevenny merged 4 commits into
mainfrom
matt/45-purge-prefix
Jul 30, 2026
Merged

matthewdevenny merged 4 commits into
mainfrom
matt/45-purge-prefix

Conversation

@matthewdevenny

Copy link
Copy Markdown
Contributor

Part of #45 (clear-by-prefix / purge maintenance helper). One of three independent PRs splitting that issue.

What

Add INatsCacheMaintenance.PurgeByPrefixAsync so multi-tenant users (who partition a shared bucket with CacheKeyPrefix) can evict every entry beneath a key prefix.

  • New INatsCacheMaintenance interface, implemented on NatsCache.
  • AddNatsDistributedCache now registers the concrete NatsCache once and forwards both IDistributedCache and INatsCacheMaintenance to that single instance, so a purge and a cache read share the same KV store, key prefix, and key encoder.
  • README section on multi-tenant prefix purging.

How it works

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 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.

  • Rejects an empty/whitespace/dot-only prefix so a scoped purge cannot collapse into a full-bucket purge.
  • Snapshots the matching keys before purging (keeps enumeration and mutation separated from the live watch backing GetKeysAsync).

Testing

  • dotnet build -p TreatWarningsAsErrors=true: clean, net8.0 + net10.0.
  • Unit tests: 117/117 (guard + DI single-instance). Integration tests: 80/80, including prefix isolation, purged count, idempotency, and keys that require encoding (tenant#1 vs a tenant#2 control) — confirming the subject-filter approach against real NATS.

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings July 28, 2026 15:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 INatsCacheMaintenance and implements PurgeByPrefixAsync on NatsCache.
  • Updates AddNatsDistributedCache to register a single NatsCache singleton and forward both IDistributedCache and INatsCacheMaintenance to 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.

Comment thread src/NatsDistributedCache/NatsCache.Maintenance.cs Outdated
@matthewdevenny
matthewdevenny requested a review from mtmk July 28, 2026 19:42
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>
Comment thread src/NatsDistributedCache/NatsCache.Maintenance.cs
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>

@mtmk mtmk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

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>
@matthewdevenny
matthewdevenny merged commit 5ada818 into main Jul 30, 2026
2 checks passed
@matthewdevenny
matthewdevenny deleted the matt/45-purge-prefix branch July 30, 2026 00:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants