Memcached #

Memcached adalah cache in-memory yang lahir dari satu filosofi: lakukan satu hal dengan sangat baik. Tidak ada struktur data kompleks, tidak ada persistence, tidak ada pub/sub — hanya key-value store yang cepat, ringan, dan mudah di-scale secara horizontal. Desain minimalis ini bukan kekurangan, melainkan keunggulan. Dengan arsitektur multi-threaded yang memanfaatkan seluruh core CPU dan protokol yang sangat sederhana, Memcached bisa menangani throughput yang sangat tinggi dengan overhead yang sangat kecil. Di ekosistem TypeScript, library memjs menyediakan antarmuka yang bersih dengan dukungan multi-server dan consistent hashing bawaan — kamu bisa menambah node cache tanpa mengubah konfigurasi klien secara manual.

Instalasi #

npm install memjs
npm install --save-dev @types/memjs

Untuk menjalankan Memcached lokal via Docker:

docker run -d --name memcached -p 11211:11211 memcached:1.6-alpine

Verifikasi server berjalan dengan telnet:

telnet localhost 11211
# ketik: stats
# ketik: quit

Arsitektur dan Cara Kerja Memcached #

Sebelum menulis kode, penting memahami bagaimana Memcached mengelola memori dan mendistribusikan data — karena ini langsung mempengaruhi keputusan desain di aplikasi kamu.

Slab Allocator #

Memcached tidak mengalokasikan memori secara dinamis per item. Ia membagi memori menjadi slab — blok berukuran tetap. Setiap slab menampung item-item yang ukurannya masuk dalam kelasnya.

Slab Class 1:  item ukuran   1 –  96 bytes
Slab Class 2:  item ukuran  97 – 120 bytes
Slab Class 3:  item ukuran 121 – 152 bytes
...
Slab Class 42: item ukuran sampai 1 MB

Implikasinya: jika kamu sering menyimpan item berukuran 100 bytes, slab class 2 akan penuh sementara slab class lain kosong. Memori tidak bisa dipinjam antar slab. Ini adalah trade-off yang disengaja untuk menghindari fragmentasi memori.

LRU Eviction #

Saat memori penuh dan ada item baru yang ingin disimpan, Memcached menghapus item yang paling lama tidak diakses (Least Recently Used) dari slab class yang sama. Item yang dihapus ini tidak memberikan notifikasi apapun — aplikasi harus siap menghadapi cache miss kapanpun.

flowchart TD
    A[SET item baru] --> B{Memori cukup\ndi slab class?}
    B -- Ya --> C[Simpan item]
    B -- Tidak --> D{Ada item expired\ndi slab class ini?}
    D -- Ya --> E[Hapus item expired]
    D -- Tidak --> F[Hapus item LRU\ndi slab class ini]
    E --> C
    F --> C

Distribusi Key di Multi-Server #

Memcached tidak punya mekanisme replikasi bawaan — setiap server adalah node independen. Klien yang bertanggung jawab menentukan server mana yang menyimpan sebuah key, menggunakan consistent hashing.

flowchart LR
    A[Key: produk:1001] --> B[Hash Function]
    B --> C{Consistent\nHash Ring}
    C --> D[Server A\n192.168.1.1:11211]
    C --> E[Server B\n192.168.1.2:11211]
    C --> F[Server C\n192.168.1.3:11211]

Dengan consistent hashing, menambah atau menghapus satu server hanya meredistribusi sebagian kecil key (sekitar 1/N dari total key), bukan seluruhnya. Library memjs sudah mengimplementasikan ini secara bawaan.


Koneksi ke Memcached #

import MemJS from "memjs";

// koneksi ke satu server
const client = MemJS.Client.create("localhost:11211", {
  failover: true,         // coba server berikutnya jika satu gagal
  timeout: 1,             // timeout koneksi dalam detik
  keepAlive: true,        // pertahankan koneksi TCP
  keepAliveDelay: 30,     // kirim keepalive setiap 30 detik
  retries: 2,             // jumlah retry saat request gagal
});

// koneksi ke beberapa server (auto consistent hashing)
const clientCluster = MemJS.Client.create(
  "192.168.1.1:11211,192.168.1.2:11211,192.168.1.3:11211",
  {
    failover: true,
    timeout: 1,
    keepAlive: true,
  }
);

// koneksi via environment variable — pola yang umum di production
const clientFromEnv = MemJS.Client.create(
  process.env.MEMCACHED_SERVERS ?? "localhost:11211",
  {
    username: process.env.MEMCACHED_USERNAME,
    password: process.env.MEMCACHED_PASSWORD,
    timeout: 1,
    failover: true,
  }
);

// menutup koneksi saat aplikasi berhenti
process.on("SIGTERM", () => {
  client.quit();
});

Wrapper dengan Type Safety #

memjs mengembalikan Buffer untuk semua nilai — kamu perlu serialisasi dan deserialisasi secara manual. Buat wrapper tipis untuk menangani ini:

import MemJS from "memjs";

class MemcachedClient {
  private client: MemJS.Client;

  constructor(servers: string, options?: MemJS.ClientOptions) {
    this.client = MemJS.Client.create(servers, {
      failover: true,
      timeout: 1,
      keepAlive: true,
      ...options,
    });
  }

  async get<T>(key: string): Promise<T | null> {
    const hasil = await this.client.get(key);
    if (hasil.value === null) return null;

    try {
      return JSON.parse(hasil.value.toString()) as T;
    } catch {
      // nilai bukan JSON (misalnya string biasa)
      return hasil.value.toString() as unknown as T;
    }
  }

  async set(key: string, value: unknown, ttlDetik: number = 0): Promise<boolean> {
    const serialized = typeof value === "string" ? value : JSON.stringify(value);
    return this.client.set(key, serialized, { expires: ttlDetik });
  }

  async add(key: string, value: unknown, ttlDetik: number = 0): Promise<boolean> {
    // add hanya berhasil jika key BELUM ada — atomik
    const serialized = typeof value === "string" ? value : JSON.stringify(value);
    return this.client.add(key, serialized, { expires: ttlDetik });
  }

  async replace(key: string, value: unknown, ttlDetik: number = 0): Promise<boolean> {
    // replace hanya berhasil jika key SUDAH ada — atomik
    const serialized = typeof value === "string" ? value : JSON.stringify(value);
    return this.client.replace(key, serialized, { expires: ttlDetik });
  }

  async delete(key: string): Promise<boolean> {
    return this.client.delete(key);
  }

  async increment(key: string, delta: number = 1): Promise<number | null> {
    const hasil = await this.client.increment(key, delta);
    return hasil.value;
  }

  async decrement(key: string, delta: number = 1): Promise<number | null> {
    const hasil = await this.client.decrement(key, delta);
    return hasil.value;
  }

  async flush(): Promise<void> {
    await this.client.flush();
  }

  quit(): void {
    this.client.quit();
  }
}

// singleton instance
let memcached: MemcachedClient;

export function getMemcached(): MemcachedClient {
  if (!memcached) {
    memcached = new MemcachedClient(
      process.env.MEMCACHED_SERVERS ?? "localhost:11211"
    );
  }
  return memcached;
}

Operasi Dasar #

Set dan Get #

set menyimpan nilai dengan key tertentu. Parameter ketiga adalah TTL dalam detik — nilai 0 berarti tidak ada kadaluarsa (item bertahan sampai di-evict atau di-delete secara eksplisit).

const cache = getMemcached();

// simpan string sederhana
await cache.set("app:status", "online", 60);

// simpan object — otomatis di-serialize ke JSON
interface KonfigurasiApp {
  tema: string;
  bahasa: string;
  batasUpload: number;
}

await cache.set(
  "konfigurasi:global",
  { tema: "dark", bahasa: "id", batasUpload: 10485760 } satisfies KonfigurasiApp,
  3600 // cache selama 1 jam
);

// ambil nilai
const status = await cache.get<string>("app:status");
// status = "online" atau null jika expired/tidak ada

const config = await cache.get<KonfigurasiApp>("konfigurasi:global");
// config = { tema: "dark", ... } atau null

Add dan Replace — Operasi Kondisional #

add dan replace adalah operasi atomik yang berguna untuk menghindari race condition.

// add — HANYA berhasil jika key belum ada
// berguna untuk inisialisasi counter atau sebagai primitive lock sederhana
const berhasil = await cache.add("init:migrasi-v2", "running", 300);

if (berhasil) {
  // hanya satu proses yang masuk sini
  await jalankanMigrasi();
  await cache.delete("init:migrasi-v2");
} else {
  console.log("Migrasi sudah berjalan di proses lain");
}

// replace — HANYA berhasil jika key sudah ada
// berguna untuk memperbarui nilai yang kamu tahu sudah ada di cache
const diperbarui = await cache.replace("konfigurasi:global", konfigBaru, 3600);
if (!diperbarui) {
  // key sudah expired atau dihapus — perlu set ulang
  await cache.set("konfigurasi:global", konfigBaru, 3600);
}

Increment dan Decrement #

Counter atomik — aman digunakan dari beberapa proses sekaligus tanpa khawatir race condition.

// PENTING: key harus diinisialisasi dengan nilai numerik sebagai string
// sebelum bisa di-increment
await cache.set("counter:page-view:home", "0");

// increment sebesar 1 (default)
const viewCount = await cache.increment("counter:page-view:home");
console.log("Total view:", viewCount); // 1

// increment sebesar N
await cache.increment("counter:page-view:home", 5); // 6

// decrement
await cache.decrement("counter:stok:produk-1001", 1);

// ANTI-PATTERN: implementasi counter dengan GET + SET
async function tambahViewSalah(key: string): Promise<void> {
  const current = await cache.get<number>(key);  // ✗ race condition di sini
  const next = (current ?? 0) + 1;               // dua proses bisa baca nilai yang sama
  await cache.set(key, next);                     // lalu keduanya menyimpan nilai yang sama
}

// BENAR: gunakan increment yang atomik
async function tambahView(key: string): Promise<number | null> {
  return cache.increment(key); // ✓ atomik, aman untuk concurrent access
}

Pola Caching #

Cache-Aside (Lazy Loading) #

Pola paling umum dan paling aman — aplikasi mengelola cache secara eksplisit.

async function ambilArtikel(id: string): Promise<Artikel | null> {
  const key = `artikel:${id}`;

  // 1. coba dari cache
  const cached = await cache.get<Artikel>(key);
  if (cached !== null) {
    return cached;
  }

  // 2. cache miss — ambil dari database
  const artikel = await db.findById("artikel", id);
  if (!artikel) return null;

  // 3. simpan ke cache
  await cache.set(key, artikel, 1800); // cache 30 menit

  return artikel;
}

async function updateArtikel(id: string, data: Partial<Artikel>): Promise<void> {
  await db.update("artikel", id, data);
  await cache.delete(`artikel:${id}`); // invalidasi cache
}

Stampede Protection — Dog-pile Effect #

Cache stampede terjadi saat banyak request datang bersamaan saat cache expired. Semua request langsung menghantam database secara bersamaan.

sequenceDiagram
    participant R1 as Request 1
    participant R2 as Request 2
    participant R3 as Request 3
    participant C as Cache
    participant DB as Database

    R1->>C: GET artikel:1
    R2->>C: GET artikel:1
    R3->>C: GET artikel:1
    C-->>R1: null (expired)
    C-->>R2: null (expired)
    C-->>R3: null (expired)
    R1->>DB: SELECT artikel WHERE id=1
    R2->>DB: SELECT artikel WHERE id=1
    R3->>DB: SELECT artikel WHERE id=1
    Note over DB: ⚠️ 3 query identik bersamaan

Solusinya adalah probabilistic early expiration atau lock-based fetch:

// pola lock sederhana dengan add() untuk mencegah stampede
async function ambilDenganLock<T>(
  key: string,
  ttl: number,
  fetchFn: () => Promise<T>
): Promise<T> {
  // coba dari cache
  const cached = await cache.get<T>(key);
  if (cached !== null) return cached;

  const lockKey = `lock:${key}`;

  // coba dapatkan lock — add() atomik, hanya satu yang berhasil
  const dapatLock = await cache.add(lockKey, "1", 10); // lock 10 detik

  if (dapatLock) {
    // proses ini yang fetch dari DB
    try {
      const data = await fetchFn();
      if (data !== null && data !== undefined) {
        await cache.set(key, data, ttl);
      }
      return data;
    } finally {
      await cache.delete(lockKey);
    }
  } else {
    // proses lain sedang fetch — tunggu sebentar lalu coba cache lagi
    await new Promise((resolve) => setTimeout(resolve, 50));
    const hasil = await cache.get<T>(key);

    if (hasil !== null) return hasil;

    // jika masih null (timeout lock), fetch langsung
    return fetchFn();
  }
}

// penggunaan
const artikel = await ambilDenganLock(
  `artikel:${id}`,
  1800,
  () => db.findById("artikel", id)
);

Namespace dan Key Invalidation Massal #

Memcached tidak mendukung operasi KEYS pattern* seperti Redis. Untuk menginvalidasi sekelompok key sekaligus, gunakan teknik namespace versioning:

class NamespacedCache {
  private cache: MemcachedClient;

  constructor(cache: MemcachedClient) {
    this.cache = cache;
  }

  // ambil versi namespace saat ini
  private async getNamespaceVersion(namespace: string): Promise<number> {
    const versi = await this.cache.get<number>(`ns:${namespace}`);
    if (versi !== null) return versi;

    // inisialisasi versi jika belum ada
    await this.cache.set(`ns:${namespace}`, 1, 0); // tidak ada TTL untuk namespace
    return 1;
  }

  // buat key dengan namespace + versi
  async buildKey(namespace: string, key: string): Promise<string> {
    const versi = await this.getNamespaceVersion(namespace);
    return `${namespace}:v${versi}:${key}`;
  }

  async get<T>(namespace: string, key: string): Promise<T | null> {
    const fullKey = await this.buildKey(namespace, key);
    return this.cache.get<T>(fullKey);
  }

  async set(namespace: string, key: string, value: unknown, ttl: number): Promise<void> {
    const fullKey = await this.buildKey(namespace, key);
    await this.cache.set(fullKey, value, ttl);
  }

  // invalidasi seluruh namespace dengan increment versi
  // semua key lama otomatis tidak terjangkau (versi berbeda)
  // dan akan di-evict oleh LRU secara alami
  async invalidasiNamespace(namespace: string): Promise<void> {
    await this.cache.increment(`ns:${namespace}`);
    // jika key namespace belum ada, increment akan gagal
    // set ulang jika perlu
    const versi = await this.cache.get<number>(`ns:${namespace}`);
    if (versi === null) {
      await this.cache.set(`ns:${namespace}`, 1, 0);
    }
  }
}

const nsCache = new NamespacedCache(cache);

// simpan semua artikel dengan namespace "artikel"
await nsCache.set("artikel", "1001", dataArtikel, 1800);
await nsCache.set("artikel", "1002", dataArtikel2, 1800);

// invalidasi SEMUA artikel sekaligus dengan satu perintah
// (increment versi namespace — key lama otomatis tidak terjangkau)
await nsCache.invalidasiNamespace("artikel");

Caching Multi-Level #

Untuk aplikasi dengan traffic sangat tinggi, kamu bisa menggabungkan in-process cache (memori Node.js) dengan Memcached. Ini mengurangi latency jaringan untuk data yang paling sering diakses.

flowchart LR
    A[Request] --> B{L1 Cache\nIn-Process Memory}
    B -- Hit --> G[Response]
    B -- Miss --> C{L2 Cache\nMemcached}
    C -- Hit --> F[Isi L1 Cache]
    F --> G
    C -- Miss --> D[(Database)]
    D --> E[Isi L2 Cache]
    E --> F
// cache L1: in-process dengan Map dan TTL sederhana
class InProcessCache {
  private store = new Map<string, { value: unknown; expiresAt: number }>();

  get<T>(key: string): T | null {
    const item = this.store.get(key);
    if (!item) return null;
    if (Date.now() > item.expiresAt) {
      this.store.delete(key);
      return null;
    }
    return item.value as T;
  }

  set(key: string, value: unknown, ttlMs: number): void {
    this.store.set(key, { value, expiresAt: Date.now() + ttlMs });
  }

  delete(key: string): void {
    this.store.delete(key);
  }
}

class MultiLevelCache {
  private l1 = new InProcessCache();
  private l2: MemcachedClient;

  constructor(memcached: MemcachedClient) {
    this.l2 = memcached;
  }

  async get<T>(key: string): Promise<T | null> {
    // L1: in-process (sub-millisecond)
    const fromL1 = this.l1.get<T>(key);
    if (fromL1 !== null) return fromL1;

    // L2: Memcached (1-2ms via jaringan lokal)
    const fromL2 = await this.l2.get<T>(key);
    if (fromL2 !== null) {
      // isi L1 dengan TTL lebih pendek
      this.l1.set(key, fromL2, 10_000); // 10 detik di L1
      return fromL2;
    }

    return null;
  }

  async set(key: string, value: unknown, ttlDetik: number): Promise<void> {
    // tulis ke kedua layer
    this.l1.set(key, value, Math.min(ttlDetik * 1000, 10_000)); // maks 10 detik di L1
    await this.l2.set(key, value, ttlDetik);
  }

  async delete(key: string): Promise<void> {
    this.l1.delete(key);
    await this.l2.delete(key);
  }
}

const multiCache = new MultiLevelCache(cache);

// penggunaan transparan — klien tidak perlu tahu ada dua layer
async function ambilProdukCepat(id: string): Promise<Produk | null> {
  const key = `produk:${id}`;

  const cached = await multiCache.get<Produk>(key);
  if (cached) return cached;

  const produk = await db.findById("produk", id);
  if (produk) {
    await multiCache.set(key, produk, 300); // 5 menit di Memcached, 10 detik di memory
  }

  return produk;
}

Monitoring dan Stats #

Memcached menyediakan statistik detail yang berguna untuk memantau kesehatan cache dan mendiagnosis masalah performa.

async function cekStatsMemcached(): Promise<void> {
  // memjs menyediakan akses ke stats via client internal
  // untuk stats lengkap, kita perlu akses TCP langsung atau gunakan stats dari client

  // cara alternatif: sambungkan via net module untuk membaca stats
  const net = await import("net");

  return new Promise((resolve, reject) => {
    const socket = net.createConnection(11211, "localhost");
    let data = "";

    socket.on("connect", () => {
      socket.write("stats\r\n");
    });

    socket.on("data", (chunk) => {
      data += chunk.toString();
      if (data.includes("END\r\n")) {
        socket.end();
      }
    });

    socket.on("end", () => {
      const stats: Record<string, string> = {};
      data.split("\r\n").forEach((line) => {
        const parts = line.split(" ");
        if (parts[0] === "STAT") {
          stats[parts[1]] = parts[2];
        }
      });

      // metrik paling penting
      const hitRate =
        (Number(stats.get_hits) /
          (Number(stats.get_hits) + Number(stats.get_misses))) *
        100;

      console.log("=== Memcached Stats ===");
      console.log(`Uptime: ${stats.uptime} detik`);
      console.log(`Memori digunakan: ${(Number(stats.bytes) / 1024 / 1024).toFixed(2)} MB`);
      console.log(`Memori maksimal: ${(Number(stats.limit_maxbytes) / 1024 / 1024).toFixed(2)} MB`);
      console.log(`Hit rate: ${hitRate.toFixed(2)}%`);
      console.log(`Cache hits: ${stats.get_hits}`);
      console.log(`Cache misses: ${stats.get_misses}`);
      console.log(`Evictions: ${stats.evictions}`);
      console.log(`Total item: ${stats.curr_items}`);
      console.log(`Koneksi aktif: ${stats.curr_connections}`);
      console.log(`Total operasi get: ${stats.cmd_get}`);
      console.log(`Total operasi set: ${stats.cmd_set}`);

      resolve();
    });

    socket.on("error", reject);
  });
}

Metrik yang perlu diperhatikan:

MetrikKondisi SehatTindakan jika Bermasalah
Hit rate> 80%Tinjau TTL, tambah memori, atau perbaiki strategi caching
EvictionsMendekati 0Tambah kapasitas memori atau kurangi ukuran item
Curr connectionsStabilCek connection pool di sisi aplikasi
Bytes / limit_maxbytes< 90%Tambah memori jika mendekati limit

Perbandingan Memcached vs Redis #

Memilih antara Memcached dan Redis adalah keputusan yang sering muncul. Keduanya adalah solusi caching yang matang, tapi punya karakteristik yang berbeda.

FiturMemcachedRedis
Struktur dataHanya stringString, Hash, List, Set, Sorted Set, Stream
ThreadingMulti-threadedSingle-threaded (dengan I/O async)
PersistenceTidak adaRDB snapshot + AOF log
Pub/SubTidak adaAda
TransaksiTidak adaAda (MULTI/EXEC)
ScriptingTidak adaLua script
ClusterDistribusi via klienRedis Cluster bawaan
Memory overheadSangat rendahLebih tinggi (metadata per key)
Max value size1 MB512 MB
Kegunaan utamaPure cachingCaching + use case lain
Pilih Memcached jika:
  ✓ Kebutuhan adalah pure caching tanpa fitur tambahan
  ✓ Data yang di-cache berupa string atau object sederhana
  ✓ Butuh multi-threading untuk memanfaatkan semua core CPU
  ✓ Memory overhead harus seminimal mungkin
  ✓ Sudah ada infrastruktur Memcached yang berjalan baik
  ✓ Skalabilitas horizontal yang sangat sederhana (tambah node, selesai)

Pilih Redis jika:
  ✗ Butuh struktur data seperti sorted set untuk leaderboard
  ✗ Perlu pub/sub untuk komunikasi antar service
  ✗ Butuh session store dengan operasi field independen (Hash)
  ✗ Perlu distributed lock yang robust (Lua script atomik)
  ✗ Data cache perlu bertahan setelah restart (persistence)
  ✗ Butuh rate limiting dengan sliding window (Sorted Set)

Penanganan Error #

memjs melempar error untuk masalah koneksi, tapi mengembalikan null untuk cache miss — keduanya perlu dibedakan.

async function getAman<T>(key: string): Promise<T | null> {
  try {
    return await cache.get<T>(key);
    // mengembalikan null untuk cache miss — bukan error
  } catch (error) {
    // error jaringan atau server tidak tersedia
    if (error instanceof Error) {
      console.error(`Memcached error untuk key "${key}":`, error.message);
    }
    // kembalikan null agar aplikasi bisa fallback ke database
    return null;
  }
}

async function setAman(key: string, value: unknown, ttl: number): Promise<void> {
  try {
    await cache.set(key, value, ttl);
  } catch (error) {
    // gagal menyimpan ke cache — catat tapi jangan gagalkan operasi utama
    // aplikasi tetap berjalan, hanya tanpa cache untuk sementara
    console.error(`Gagal menyimpan ke Memcached (key: "${key}"):`, error);
  }
}

// wrapper yang membuat cache sepenuhnya opsional
// jika Memcached mati, aplikasi tetap berjalan via database
async function withFallback<T>(
  key: string,
  ttl: number,
  fetchFn: () => Promise<T>
): Promise<T> {
  // coba cache — jika error, lanjut ke fetchFn
  const cached = await getAman<T>(key);
  if (cached !== null) return cached;

  const data = await fetchFn();

  // simpan ke cache secara fire-and-forget — jangan blokir response
  setAman(key, data, ttl).catch((err) => {
    console.error("Cache write gagal (diabaikan):", err);
  });

  return data;
}
Jangan biarkan kegagalan Memcached menggagalkan request pengguna. Cache adalah lapisan opsional — jika cache tidak tersedia, aplikasi harus bisa berjalan langsung ke database (degraded gracefully). Selalu tangkap error Memcached dan kembalikan null atau lanjutkan tanpa cache.

Kapan Menggunakan Memcached #

Gunakan Memcached untuk:
  ✓ Cache hasil query database yang mahal (laporan, agregasi, join kompleks)
  ✓ Cache response API eksternal yang lambat
  ✓ Cache halaman atau fragment HTML yang dirender server
  ✓ Menyimpan konfigurasi aplikasi yang jarang berubah
  ✓ Counter sederhana dengan increment/decrement atomik
  ✓ Aplikasi yang sudah punya Memcached dan tidak butuh fitur Redis

Jangan gunakan Memcached untuk:
  ✗ Session management — tidak ada struktur hash untuk update field independen
  ✗ Leaderboard atau ranking — tidak ada sorted set
  ✗ Pub/sub atau message passing antar service
  ✗ Data yang tidak boleh hilang — tidak ada persistence
  ✗ Item lebih besar dari 1 MB — ada batas ukuran nilai
  ✗ Invalidasi massal berdasarkan pattern — tidak ada SCAN seperti Redis

Ringkasan #

  • Memcached adalah pure cache — tidak ada persistence, tidak ada struktur data kompleks, tidak ada pub/sub. Desain minimalis ini menghasilkan throughput tinggi dengan overhead memori yang sangat rendah.
  • Slab allocator dan LRU eviction adalah fondasi Memcached — pahami bahwa eviction bisa terjadi kapanpun saat memori penuh; aplikasi harus selalu siap menghadapi cache miss.
  • Consistent hashing memungkinkan penambahan node server tanpa redistribusi masif — memjs sudah menangani ini secara otomatis dari daftar server yang kamu berikan.
  • add() dan replace() adalah operasi atomik yang berguna — gunakan add() untuk inisialisasi atau primitive lock, replace() untuk memperbarui nilai yang sudah ada tanpa risiko membuat key baru.
  • Namespace versioning adalah cara menginvalidasi sekelompok key sekaligus di Memcached — increment versi namespace agar semua key lama otomatis tidak terjangkau tanpa perlu menghapusnya satu per satu.
  • Multi-level cache (in-process + Memcached) bisa mengurangi latency secara signifikan untuk data yang sangat sering diakses — gunakan TTL lebih pendek di L1 untuk meminimalkan stale data.
  • Selalu tangkap error Memcached dan kembalikan null — cache adalah lapisan opsional. Kegagalan Memcached tidak boleh menggagalkan request pengguna; fallback ke database adalah perilaku yang benar.
  • Pantau hit rate dan eviction secara rutin — hit rate di bawah 80% mengindikasikan TTL terlalu pendek atau kapasitas memori kurang; eviction tinggi adalah sinyal untuk menambah memori.

← Sebelumnya: Redis   Berikutnya: Artikel & Sumber Daya →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact