Summary
linkCache.delete() (and deleteMany/expireMany) only remove the link from Redis and the Vercel runtime cache — they never purge the in-process LRUCache in apps/web/lib/api/links/cache.ts. Since linkCache.get() serves the LRU first (10k entries, 5s TTL), a link that was deleted, or whose key/domain was changed, continues to resolve to its old destination for up to 5 seconds per server instance.
Files involved
- apps/web/lib/api/links/cache.ts — get() (LRU-first) and delete()/deleteMany()/expireMany() (Redis/Vercel only)
- apps/web/lib/api/links/update-link.ts:203 — linkCache.delete(oldLink) on key/domain change
- apps/web/lib/api/links/delete-link.ts:59 — linkCache.delete(link) on delete
// cache.ts — get() checks LRU first, delete() never clears it
async get({ domain, key }) {
let cachedLink = linkLRUCache.get(cacheKey) || null; // <-- stale hit possible
...
}
async delete({ domain, key }) {
const cacheKey = this._createKey({ domain, key });
waitUntil(this._invalidateVercelRuntimeCache(cacheKey));
return await redisGlobal.del(cacheKey); // <-- LRU untouched
}
Expected behavior
Once a link is deleted or its key is changed, the old short link stops resolving immediately.
Actual behavior
The old short link keeps redirecting to the old destination for up to ~5 seconds (the LRU TTL), because linkCache.delete() doesn't invalidate the in-process linkLRUCache.
Impact
Users see deleted links keep working and old keys still redirect after an edit. Every delete/key-edit on a hot link hits this window; the delete+recreate variant can transiently break the new link.
Suggested direction
Have delete()/deleteMany()/expireMany() also linkLRUCache.delete(cacheKey) (same as set() does on write), or version the LRU entries so post-mutation reads bypass them.
Environment
- dub monorepo @ main (bac6b33)
- Reproduced against local dev setup (pnpm dev, Next.js 15.5.8)
Summary
linkCache.delete() (and deleteMany/expireMany) only remove the link from Redis and the Vercel runtime cache — they never purge the in-process LRUCache in apps/web/lib/api/links/cache.ts. Since linkCache.get() serves the LRU first (10k entries, 5s TTL), a link that was deleted, or whose key/domain was changed, continues to resolve to its old destination for up to 5 seconds per server instance.
Files involved
// cache.ts — get() checks LRU first, delete() never clears it
Expected behavior
Once a link is deleted or its key is changed, the old short link stops resolving immediately.
Actual behavior
The old short link keeps redirecting to the old destination for up to ~5 seconds (the LRU TTL), because linkCache.delete() doesn't invalidate the in-process linkLRUCache.
Impact
Users see deleted links keep working and old keys still redirect after an edit. Every delete/key-edit on a hot link hits this window; the delete+recreate variant can transiently break the new link.
Suggested direction
Have delete()/deleteMany()/expireMany() also linkLRUCache.delete(cacheKey) (same as set() does on write), or version the LRU entries so post-mutation reads bypass them.
Environment