Skip to content
← blog

Postgres vs Redis: when do you need each?

Start with Postgres for durable relational state. Add Redis when a measured workload needs shared low-latency coordination, caching, rate limits, streams, or queue semantics.

floo team

floo team

tl;dr

Most new applications should start with Postgres. Add Redis when a specific workload needs shared low-latency state, expiring keys, counters, rate limits, cache semantics, pub/sub, streams, or queue coordination that Postgres should not carry alone.

  • Postgres is usually the system of record. Transactions, constraints, relationships, and flexible queries fit durable business data.
  • Redis is not merely ephemeral. It offers persistence and can be a primary database in some architectures.
  • Do not add Redis on reputation alone. A second state system adds invalidation, consistency, credentials, monitoring, recovery, and cost.
  • Postgres can cover more than tables. It can support locks, notifications, queues, sessions, and computed state at modest scale.
  • Use both when their responsibilities are explicit. Postgres owns durable truth. Redis owns a named access or coordination path.

“Postgres or Redis?” is usually the wrong first question.

The right first question is:

What state does this application have, and what contract must that state satisfy?

Postgres and Redis overlap. Both can store application data. Both can persist writes. Both can support coordination patterns. The useful distinction is not “durable database versus temporary cache.” It is which system matches the workload's relationships, access pattern, latency target, expiration model, and failure semantics.

For most agent-generated business applications, the answer starts with Postgres alone.

start with Postgres when correctness is relational

Suppose an agent builds a customer-support application. It stores organizations, users, tickets, assignments, comments, and permissions.

Those records relate to one another. A ticket belongs to an organization. An assignment references a valid user. A comment belongs to an existing ticket. Several changes may need to commit together.

Postgres gives that state a useful default contract:

  • transactions for all-or-nothing changes;
  • foreign keys and constraints;
  • joins and flexible queries;
  • indexes for several access paths over the same records;
  • durable history that should survive process and cache replacement.

PostgreSQL describes a transaction as multiple steps bundled into one all-or-nothing operation. Its constraints can enforce valid values, uniqueness, primary keys, and relationships in the database rather than trusting every caller to repeat the rule correctly. The current constraints documentation shows those mechanisms.

That matters when an agent adds a second code path later. Database constraints continue to protect the invariant even if the new handler forgets a check.

what Postgres can do before Redis enters the stack

Postgres is not limited to long-lived rows and complex joins.

For a young application, it can also handle:

  • server-side sessions;
  • advisory locks;
  • job rows claimed with transactional locking;
  • notifications through LISTEN and NOTIFY;
  • materialized or precomputed results;
  • rate-limit counters at modest traffic;
  • JSON documents alongside relational records.

Keeping these responsibilities in one database can be the better architecture. It avoids a cross-system consistency boundary and lets one transaction create both a business record and the work that must follow it.

The key is not whether Redis would be faster in a benchmark. It is whether the application has measured a problem worth introducing another state system.

Redis is a database with different strengths

Redis is an in-memory data store with data structures and atomic operations designed for low-latency access and coordination. Its documented use cases include caching, session storage, rate limiting, job queues, pub/sub, streams, leaderboards, time series, vector search, and agent memory. Redis maintains current examples for these workload patterns.

Redis also supports several persistence choices:

  • point-in-time RDB snapshots;
  • append-only logging;
  • both mechanisms together;
  • no persistence for replaceable cache state.

Redis documents the durability and data-loss tradeoffs of each persistence mode. Calling all Redis state ephemeral is therefore wrong.

The better distinction is that Redis makes certain shared, low-latency state transitions natural. Postgres makes relational integrity and durable multi-record truth natural.

the decision table

requirementstart with Postgresconsider Redis
Durable users, orders, tickets, permissionsYesOnly with an intentional Redis-primary design
Multi-row transactions and foreign keysYesUsually not the center of the model
Ad hoc filtering and reportingYesNot its primary strength
Shared cache with TTLPossible, but not the natural fitYes
High-rate counters and rate limitsPossible at modest scaleOften a strong fit
Pub/sub where offline subscribers may miss messagesPossible with NOTIFY constraintsRedis Pub/Sub is a direct fit
Replayable event stream with consumer groupsPossible with tables and cursorsRedis Streams may fit
Queue with transactional coupling to business writesStrong when one Postgres transaction mattersRequires a cross-system handoff unless designed around it
SessionsOften enoughUseful for high-volume shared expiration
Leaderboards and ranked setsMore query workSorted sets are purpose-built

This table is a starting point, not a law. Workload evidence decides.

a concrete evolution from Postgres-only to Redis

Return to the support application.

stage 1: Postgres only

The app has a web service and Postgres. Ticket writes, assignments, search filters, and sessions all use the database. This is easy to inspect and recover.

stage 2: the workload changes

The application now serves thousands of agents in many browser tabs. Every request recomputes the same organization permissions. The primary database shows repeated reads dominating load. The result may be stale for 30 seconds without harming correctness.

Now Redis has a named job:

Cache organization-permission projections for 30 seconds, with Postgres remaining authoritative.

The application uses cache-aside:

  1. Read Redis by organization and permissions-version key.
  2. On a miss, read Postgres.
  3. Store the projection with a TTL.
  4. On a permission write, commit Postgres first and invalidate the relevant cache key.
  5. If Redis is unavailable, fall back to Postgres.

Redis describes cache-aside as a pattern where the application reads the cache first, loads from the primary on a miss, writes the result with a TTL, and invalidates after primary writes.

Redis is justified because the team can name the hot read, staleness tolerance, fallback, invalidation event, and success metric.

the cost of adding Redis

A second state system creates obligations that code generation can hide:

  • Which database is authoritative for each key?
  • Can the cache be stale, and for how long?
  • What invalidates a value?
  • What happens when invalidation fails?
  • Does the application continue when Redis is unavailable?
  • Which keys belong to each environment and application?
  • What is the eviction policy?
  • Which persistence and backup contract applies?
  • How are credentials rotated?
  • Can a preview read development or production keys?

If these questions do not have answers, the architecture gained ambiguity rather than capability.

queues and streams need sharper language

“Use Redis as a queue” is not a complete design.

Redis supports several message shapes with different semantics:

  • Pub/Sub broadcasts to currently connected subscribers and retains no history.
  • Streams retain ordered entries and can use consumer groups, acknowledgments, and replay.
  • A job queue implementation assigns tasks to workers, usually with retry and visibility behavior defined by the library or application.

Redis explicitly distinguishes Pub/Sub, streams, and job queues. The choice determines what happens when a consumer crashes, a message is delivered twice, or a worker is offline.

Do not write “exactly once” because one library makes duplicate processing unlikely. If a queue provides at-least-once delivery, the handler still needs idempotency or a deduplication boundary.

common mistakes from agent-generated code

Watch for these patterns:

  • Redis added to the template before a workload needs it;
  • user records written to both Postgres and Redis without one authority;
  • cache keys with no TTL or invalidation path;
  • authorization decisions served from an unversioned cache;
  • queue payloads that cannot be processed twice safely;
  • global key names shared across applications or environments;
  • a Redis outage converted into a total application outage even though Postgres has the source value;
  • a benchmark claim used instead of production latency and database-load evidence.

The safe default is boring: one source of truth, one explicit reason for every additional state path.

how this changes preview environments

Once an application has both Postgres and Redis, a preview needs more than a branch URL.

It needs to answer:

  • Which Postgres branch or schema does this preview own?
  • Which Redis database or key namespace does it own?
  • Can a worker from this branch consume another environment's messages?
  • Does teardown remove only preview-owned resources?
  • Can the agent identify those resources in structured output?

That is why production-shaped previews and shared staging matter. Adding a state layer adds an isolation obligation.

where floo fits

floo can start with the small shape: an application service and floo-managed Postgres in one environment model. If the workload later earns Redis, floo-managed Redis joins that application and environment rather than becoming an unrelated credential pasted into one service.

For previews, supported managed Postgres and Redis receive preview-owned identity and credentials. The application topology, route, source change, and managed attachments remain attributable to the same preview. The repository holds reviewable desired state, while secret connection values stay outside git.

The decision still belongs to the application team. floo does not make every app need Redis. It makes the chosen state topology legible to the agent and human operating it.

Start with Postgres. Add Redis when you can finish this sentence precisely: “We need shared low-latency state for ___, its authority is ___, staleness may last ___, and failure falls back to ___.” If that workload is part of a real app your agent is building, map its production environment with floo.

request access

name and email only. add a note if you'd like.

by submitting, you agree to our terms and privacy policy.