Memcached Overview
Memcached is a simple in-memory key-value cache. PHP applications use it to avoid repeating expensive work, such as database queries, rendered fragments, API lookups, or configuration reads.
Memcached is intentionally narrow: it is mainly a cache. Unlike Redis, it is not commonly used as a queue, stream, lock store, or general data-structure server. That simplicity can be a strength when all the application needs is fast temporary cache storage.
Cache, Not Source Of Truth
Memcached values can disappear because they expire, because memory fills up, or because the server restarts. Application code must treat Memcached as optional acceleration, not as the only copy of important business data.
The usual flow is:
- Build a cache key.
- Try to read the value from Memcached.
- If it exists, use it.
- If it does not exist, load from the source of truth.
- Store the value in Memcached with a TTL.
<?php
declare(strict_types=1);
function productCacheKey(int $productId): string
{
return 'product:' . $productId;
}
echo productCacheKey(123) . PHP_EOL;
// Prints:
// product:123
TTLs
A TTL, or time to live, controls how long a value may remain in cache. Most Memcached entries should have an expiry.
<?php
declare(strict_types=1);
$cacheWrite = [
'key' => 'product:123',
'ttl_seconds' => 600,
'value' => ['id' => 123, 'name' => 'Keyboard'],
];
print_r($cacheWrite);
// Prints:
// [ttl_seconds] => 600
Short TTLs reduce stale data but increase cache misses. Long TTLs reduce repeated work but may serve old data for longer. Choose TTLs based on the cost of recomputing and how quickly the data changes.
Cache Misses
A cache miss is normal. Code should not treat it as an error.
<?php
declare(strict_types=1);
function cachePlan(bool $hit): string
{
return $hit ? 'use cached value' : 'load from database and cache result';
}
echo cachePlan(true) . PHP_EOL;
echo cachePlan(false) . PHP_EOL;
// Prints:
// use cached value
// load from database and cache result
If a cache miss breaks the feature, the cache is being used as a source of truth.
Memcached vs Redis
Both Memcached and Redis can cache values, but they are not identical.
Memcached is a good fit when:
- The application needs simple temporary key-value caching.
- Losing cached values is acceptable.
- The team wants a small cache-specific service.
Redis may be a better fit when:
- The application also needs queues, locks, counters, sessions, or richer data structures.
- The framework already standardises on Redis.
- Operational tooling expects Redis.
Use the technology your project can operate safely, not only the one that looks faster in a benchmark.
Key Design
Use clear namespacing and include enough information to avoid collisions.
<?php
declare(strict_types=1);
function categoryPageCacheKey(int $categoryId, int $page): string
{
return 'catalog:category:' . $categoryId . ':page:' . $page;
}
echo categoryPageCacheKey(7, 2) . PHP_EOL;
// Prints:
// catalog:category:7:page:2
Avoid putting secrets or personal data directly into cache keys. Keys are often visible in logs or diagnostics.
Failure Behaviour
If Memcached is unavailable, many applications can bypass it and read from the database. That may be slower, but correct.
However, a cache outage can still overload the database if every request suddenly recomputes expensive data. Production systems need monitoring for cache hit rate, evictions, memory usage, and connection failures.
What To Check
Before moving on, make sure you can:
- Explain what Memcached is used for in PHP applications.
- Treat Memcached as cache, not source-of-truth storage.
- Design predictable cache keys.
- Use TTLs deliberately.
- Handle cache misses normally.
- Explain when Redis might be a better fit.
- Describe what should happen if Memcached is unavailable.
Design Around A Miss
Memcached stores opaque values under keys in distributed memory. Entries can disappear because their TTL expires, the server restarts, memory pressure evicts them, a node is removed, or the cache is flushed. The application must therefore remain correct when every lookup misses.
That requirement determines what belongs in Memcached. Product summaries, rendered fragments, rate-limit hints with an authoritative fallback, and expensive query results can be reasonable. The only copy of an order, password-reset token, account balance, or job is not. If losing the entry loses business state, it belongs in durable storage.
A typical cache-aside read is:
- derive a stable key from the requested object and representation version;
- attempt the cache lookup;
- on a hit, validate or decode the value and return it;
- on a miss, read the authoritative store;
- populate the cache with a bounded TTL;
- return the authoritative result even if the cache write fails.
Cache failure should normally increase latency, not change correctness. Timeouts must be short enough that an unavailable cache does not become a slower mandatory dependency.
Keys Are Part Of The Data Model
A key should identify the exact representation being cached. Include tenant or authorization scope when results differ by caller, the entity identifier, and a schema or representation version. Avoid raw personal data and secrets in keys because keys may appear in logs and diagnostic tools.
Memcached has key-length and character constraints, and client libraries may hash long keys differently. A common pattern is a readable prefix followed by a digest of normalized inputs. Document normalization: sorting a filter map, lowercasing a case-insensitive field, or preserving order can change whether two requests correctly share an entry.
Versioned prefixes make broad invalidation explicit. Changing product:v3:42 to product:v4:42 leaves old entries harmless until expiry. This costs temporary memory but avoids relying on a cluster-wide flush, which can cause a sudden database load spike.
TTL Is A Staleness Budget
A TTL is not only a performance setting. It states how long the application is willing to serve a cached value without checking the source. Choose it from the cost of staleness and the expected update rate. A marketing description may tolerate minutes; inventory availability near checkout may require a fresh database check regardless of what search or cache displays.
Memcached expiration has protocol details worth testing. Expiration values can be interpreted differently when they exceed the relative-time threshold, and server clocks matter for absolute times. Use client-library duration APIs where available and test the deployed server behavior instead of assuming every integer means seconds from now.
Adding random jitter to TTLs can prevent many related keys from expiring simultaneously. Jitter should remain within the accepted staleness budget. It is not a substitute for capacity planning.
Invalidation And Write Paths
The simplest update flow writes the database first and then deletes the affected cache entry. Deletion after commit prevents a cache miss from reading uncommitted state. There is still a race: another reader may miss before the commit, read the old value, and repopulate it after deletion. Short TTLs, versioned records, delayed second deletion, or event-driven invalidation can reduce this window, but each adds complexity.
Write-through caching changes the sequence but does not create a transaction across Memcached and a database. If one write succeeds and the other fails, reconciliation is required. For most PHP applications, cache-aside with clear staleness tolerance is easier to reason about.
Negative caching can protect the database from repeated requests for a missing object. Use a shorter TTL and a distinct encoded value so a cached absence is not confused with a transport failure or deserialization error. Invalidate the negative entry when the object is created.
Stampedes And Hot Keys
When a popular entry expires, many PHP workers can miss together and all execute the expensive regeneration. This cache stampede can overload the database precisely when the cache is least helpful.
Mitigations include probabilistic early refresh, a short regeneration lock, serving a stale value while one worker refreshes, and prewarming known hot keys. A lock also needs a TTL and failure policy; a worker can die while holding it. Callers that cannot acquire the lock should wait only briefly or use stale data if the product allows it.
One extremely popular key can overload a single cache node because consistent hashing sends that key to one destination. Measure per-key traffic where the client and server tooling permit it. Sometimes the correct repair is to avoid caching one huge aggregate, shard the representation, or compute a cheaper response.
Serialization And Compatibility
Memcached stores bytes. PHP clients may serialize arrays or objects automatically, but serialized class instances couple cache contents to class names and deployment versions. Prefer explicit JSON or another stable representation with a version field. Validate decoded types and treat malformed values as misses while recording a metric.
Never use unsafe deserialization for values an attacker might influence. Cache network access should be private and authenticated where the deployment supports it, but network controls do not make arbitrary object deserialization safe.
During a rolling deployment, old and new application versions may read the same cache. Add fields compatibly, tolerate unknown fields, or change the key version before deploying an incompatible representation. Rollback must also be able to read the remaining entries or deliberately avoid them.
Operations And Testing
Measure hit ratio by operation, not only cluster-wide. Also record lookup latency, timeouts, evictions, item age, connection failures, rejected writes, and fallback database load. A high hit ratio can coexist with poor performance if the misses are the most expensive requests or one hot key dominates traffic.
Test with Memcached unavailable, slow, empty, and evicting entries. Verify that reads fall back, writes continue when policy permits, and public responses remain correct. Use concurrent tests around invalidation and regeneration. Restart a cache node in staging and observe whether database pools and queue workers remain within capacity.
Capacity planning must include item overhead, replication assumptions in the client topology, maximum item size, and expected churn. Memcached does not provide durable replication semantics merely because multiple servers are configured; clients distribute keys among independent nodes.
After this lesson, you should be able to use Memcached as a disposable cache, design versioned and scoped keys, choose TTLs as staleness budgets, handle invalidation races and stampedes, deploy compatible serialized values, and verify that cache loss affects performance rather than correctness.
Practice
Practice: Design Memcached Behaviour
Write a small PHP example that models Memcached cache behaviour for a product lookup.
Requirements
- Build a predictable product cache key.
- Include a TTL.
- Model a cache hit and cache miss.
- Identify the database as the source of truth.
- Include a note about what should happen if Memcached is unavailable.
- Mention one reason Redis might be chosen instead.
Show solution
This solution models the cache flow without needing a Memcached server.
<?php
declare(strict_types=1);
function productCacheKey(int $productId): string
{
return 'catalog:product:' . $productId;
}
function productCachePlan(int $productId, bool $cacheHit, bool $memcachedAvailable): array
{
if (!$memcachedAvailable) {
return [
'key' => productCacheKey($productId),
'source' => 'database',
'action' => 'skip Memcached and load from the database',
'ttl_seconds' => null,
];
}
if ($cacheHit) {
return [
'key' => productCacheKey($productId),
'source' => 'memcached',
'action' => 'return cached product',
'ttl_seconds' => 600,
];
}
return [
'key' => productCacheKey($productId),
'source' => 'database',
'action' => 'load product, store in Memcached, then return it',
'ttl_seconds' => 600,
];
}
$examples = [
productCachePlan(123, true, true),
productCachePlan(123, false, true),
productCachePlan(123, false, false),
];
foreach ($examples as $example) {
echo json_encode($example, JSON_THROW_ON_ERROR) . PHP_EOL;
}
// Prints:
// {"key":"catalog:product:123","source":"memcached","action":"return cached product","ttl_seconds":600}
// {"key":"catalog:product:123","source":"database","action":"load product, store in Memcached, then return it","ttl_seconds":600}
// {"key":"catalog:product:123","source":"database","action":"skip Memcached and load from the database","ttl_seconds":null}
The database remains the source of truth. Redis might be chosen instead if the application also needs queues, locks, counters, or richer data structures.