Redis

Redis is an in-memory data store with useful data structures: strings, hashes, lists, sets, sorted sets, streams. Being in memory makes it fast enough that its latency is usually negligible against everything else in a request.

The question it answers is not “how do I make things faster”. It is where state that is neither durable nor process-local should live. A relational database is the right home for data that must survive; process memory is the right home for data nobody else needs. Redis is for the gap between: shared across processes, and disposable.

That gap is larger than it first appears. Session data, rate-limit counters, task queues, caches, websocket presence, and the coordination state in a realtime application all fall in it — and the failure mode of putting them in PostgreSQL instead is writing rows nobody will read to a store built for durability.

The multi-process argument

The strongest case for Redis appears the moment an application runs more than one worker process. State in Python memory is per-process, so with several workers behind a load balancer, a cache is really N inconsistent caches and a rate limiter permits N times the intended rate.

These bugs are invisible in development, where there is one process, and appear in production, where there are several. Redis makes shared state genuinely shared, and that is usually the real reason to introduce it rather than raw speed.

It is also the standard broker for Celery, which is how it most often enters a stack.

Where the simplicity erodes

Persistence is a configuration, not a default guarantee. Redis offers snapshotting and an append-only log, and the default is a compromise permitting some data loss on crash. That is fine for a cache and not fine for a task queue, where losing the queue means losing work. The setting has to be chosen against what is being stored.

Eviction likewise. Redis holds everything in memory, and at the limit it either evicts or refuses writes depending on policy. A cache should evict; a queue must not. Running both under one policy means one of them is configured wrongly, which is a good argument for separate instances or at least separate databases.

It is not a database. No joins, no transactions in the usual sense, no schema. Data modelling means designing key structures by hand, and key naming becomes an informal schema that nothing enforces.

See also: Celery, websocket patterns, PostgreSQL for the durable half, and FastAPI.