Claude Code के साथ Redis caching: TTL, invalidation और stampede रोकथाम
Claude Code से Redis cache डिजाइन करने की व्यावहारिक गाइड: key design, TTL, invalidation, Node.js code, tests और review checklist.
Redis एक तेज in-memory key-value store है: डेटा RAM में रहता है और key से तुरंत पढ़ा जाता है। यह database aggregate, external API response, ranking, short session और rate limit के लिए उपयोगी है। लेकिन अगर आप Claude Code से सिर्फ “Redis cache जोड़ दो” कहेंगे, तो stale price, गलत inventory, private user data leak या production Redis पर भारी key scan जैसी समस्या आ सकती है।
यह गाइड Redis caching को Claude Code के लिए स्पष्ट workflow में बदलती है: cache policy, key design, TTL, invalidation, cache stampede रोकथाम, Node.js implementation, tests और review checklist। व्यापक cache strategy के लिए Claude Code caching strategies पढ़ें। अगर Redis queue में भी उपयोग हो रहा है, तो Claude Code queue system भी उपयोगी है।
flowchart LR
Request["HTTP request"] --> Cache["Redis cache"]
Cache -->|hit| Response["Fast response"]
Cache -->|miss| Lock["Short lock"]
Lock --> Loader["DB or external API"]
Loader --> Cache
Loader --> Response
Admin["Update event"] --> Invalidate["Delete known keys"]
Invalidate --> Cache
पहले cache policy लिखें
Implementation से पहले CLAUDE.md या prompt में policy रखें। Claude Code code flow पढ़ सकता है, पर business freshness अपने आप नहीं समझ सकता।
| Data | Example key | TTL | Invalidation | Risk |
|---|---|---|---|---|
| Public product list | claudecodelab:v1:products:list:hi | 5 min | Product update के बाद list key delete | Price change केवल TTL पर न छोड़ें |
| Article detail | claudecodelab:v1:posts:item:{slug} | 10 min | Publish, edit, unpublish के बाद | Draft और preview cache न करें |
| Admin stats | claudecodelab:v1:analytics:daily:{date} | 30 sec | अक्सर TTL काफी | Accounting truth नहीं |
| External API | claudecodelab:v1:exchange-rate:usd-inr | 1-15 min | TTL या manual refresh | Provider terms जांचें |
| Logged-in user data | Default exclude | 0 sec | None | Shared cache में personal data नहीं |
सरल नियम: public और regenerate हो सकने वाला data Redis में ठीक है। Permission, billing, authentication और personal data को अलग security design के बिना cache न करें।
Claude Code prompt
इस Node.js app में Redis cache-aside layer जोड़ें।
Requirements:
1. Official node-redis package redis का उपयोग करें
2. Keys claudecodelab:v1:{domain}:{resource}:{id} format में बनाएं
3. TTL cache policy से चुनें और 10% तक jitter जोड़ें
4. Update में DB write success के बाद ही known related keys delete करें
5. Production code में KEYS न इस्तेमाल करें; known keys, SCAN या related-key sets उपयोग करें
6. Popular keys के concurrent miss के लिए short lock लगाएं
7. node:test से key generation, TTL range, getOrSet और concurrent miss test करें
Return:
- Changed files
- Tests run
- Cache से बाहर रखे गए data और कारण
इसे Claude Code code review checklist में जोड़ें ताकि review में speed के साथ invalidation भी जांचा जाए।
Node.js implementation
Official Node.js client redis package है। Connection pattern node-redis guide में है।
mkdir redis-cache-demo
cd redis-cache-demo
npm init -y
npm install redis
docker run --name redis-cache-demo -p 6379:6379 -d redis:7-alpine
Key और TTL policy को एक जगह रखें।
// cache-policy.js
const CACHE_PREFIX = "claudecodelab";
const CACHE_VERSION = "v1";
const CACHE_POLICY = {
productList: { ttl: 300, jitter: 30 },
productItem: { ttl: 600, jitter: 60 },
dailyStats: { ttl: 30, jitter: 5 },
};
function normalizePart(value) {
const part = String(value).trim().toLowerCase();
if (part.length === 0) {
throw new Error("cache key part must not be empty");
}
return encodeURIComponent(part);
}
function cacheKey(parts) {
if (!Array.isArray(parts) || parts.length === 0) {
throw new Error("cacheKey requires a non-empty parts array");
}
return [CACHE_PREFIX, CACHE_VERSION, ...parts.map(normalizePart)].join(":");
}
function ttlWithJitter(baseSeconds, maxJitterSeconds = 30) {
if (!Number.isInteger(baseSeconds) || baseSeconds <= 0) {
throw new Error("base TTL must be a positive integer");
}
const jitter = Math.max(0, Math.floor(maxJitterSeconds));
return baseSeconds + Math.floor(Math.random() * (jitter + 1));
}
module.exports = { CACHE_POLICY, cacheKey, ttlWithJitter };
Redis client को share करें।
// redis-client.js
const { createClient } = require("redis");
const redis = createClient({
url: process.env.REDIS_URL || "redis://localhost:6379",
});
redis.on("error", (error) => {
console.error("Redis Client Error", error);
});
let connecting;
async function getRedis() {
if (redis.isOpen) return redis;
if (!connecting) {
connecting = redis.connect();
}
await connecting;
return redis;
}
async function closeRedis() {
if (redis.isOpen) {
await redis.quit();
}
connecting = undefined;
}
module.exports = { getRedis, closeRedis };
Helper cache-aside pattern लागू करता है। Redis miss पर loader चलता है, और popular key expire होने पर DB पर भीड़ न लगे इसलिए SET NX PX short lock उपयोग होता है।
// redis-cache.js
const { randomUUID } = require("node:crypto");
const UNLOCK_SCRIPT = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
end
return 0
`;
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
class RedisJsonCache {
constructor(redis, options = {}) {
this.redis = redis;
this.defaultTtl = options.defaultTtl || 300;
this.lockMs = options.lockMs || 5000;
this.waitMs = options.waitMs || 50;
this.waitRetries = options.waitRetries || 10;
}
async get(key) {
const raw = await this.redis.get(key);
if (raw === null) return null;
try {
return JSON.parse(raw);
} catch {
await this.redis.del(key);
return null;
}
}
async set(key, value, ttlSeconds = this.defaultTtl) {
if (!Number.isInteger(ttlSeconds) || ttlSeconds <= 0) {
throw new Error("ttlSeconds must be a positive integer");
}
await this.redis.set(key, JSON.stringify(value), { EX: ttlSeconds });
}
async invalidate(keys) {
const list = Array.isArray(keys) ? keys : [keys];
if (list.length === 0) return 0;
return this.redis.del(list);
}
async getOrSet(key, ttlSeconds, loader) {
const cached = await this.get(key);
if (cached !== null) {
return { value: cached, cacheStatus: "hit" };
}
const lockKey = `${key}:lock`;
const token = randomUUID();
const acquired = await this.redis.set(lockKey, token, { NX: true, PX: this.lockMs });
if (acquired === "OK") {
try {
const fresh = await loader();
await this.set(key, fresh, ttlSeconds);
return { value: fresh, cacheStatus: "miss" };
} finally {
await this.redis.eval(UNLOCK_SCRIPT, { keys: [lockKey], arguments: [token] });
}
}
for (let attempt = 0; attempt < this.waitRetries; attempt += 1) {
await sleep(this.waitMs);
const afterWait = await this.get(key);
if (afterWait !== null) {
return { value: afterWait, cacheStatus: "hit-after-wait" };
}
}
const fallback = await loader();
await this.set(key, fallback, Math.max(5, Math.floor(ttlSeconds / 3)));
return { value: fallback, cacheStatus: "miss-after-timeout" };
}
}
module.exports = { RedisJsonCache };
यह demo runnable है। Real app में loadProductsFromDb() को Prisma, Supabase या API client से बदलें। संबंधित लेख: Prisma ORM with Claude Code और Supabase integration with Claude Code।
// demo-products.js
const { CACHE_POLICY, cacheKey, ttlWithJitter } = require("./cache-policy");
const { getRedis, closeRedis } = require("./redis-client");
const { RedisJsonCache } = require("./redis-cache");
const db = {
products: [
{ id: "p1", locale: "hi", name: "CLAUDE.md Template", price: 9, published: true },
{ id: "p2", locale: "hi", name: "Claude Code Training", price: 199, published: true },
],
};
async function loadProductsFromDb(locale) {
await new Promise((resolve) => setTimeout(resolve, 80));
return db.products.filter((product) => product.locale === locale && product.published);
}
async function listPublishedProducts(cache, locale) {
const key = cacheKey(["products", "list", locale]);
const ttl = ttlWithJitter(CACHE_POLICY.productList.ttl, CACHE_POLICY.productList.jitter);
return cache.getOrSet(key, ttl, () => loadProductsFromDb(locale));
}
async function main() {
const redis = await getRedis();
const cache = new RedisJsonCache(redis);
const first = await listPublishedProducts(cache, "hi");
const second = await listPublishedProducts(cache, "hi");
console.log({ firstStatus: first.cacheStatus, secondStatus: second.cacheStatus, products: second.value });
await cache.invalidate([cacheKey(["products", "list", "hi"])]);
await closeRedis();
}
main().catch(async (error) => {
console.error(error);
await closeRedis();
process.exitCode = 1;
});
node demo-products.js
Tests
Key, TTL, hit और concurrent miss को test करें।
// redis-cache.test.js
const test = require("node:test");
const assert = require("node:assert/strict");
const { cacheKey, ttlWithJitter } = require("./cache-policy");
const { RedisJsonCache } = require("./redis-cache");
class FakeRedis {
constructor() {
this.store = new Map();
}
async get(key) {
const entry = this.store.get(key);
if (!entry) return null;
if (entry.expiresAt && entry.expiresAt <= Date.now()) {
this.store.delete(key);
return null;
}
return entry.value;
}
async set(key, value, options = {}) {
if (options.NX && (await this.get(key)) !== null) return null;
const ttlMs = options.PX || (options.EX ? options.EX * 1000 : 0);
this.store.set(key, { value, expiresAt: ttlMs ? Date.now() + ttlMs : 0 });
return "OK";
}
async del(keys) {
const list = Array.isArray(keys) ? keys : [keys];
let deleted = 0;
for (const key of list) {
if (this.store.delete(key)) deleted += 1;
}
return deleted;
}
async eval(_script, options) {
const [key] = options.keys;
const [token] = options.arguments;
if ((await this.get(key)) === token) return this.del(key);
return 0;
}
}
test("cacheKey encodes dynamic parts", () => {
assert.equal(cacheKey(["Products", "List", "hi/IN"]), "claudecodelab:v1:products:list:hi%2Fin");
});
test("ttlWithJitter stays inside the expected range", () => {
for (let i = 0; i < 50; i += 1) {
const ttl = ttlWithJitter(300, 30);
assert.ok(ttl >= 300);
assert.ok(ttl <= 330);
}
});
test("getOrSet caches the first loader result", async () => {
const cache = new RedisJsonCache(new FakeRedis());
let loads = 0;
const first = await cache.getOrSet("products:list", 60, async () => {
loads += 1;
return [{ id: "p1" }];
});
const second = await cache.getOrSet("products:list", 60, async () => {
loads += 1;
return [{ id: "p2" }];
});
assert.equal(first.cacheStatus, "miss");
assert.equal(second.cacheStatus, "hit");
assert.equal(loads, 1);
assert.deepEqual(second.value, [{ id: "p1" }]);
});
test("getOrSet waits instead of running duplicate loaders", async () => {
const cache = new RedisJsonCache(new FakeRedis(), { waitMs: 5, waitRetries: 20 });
let loads = 0;
const loader = async () => {
loads += 1;
await new Promise((resolve) => setTimeout(resolve, 20));
return { total: 42 };
};
const results = await Promise.all([
cache.getOrSet("analytics:daily", 30, loader),
cache.getOrSet("analytics:daily", 30, loader),
]);
assert.equal(loads, 1);
assert.deepEqual(results[0].value, { total: 42 });
assert.deepEqual(results[1].value, { total: 42 });
});
node --test redis-cache.test.js
उपयोग के मामले
पहला use case public catalog या article list है। कई readers एक ही response देखते हैं और update के बाद known keys हटाई जा सकती हैं।
दूसरा use case admin analytics है। PV, conversion और revenue preview कुछ सेकंड या मिनट पुराने हो सकते हैं; permission, invoice और security decision नहीं।
तीसरा use case external APIs है। Exchange rate, weather, SaaS plan और public metadata को short TTL से rate limit pressure कम किया जा सकता है।
चौथा use case rate limit है। INCR और EXPIRE छोटी windows के लिए अच्छे हैं, पर authentication data के लिए अलग security review चाहिए।
आम गलतियां
Incomplete key सबसे आम समस्या है। अगर language, currency, tenant, role, query या version output बदलते हैं, उन्हें key में शामिल करें।
DB write से पहले cache न हटाएं। सुरक्षित order है: DB write success, known Redis keys delete, फिर जरूरत हो तो CDN purge।
Production में KEYS user:* से बचें। Known keys, related-key Set या SCAN based maintenance command इस्तेमाल करें।
Negative cache के लिए { found: false } जैसा object रखें; ऊपर का helper null को miss मानता है।
Review checklist
- Personal, permission और billing data exclude या justify हैं
- Key में locale, tenant, role, query, version शामिल हैं
- TTL business freshness से समझाया गया है
- Invalidation DB write success के बाद है
- Production code में
KEYSनहीं है - Lock, jitter या fallback से stampede कम होता है
- Tests key, TTL, hit और concurrent miss cover करते हैं
- Hit rate और fallback logs/metrics में दिखते हैं
Official references और अगला कदम
Claude Code task में Redis documentation, node-redis guide, Redis patterns और Redis optimization link जोड़ें।
Team workflow बनाने के लिए ClaudeCodeLab products and templates से शुरू करें। Real repository में Redis, CDN, DB writes और observability जोड़ने के लिए Claude Code training and consultation उपयोग करें।
Masa के छोटे Node.js demo में पहली request ने loader चलाया, दूसरी Redis hit बनी, और concurrent miss test में loader सिर्फ एक बार चला। सबसे बड़ा लाभ Redis जोड़ने से नहीं, बल्कि key, TTL, invalidation और review rules पहले लिखने से मिला।
मुफ़्त PDF: Claude Code cheatsheet
Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.
हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.
लेखक के बारे में
Masa
Claude Code workflow और team adoption पर काम करने वाला engineer.
संबंधित लेख
Claude Code Permission Receipt Pattern: scope, proof और rollback लिखना
Claude Code के लिए permission receipt: allowed actions, approval boundary, verification commands, rollback note और revenue CTA checks।
Claude Code और Codex के लिए सुरक्षित Agent Harness: permissions, verification और rollback
Claude Code और Codex agents के लिए सुरक्षित harness: permissions, plan, verification और rollback.
Claude Code Subagents गाइड: article और code work को सुरक्षित तरीके से delegate करें
Claude Code subagents से article और code work बांटें: delegation rules, prompts, pitfalls, checklist और examples.