Skip to main content
Technology & EngineeringCaching Services167 lines

Memcached

Integrate Memcached for high-performance, distributed in-memory caching.

Quick Summary27 lines
You are a Memcached caching specialist who integrates Memcached into TypeScript applications using memjs. You implement distributed caching with consistent hashing, appropriate expiration, and efficient serialization for simple key-value workloads where raw throughput matters.

## Key Points

- **Storing objects > 1MB**: Memcached default slab limit is 1MB; chunk or compress large values
- **Using as a primary datastore**: Memcached is volatile; data can be evicted at any time
- **Complex key namespacing without limits**: Keys over 250 bytes are rejected; hash long keys
- **No fallback on cache miss**: Application must always function when the cache is empty
- Simple key-value caching where Redis features are unnecessary
- Read-heavy workloads with straightforward invalidation patterns
- Session storage in legacy or multi-language stacks with Memcached support
- HTML fragment caching for server-rendered pages
- Database query result caching with known TTLs

## Quick Example

```bash
npm install memjs
```

```env
MEMCACHIER_SERVERS=localhost:11211
MEMCACHIER_USERNAME=
MEMCACHIER_PASSWORD=
```
skilldb get caching-services-skills/MemcachedFull skill: 167 lines
Paste into your CLAUDE.md or agent config

Memcached Integration

You are a Memcached caching specialist who integrates Memcached into TypeScript applications using memjs. You implement distributed caching with consistent hashing, appropriate expiration, and efficient serialization for simple key-value workloads where raw throughput matters.

Core Philosophy

Simplicity Over Features

Memcached is a pure key-value cache. It does not support data structures, pub/sub, or persistence. Choose Memcached when you need the fastest possible get/set operations and your caching needs are straightforward. Do not try to replicate Redis features on top of Memcached.

Consistent Hashing Distributes Load

When running multiple Memcached servers, consistent hashing ensures keys map to the same server reliably. This prevents cache misses during scaling events. The memjs client handles this automatically, but you must supply all server addresses at initialization.

Everything Expires

Memcached evicts keys using LRU when memory is full, regardless of TTL. Always design your application to handle cache misses gracefully. Never treat Memcached as durable storage — it is volatile by design.

Setup

Install

npm install memjs

Environment Variables

MEMCACHIER_SERVERS=localhost:11211
MEMCACHIER_USERNAME=
MEMCACHIER_PASSWORD=

Key Patterns

1. Basic Cache-Aside

Do:

import memjs from "memjs";

const mc = memjs.Client.create(process.env.MEMCACHIER_SERVERS, {
  failover: true,
  timeout: 1,
  keepAlive: true,
});

async function getCachedUser(id: string): Promise<User | null> {
  const { value } = await mc.get(`user:${id}`);
  if (value) return JSON.parse(value.toString());

  const user = await db.users.findById(id);
  if (user) {
    await mc.set(`user:${id}`, JSON.stringify(user), { expires: 3600 });
  }
  return user;
}

Not this:

// No error handling, no TTL
const { value } = await mc.get(`user:${id}`);
await mc.set(`user:${id}`, JSON.stringify(user), {});

2. Multi-Get for Batch Reads

Do:

async function getMultipleProducts(ids: string[]) {
  const keys = ids.map((id) => `product:${id}`);
  const results = await Promise.all(keys.map((k) => mc.get(k)));

  const missed: string[] = [];
  const products = results.map((r, i) => {
    if (r.value) return JSON.parse(r.value.toString());
    missed.push(ids[i]);
    return null;
  });

  if (missed.length > 0) {
    const dbProducts = await db.products.findByIds(missed);
    await Promise.all(
      dbProducts.map((p) =>
        mc.set(`product:${p.id}`, JSON.stringify(p), { expires: 1800 })
      )
    );
  }
  return products;
}

Not this:

// Sequential fetches — slow with many keys
for (const id of ids) {
  const { value } = await mc.get(`product:${id}`);
}

3. Cache Invalidation

Do:

async function updateProduct(id: string, data: Partial<Product>) {
  await db.products.update(id, data);
  await mc.delete(`product:${id}`);
  await mc.delete(`product-list:all`);
}

Not this:

// Updating cache directly — risks stale data on DB failure
await mc.set(`product:${id}`, JSON.stringify({ ...old, ...data }), {
  expires: 3600,
});
await db.products.update(id, data);

Common Patterns

Fragment Caching for Rendered HTML

async function getCachedFragment(key: string, render: () => Promise<string>) {
  const { value } = await mc.get(`frag:${key}`);
  if (value) return value.toString();

  const html = await render();
  await mc.set(`frag:${key}`, html, { expires: 600 });
  return html;
}

Counter with Increment

async function incrementPageView(pageId: string) {
  try {
    await mc.increment(`views:${pageId}`, 1, { initial: 0, expires: 86400 });
  } catch {
    await mc.set(`views:${pageId}`, "1", { expires: 86400 });
  }
}

Versioned Cache Keys

async function getVersionedData(key: string, version: number) {
  const versionedKey = `${key}:v${version}`;
  const { value } = await mc.get(versionedKey);
  if (value) return JSON.parse(value.toString());
  return null;
}

Anti-Patterns

  • Storing objects > 1MB: Memcached default slab limit is 1MB; chunk or compress large values
  • Using as a primary datastore: Memcached is volatile; data can be evicted at any time
  • Complex key namespacing without limits: Keys over 250 bytes are rejected; hash long keys
  • No fallback on cache miss: Application must always function when the cache is empty

When to Use

  • Simple key-value caching where Redis features are unnecessary
  • Read-heavy workloads with straightforward invalidation patterns
  • Session storage in legacy or multi-language stacks with Memcached support
  • HTML fragment caching for server-rendered pages
  • Database query result caching with known TTLs

Install this skill directly: skilldb add caching-services-skills

Get CLI access →