10. Caching, Queues, & Background Jobs

Decouple slow work, design idempotent tasks, and cache intentionally with safe invalidation.

Question: When would you use a task queue like Celery?

Answer: A task queue is used to offload long-running, resource-intensive, or asynchronous operations from the main request-response cycle of a web application. This improves API response times and application reliability.

Explanation: Common use cases include sending emails, processing images, calling slow third-party APIs, or running periodic jobs. Celery works with a message broker (like Redis or RabbitMQ) to manage a queue of tasks. Worker processes execute tasks in the background. Key features include automatic retries with exponential backoff, which is essential for building resilient systems.

from celery import Celery

celery_app = Celery(__name__, broker="redis://localhost:6379/0")

@celery_app.task(autoretry_for=(Exception,), retry_backoff=True, max_retries=5)
def send_email(to: str):
    # Logic to send email
    actually_send(to)

Question: What delivery guarantees do queues provide, and how do you design idempotent workers?

Answer: Most brokers provide at-least-once delivery. Make workers idempotent with idempotency keys, transactional outbox/inbox, and retry-safe side effects.

Explanation: Exactly-once is rare and expensive; idempotency is the pragmatic default.

Question: What are the common cache invalidation strategies?

Answer: Cache invalidation is the process of declaring cached data as invalid or stale. The three main strategies are write-through, write-around, and write-back.

Explanation:

  • Write-Through: Data is written to both the cache and the database at the same time. This ensures the cache is always consistent but adds latency to write operations.

  • Write-Around: Data is written directly to the database. The cache is only populated on a read miss. This is good for applications with few repeated reads of recently written data.

  • Write-Back (or Write-Behind): Data is written only to the cache. The cache then asynchronously writes the data to the database after a delay. This offers the lowest latency for writes but risks data loss if the cache fails before the data is persisted.

  • A simpler approach is using a Time-To-Live (TTL), where data is automatically evicted from the cache after a set period.

Question: What is a cache stampede and how do you prevent it?

Answer: A surge of concurrent recomputations on expiration. Prevent it with TTL jitter, refresh-ahead, request coalescing (single-flight), and per-key locks.

Explanation: Small randomness in TTLs avoids synchronized expirations; coalescing deduplicates concurrent misses.

Question: What is a Dead-Letter Queue (DLQ) and when to use it?

Answer: A DLQ stores messages that failed processing after retries so they can be inspected or replayed safely.

Question: How do you implement a distributed lock?

Answer: Use Redis SET key val NX EX ttl and store a unique value; release only if you still own it.