feat: write cache misses with a single setMany - #21
Merged
Conversation
🦋 Changeset detectedLatest commit: 97f2c75 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
toCache issued a set per cache key and never collected the promises. The individual writes were already pipelined by the store's client, so this is not a round-trip win -- the same commands and bytes go over the socket either way -- but the batch is now one call into the store, which the redis adapter wraps in a single MULTI, and instrumentation reports one batch instead of one command per key. A category page loading 36 products emitted 36 redis-SET spans per request. The writes are also awaited, so a failing store surfaces as an error event on it rather than as a floating rejection. setMany needs keyv 5.3.4 or newer. Before that, keyv hands unserialized entries straight to the store adapter, without the value wrapper or the key prefix that get applies on read, so the writes land as values get cannot read back.
mvantellingen
force-pushed
the
feat/batch-cache-writes
branch
from
August 19, 2026 17:34
82ce2af to
c5857c0
Compare
The tsdown build failed on the 20.x job. tsdown only uses its native config loader when `import-without-cache` is supported (node ^22.18.0 || >=24.0.0); below that it falls back to the `unrun` loader, which is an optional peer dependency and not installed here. Node 20 is end-of-life anyway. engines.node moves from >=20 to >=22 to match.
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.
toCacheissued asetper cache key and never collected the promises:Found while looking at a category page trace in a storefront that uses this
through
@evolve-framework/commercetools' dataloaders: 35redis-SETspans inone request, against 6
redis-MGET— reads batch throughstore.get(keys),writes did not. Over two hours
redis-SETwas the single most-emitted span inthat service (1021 spans, P50 99ms, P95 496ms), which drowned out everything else
in the trace.
What this is not
Not a round-trip win. Measured against a real Redis by patching
net.Socket.prototype.write, 35 keys of ~2KB, three runs:for (…) store.set(), not awaited (before)for (…) await store.set()(hypothetical)store.setMany()(this PR)MULTI+ 35×SET)The old writes went out within 0.2 ms without waiting for replies, so the client
already pipelined them. Same command count, same bytes (76690 vs 76675).
What it is
@keyv/redis.setManybuilds asingle
MULTI/EXEC, so tracing reports one batch span instead of 35.errorevent onit rather than as a floating rejection.
The keyv floor
setManyneeds keyv >= 5.3.4; the peer range moves from>=4.0.0 <6.0.0.Before 5.3.4
Keyv.setManyhands entries straight to the store adapter with no{value, expires}wrapper, no serialization and no key prefix — so it writesvalues
getcannot read back, silently. Verified per version:Verification
pnpm test(8 passed) andpnpm checkgreen, plus against a real Redis withMONITORattached:DataLoaderCacheproduce oneMULTI… 8×SET…EXECso
setManywrites whatgetparsessetandsetManyland on byte-identical keys, both with a TTL — otherwise arelease would silently invalidate every cached entry