| name | caching-patterns | |||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| summary | Couchbase as a cache — TTL/expiry, cache-aside pattern, write-through, get_and_touch, ephemeral buckets, session storage, rate limiting with atomic counters, replacing Redis/Memcached with Couchbase | |||||||||||||||||||||||
| description | Couchbase as a cache — TTL/expiry, cache-aside pattern, write-through, get_and_touch, ephemeral buckets, session storage, rate limiting with atomic counters, replacing Redis/Memcached with Couchbase | |||||||||||||||||||||||
| compatibility | Language-agnostic concept skill. Code examples use Node.js, Python, and Java for illustration. | |||||||||||||||||||||||
| metadata |
|
Couchbase works as a cache natively — every document can have a TTL, and the KV API is sub-millisecond. No separate Redis instance needed.
Set expiry on any KV operation. The document is automatically deleted when it expires.
// Node.js — expiry in seconds
await collection.upsert('session::abc123', { userId: 'alice', cart: [] },
{ expiry: 3600 } // 1 hour
);
// Insert with expiry (fails if key exists)
await collection.insert('rate::ip::1.2.3.4', { count: 0 },
{ expiry: 60 } // 1 minute window
);# Python
from datetime import timedelta
collection.upsert('session::abc123', {'userId': 'alice'},
UpsertOptions(expiry=timedelta(hours=1)))// Java
collection.upsert("session::abc123", JsonObject.create().put("userId", "alice"),
UpsertOptions.upsertOptions().expiry(Duration.ofHours(1)));The most common pattern: read from cache, fall back to source of truth on miss, populate cache.
async function getUser(userId) {
const cacheKey = `user::${userId}`;
// 1. Try cache
try {
const cached = await collection.get(cacheKey);
return cached.content;
} catch (e) {
if (!(e instanceof couchbase.DocumentNotFoundError)) throw e;
}
// 2. Cache miss — fetch from database
const user = await db.users.findById(userId);
if (!user) return null;
// 3. Populate cache with TTL
await collection.upsert(cacheKey, user, { expiry: 300 }); // 5 min
return user;
}Write to cache and source of truth together. Cache is always warm.
async function updateUser(userId, updates) {
// 1. Write to source of truth
const user = await db.users.update(userId, updates);
// 2. Update cache immediately
await collection.upsert(`user::${userId}`, user, { expiry: 300 });
return user;
}
async function deleteUser(userId) {
await db.users.delete(userId);
// Invalidate cache
try {
await collection.remove(`user::${userId}`);
} catch (e) {
if (!(e instanceof couchbase.DocumentNotFoundError)) throw e;
}
}Extend a document's TTL each time it's accessed (sliding expiry — useful for sessions).
// Node.js — getAndTouch resets TTL and returns content atomically
const result = await collection.getAndTouch('session::abc123', 3600);
const session = result.content;# Python
result = collection.get_and_touch('session::abc123', timedelta(hours=1))
session = result.content_as[dict]// Java
GetResult result = collection.getAndTouch("session::abc123", Duration.ofHours(1));// Create session
async function createSession(userId) {
const sessionId = crypto.randomUUID();
await collection.insert(`session::${sessionId}`, {
userId,
createdAt: Date.now(),
data: {}
}, { expiry: 86400 }); // 24 hours
return sessionId;
}
// Read + extend session (sliding expiry)
async function getSession(sessionId) {
try {
const result = await collection.getAndTouch(
`session::${sessionId}`,
86400 // reset to 24h on each access
);
return result.content;
} catch (e) {
if (e instanceof couchbase.DocumentNotFoundError) return null;
throw e;
}
}
// Destroy session
async function destroySession(sessionId) {
try {
await collection.remove(`session::${sessionId}`);
} catch (e) {
if (!(e instanceof couchbase.DocumentNotFoundError)) throw e;
}
}async function checkRateLimit(ip, limitPerMinute = 100) {
const key = `rate::${ip}::${Math.floor(Date.now() / 60000)}`; // per-minute bucket
try {
const result = await collection.binary.increment(key, {
delta: 1,
initial: 1,
expiry: 60, // auto-expire after 1 minute
});
return result.value <= limitPerMinute;
} catch (e) {
// Fail open on errors — don't block legitimate traffic
console.error('Rate limit check failed:', e);
return true;
}
}For pure caching workloads (no persistence needed), use an Ephemeral bucket:
- Data lives in RAM only — no disk I/O
- Faster than Couchbase buckets for cache-only use cases
- Eviction policies:
noEviction(reject writes when full) ornruEviction(evict least-recently-used)
Create via UI: Buckets → Add Bucket → Bucket Type: Ephemeral
Or via REST:
curl -u Administrator:"$CB_ADMIN_PASSWORD" \
-X POST http://localhost:8091/pools/default/buckets \
-d name=cache \
-d bucketType=ephemeral \
-d ramQuota=512 \
-d evictionPolicy=nruEvictionConnect to an ephemeral bucket the same way as a regular bucket — the SDK API is identical.
session::{session-id} # user sessions
user::{user-id}:profile # cached user profiles
product::{sku}:detail # product detail pages
rate::{ip}::{minute-bucket} # rate limit counters
lock::{resource-id} # distributed locks (use getAndLock)
| Feature | Redis | Couchbase |
|---|---|---|
| TTL per key | ✅ | ✅ |
| Atomic increment | ✅ | ✅ |
| Sub-ms KV | ✅ | ✅ |
| Sliding expiry | ✅ GETEX |
✅ getAndTouch |
| Persistence | Optional | Built-in |
| SQL queries over cache | ❌ | ✅ SQL++ |
| Full-text / vector search | ❌ | ✅ |
| Transactions | Limited | ✅ ACID |
| Cluster replication | ✅ | ✅ |
| Ephemeral (RAM-only) mode | ✅ | ✅ Ephemeral bucket |