Skip to content

cache-manager - fix: use store.deleteMany in mdel instead of per-key delete loop - #1697

Open
pavelmaksimov25 wants to merge 1 commit into
jaredwray:mainfrom
pavelmaksimov25:mdel-delete-many
Open

cache-manager - fix: use store.deleteMany in mdel instead of per-key delete loop#1697
pavelmaksimov25 wants to merge 1 commit into
jaredwray:mainfrom
pavelmaksimov25:mdel-delete-many

Conversation

@pavelmaksimov25

Copy link
Copy Markdown

Closes #1696

Please check if the PR fulfills these requirements

  • Followed the Contributing guidelines and Code of Conduct
  • Tests for the changes have been added (for bug fixes/features) with 100% code coverage.

What kind of change does this PR introduce?

Bug fix / performance — aligns mdel with the bulk pattern mget and mset already use.

Problem

Since #1014, mget and mset delegate to the Keyv store's getMany/setMany, so bulk-capable adapters (e.g. @keyv/redis) issue a single native command. mdel was left out: it looped the key list calling store.delete(key) per key, costing one round-trip per key.

const promises: Array<Promise<boolean>> = [];
for (const key of keys) {
	promises.push(...stores.map(async (store) => store.delete(key)));
}

For N keys across M stores that is N × M round-trips.

Change

Mirrors the mset implementation exactly:

const promises = stores.map(async (store) => store.deleteMany(keys));

M round-trips regardless of key count. Keyv's deleteMany falls back internally for adapters without native multi-delete support, so no fallback logic is needed in cache-manager.

Unchanged: nonBlocking behavior, the mdel event payload, the Promise<true> return value, and error handling.

Tests

test/mdel.test.ts:

  • new — every key deleted with a single store call, asserting deleteMany receives the whole key array
  • new — one deleteMany per store in a two-store cache
  • new — empty key list still resolves true
  • updated — the existing blocking / non-blocking tests now spy on deleteMany and assert toHaveBeenCalledOnce(), matching the shape of test/mset.test.ts

pnpm test in packages/cache-manager: 17 files, 89 tests passing, coverage 100% statements / 100% branches / 100% functions / 100% lines. Lint and build clean.

Docs

Added a line to the mdel section of packages/cache-manager/README.md noting that keys are deleted with a single bulk operation per store.

Note on scope

#1014 also mentions hasMany. cache-manager currently exposes no mhas, so adding one is a new feature rather than an alignment — left out of this PR.

@pavelmaksimov25
pavelmaksimov25 marked this pull request as ready for review August 20, 2026 19:49
…delete loop

mget and mset already delegate to the Keyv store's getMany/setMany, so
bulk-capable adapters issue a single native command. mdel still looped the
key list calling store.delete(key) per key, costing one round-trip per key.

Align it with the mset pattern: stores.map((store) => store.deleteMany(keys)).
Keyv's deleteMany falls back internally for adapters without native support,
so no fallback logic is needed here. nonBlocking behavior, the mdel event
payload, and the return value are unchanged.

Closes jaredwray#1696

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9a7c30c045

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

for (const key of keys) {
promises.push(...stores.map(async (store) => store.delete(key)));
}
const promises = stores.map(async (store) => store.deleteMany(keys));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve empty-list behavior before bulk deletion

When keys is empty and the cache uses the documented @keyv/redis store, this now invokes its native deleteMany([]), which delegates to Redis DEL without any key arguments and is rejected by Redis. Previously the empty loop produced Promise.all([]) and resolved true; the new empty-list test only exercises Keyv's in-memory fallback, so it does not cover this adapter-specific regression. Return early for an empty array before dispatching to stores.

Useful? React with 👍 / 👎.

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.

Use store.deleteMany instead of per-key delete loop in mdel

1 participant