0

Cache — storing computed or fetched results to avoid repeating the work

Beginner5 min read·eng-20-002
interviewperformance

Concept

Cache — a storage layer that holds copies of expensive-to-compute or expensive-to-fetch data so future requests can be served faster from the copy rather than recomputing.

The caching trade-off: Speed vs freshness. Cached data can be stale. The question is always: "Can I tolerate serving data that's X minutes old?"

What to cache:

  • Database query results that don't change often (user counts, product lists, settings).
  • Rendered HTML fragments.
  • API responses from external services.
  • Session data.
  • Computed results (analytics, aggregates).

Cache layers in a PHP app:

  1. OPcache: Caches compiled PHP bytecode. Fast. Automatic. (See eng-20-005.)
  2. Application cache: Laravel's Cache:: facade. Redis or Memcached. Caches data.
  3. Database query cache: MySQL's query cache (deprecated in MySQL 8). Use Redis instead.
  4. HTTP cache: Browser cache (Cache-Control headers), CDN cache.
  5. Reverse proxy cache: Nginx or Varnish can cache full HTTP responses.

Cache invalidation: One of the two hard problems in computer science. When underlying data changes, the cache must be invalidated (cleared or updated). Strategies: TTL (time-to-live), event-based invalidation, write-through, cache-aside.

In Laravel: Cache::put(), Cache::get(), Cache::remember(), Cache::forget(). Supports Redis, Memcached, database, file, array drivers.

Code Example

php
<?php
// CACHE DRIVERS in Laravel (config/cache.php)
// 'default' => env('CACHE_DRIVER', 'redis')
// Options: redis, memcached, database, file, array (in-memory, resets per request)

// BASIC cache operations
Cache::put('user:1:profile', $user, now()->addHours(1));  // store for 1 hour
$profile = Cache::get('user:1:profile');                   // retrieve
$profile = Cache::get('user:1:profile', 'default value'); // with fallback
Cache::forget('user:1:profile');                           // delete
Cache::flush();                                            // clear all (careful in production!)

// CACHE-ASIDE pattern (most common) — check cache, if miss fetch from DB
public function getUser(int $id): User
{
    return Cache::remember("user:{$id}", 3600, function () use ($id) {
        return User::with('profile', 'preferences')->findOrFail($id);
    });
    // If 'user:1' is in cache → return cached, no DB query
    // If not → run the closure, cache the result for 3600 seconds, return it
}

// CACHE WITH TAGS — group related items for bulk invalidation
Cache::tags(['users', "user:{$userId}"])->remember('profile', 3600, fn() => User::find($userId));
Cache::tags(['users'])->flush(); // flush all user-tagged caches when users change

// WRITE-THROUGH — update cache when writing to DB
public function updateUser(int $id, array $data): User
{
    $user = User::findOrFail($id)->update($data);
    Cache::put("user:{$id}", $user, 3600); // update cache immediately
    return $user;
}

// HTTP CACHE — let browsers/CDNs cache responses
Route::get('/api/products', function () {
    $products = Cache::remember('products', 3600, fn() => Product::all());
    return response()
        ->json($products)
        ->header('Cache-Control', 'public, max-age=3600')
        ->header('ETag', md5($products->toJson()));
});