# The problem
Many teams discover keys using Redis's KEYS command or SCAN. On large datasets, KEYS can block the Redis thread for seconds. Redis is single-threaded for command execution, so a blocking search stalls every other client, including simple GETs.
# How the read path builds a key
service::tenant::category::entityId::params
Where params are canonical JSON (recursively sorted keys) of the method's tail arguments. The first argument must be a string entityId and validated as a key segment. Because canonicalJson normalizes parameter order, requests with the same logical params map to the same key.
The important point: the read path has the full arguments and constructs the exact cache key strings. The delete/invalidate path normally only has an entity id and cannot reconstitute the many different keys the read path produced.
# The missing index and how it helps Instead of scanning the entire keyspace, the author introduces a per-entity index: a small Redis structure that maps an entityId to the list (or set) of full cache key strings that contain that entityId.
- On a cache write (when a read path populates the cache after a miss), also add the generated full key to the entity's index entry.
- On invalidation for an entity, fetch the index entry and run targeted DEL commands for the listed keys.
- Optionally remove the index entry or prune it as keys expire.
This replaces a KEYS or pattern scan with a single index lookup plus a short batch of deletes. The index lookup is cheap and non-blocking compared with scanning millions of keys.
- buildCacheKey enforces the contract: first argument is entityId (string), remaining args become canonical JSON params.
- Index entries must be kept consistent with cache writes and deletes: add on write, remove on explicit delete, allow TTLs or background compaction for stale entries.
# Pitfalls and related anti-patterns
- Do not treat origin failures as valid cache values. The article shows an anti-pattern where a caught error returns undefined and that undefined is stored as a negative cache result. That turns transient failures into incorrect cached state.
- Index maintenance is extra work: you must handle race conditions, expirations, and potential index growth. The demo code and scripts show one approach but evaluate it against your workload.
# Where to try this The author includes an accompanying demo repository with the implementation, benchmark scripts, and recorded results. Run it locally to repeat experiments and test different workloads.