Phase 5 · Professional ApplicationsModule 34~52 min read

Cache, Queues & Background Work

Improve responsiveness and resilience with caches, background jobs, retries, idempotency, scheduling, and email.

What you'll learn

Improve response time without trading away correctness. Cache derived reads with an invalidation policy and move slow work to retryable, observable jobs designed for duplicate delivery.

By the end of this lesson, you'll be able to:

  • Implement cache-aside safely
  • Design an idempotent background job
  • Choose retry, dead-letter, scheduling, and observability policies

Core mental model

Professional PHP is less about memorizing APIs and more about choosing a clear boundary for each responsibility. Use this table as a decision guide while reading the examples.

ConceptWhat it protectsDecision rule
Cache-asideFaster repeated readsCache derived data with TTL and invalidation ownership.
At-least-once deliveryDurable queuesAssume a job may run more than once.
Idempotency keyDuplicate-effect defensePersist completion under a stable operation identity.

Professional workflow

Build the feature in small, verifiable steps. Each step leaves the system in a state you can test.

  1. Describe the asynchronous workload boundary: its inputs, outputs, invariants, and expected failures.
  2. Implement the smallest happy path behind an explicit contract.
  3. Add validation and translate low-level failures into language the caller understands.
  4. Exercise the boundary with realistic data, then inspect output, logs, and resource cleanup.
  5. Refactor only after behavior is protected by a repeatable check.

Make the boundary visible

Name inputs, outputs, side effects, and failure cases before adding framework or infrastructure code. That habit keeps advanced PHP understandable as the application grows.

Guided code lab

Use cache-aside

The repository remains authoritative; the cache accelerates a named view with a bounded lifetime.

CachedCatalog.php
<?php
final readonly class CachedCatalog
{
    public function __construct(private Cache $cache, private CourseRepository $courses) {}
    public function featured(): array
    {
        return $this->cache->remember(
            'catalog:featured:v2',
            ttlSeconds: 300,
            compute: fn (): array => $this->courses->featured(),
        );
    }
    public function invalidateFeatured(): void { $this->cache->delete('catalog:featured:v2'); }
}

Make a job duplicate-safe

A unique operation key turns redelivery into a no-op after success.

SendWelcomeEmail.php
<?php
final readonly class SendWelcomeEmail
{
    public function __construct(private JobLedger $ledger, private Mailer $mailer) {}
    public function handle(int $userId): void
    {
        $key = "welcome-email:$userId";
        if ($this->ledger->completed($key)) return;
        $this->mailer->sendWelcome($userId);
        $this->ledger->markCompleted($key);
    }
}

Production practice

Contract

Jobs carry identifiers, not serialized domain graphs; handlers reload current state and record outcomes.

Verification

Run handlers twice, force transient failures, expire caches, and assert retries do not duplicate external effects.

Operations

Measure queue depth, oldest-job age, attempts, failures, dead letters, cache hit ratio, and evictions.

Common failure mode

Retrying every exception can amplify permanent failures. Classify transient errors, cap exponential backoff, and dead-letter jobs that need human action.

Independent workshop

Add cached dashboards and queued report emails to the content application while preserving correctness under redelivery.

Your finished workshop must include:

  • Versioned cache keys and invalidation plan
  • Idempotent job plus retry policy
  • Worker metrics and dead-letter recovery runbook

Definition of done

Run the happy path and at least two failure paths, explain one design tradeoff in a short README, and leave the code formatted and ready for review.

Recap & quick check

Key takeaways

  • The database remains the source of truth.
  • Cache invalidation is part of feature design.
  • Queue delivery may repeat.
  • Async systems require observable lifecycle states.

Quick check

1. What must a queue consumer assume?

2. What does a cache TTL provide?

3. Which metric reveals growing delay?

Keep the workshop: later phases deliberately build on these boundaries, so today's small example can become part of your portfolio architecture.