Postgres Is Enough More Often Than You Think: Stop Piling On Your Tech Stack

Postgres covers far more than you think—stop over-engineering your stack before real bottlenecks appear.
Many teams introduce Redis, Kafka, and Elasticsearch before they actually need them. This article explores how a single Postgres instance can replace queues, search, cache, and document storage, and lays out clear, data-driven criteria for when you genuinely need dedicated tools.
An Opinion That Hits a Nerve
Recently, a simple yet thought-provoking observation has been circulating in developer communities like Hacker News and Reddit: many teams rush to introduce additional databases, message queues, search engines, caches, and various services long before they actually need them. A page called postgresisenough.dev systematically maps out the boundaries of "Postgres is usually enough" versus "you genuinely need something else."
The reason this topic resonates so widely is that it touches on a pervasive problem in modern software engineering—designing architecture for a future scale that never arrives. As the original author put it: "I've seen too many tech stacks become hard to operate, not because the product needed that complexity, but because the architecture was preparing for scalability that never came."
This phenomenon has a proper name in engineering circles: Technology Sprawl—introducing more tools into your stack than the business actually requires. It's typically driven by overly optimistic projections of future traffic, a tendency to mimic "big tech architectures," or even engineers' enthusiasm for exploring new technologies (the latter sometimes jokingly called Resume-Driven Development). The YAGNI (You Aren't Gonna Need It) principle is the classic engineering philosophy that pushes back against this tendency.
The Historical Roots of YAGNI: YAGNI originated from the Extreme Programming (XP) movement, systematically articulated by Kent Beck and Ron Jeffries in the late 1990s. Its core insight is that software engineers tend to write code for "possible future requirements," but those requirements often never materialize, while the preemptive complexity continues to drain team resources. Technology sprawl is the architectural manifestation of a YAGNI violation—it's not about writing a few extra functions, but about introducing entire extra services. Frequently cited statistics show that most startups are slowed down by overly complex architectures before they ever hit real scaling bottlenecks—many even fold before the day they "need to scale" ever arrives.

Just How Many Scenarios Can Postgres Cover?
One Database Replaces Multiple Components
Postgres has long been more than just a relational database. With its continuous evolution of features, it can replace an entire suite of standalone middleware in many scenarios. To understand where this capability comes from, you need to understand PostgreSQL's architectural philosophy: PostgreSQL originated from the POSTGRES project at UC Berkeley in 1986, led by Michael Stonebraker. Its extensibility design (Extension System) is the fundamental reason it can carry so much functionality—PostgreSQL allows users to add new data types, index methods, functions, and operators without modifying the core.
A Deeper Look at the PostgreSQL Extension System: Unlike MySQL's plugin mechanism, PostgreSQL extensions can register entirely new data types, Index Access Methods, operator classes, and aggregate functions. These extensions are treated as full equals to the core at the query planner level—the optimizer can be aware of and leverage the statistics of extension indexes to generate optimal execution plans. This is precisely why pgvector's vector similarity search and PostGIS's spatial indexing can achieve near-native performance: they aren't application-layer wrappers bolted on top of the database, but first-class citizens deeply embedded in the query execution engine. This design philosophy enables extensions like pgvector and PostGIS to integrate deeply rather than merely act as add-ons, delivering performance close to native implementations. On this foundation, Postgres can replace an entire suite of standalone middleware in many scenarios:
- Message Queue: With
SELECT ... FOR UPDATE SKIP LOCKED, Postgres can implement a reliable task queue, and combined withLISTEN/NOTIFY, it can even do lightweight event push—eliminating the need for RabbitMQ or Kafka in many cases. - Full-Text Search: The built-in
tsvectorand GIN indexes are more than enough for small-to-medium search needs, so there's no need to deploy Elasticsearch right out of the gate. - Cache: For read-heavy, write-light scenarios with moderate data volumes, well-designed indexes combined with Materialized Views are often simpler and more reliable than introducing an extra Redis layer.
- JSON Document Storage: The
jsonbtype gives Postgres the flexibility of a document database, letting you handle many scenarios that would otherwise require MongoDB directly. - Geospatial, Time-Series, and Vector Search: Extensions like PostGIS, TimescaleDB, and pgvector further broaden its capability boundaries.
On How the Message Queue Works: SELECT ... FOR UPDATE SKIP LOCKED is a feature introduced in PostgreSQL 9.5, designed specifically for concurrent queue consumption. When multiple workers pull tasks simultaneously, each worker places an exclusive lock on its target rows, and the SKIP LOCKED clause causes it to automatically skip rows already locked, enabling conflict-free concurrent consumption and avoiding the performance bottleneck of traditional lock waiting. LISTEN/NOTIFY is Postgres's built-in lightweight publish/subscribe mechanism—when a new task is written to the database, a notification is triggered, and listening workers respond in real time. Combined, in scenarios where task throughput doesn't exceed tens of thousands per second, the performance rivals that of dedicated message queues.
On the Technical Details of Full-Text Search: tsvector is a preprocessed representation of text that breaks the original string into lexemes and performs stemming. Once a GIN (Generalized Inverted Index) is built on a tsvector column, each lexeme maps to the set of rows containing it, enabling O(log n) field-level queries. This mechanism supports boolean operations, phrase search, and relevance ranking (ts_rank). For business scenarios with a single table of millions of rows and relatively fixed search fields, the performance gap versus Elasticsearch is usually within an acceptable range, while completely eliminating the overhead of data synchronization and cluster maintenance.
On Materialized Views Replacing the Cache Layer: Materialized views persist query results to disk, and REFRESH MATERIALIZED VIEW CONCURRENTLY allows asynchronous refresh without blocking read operations, striking a balance between data freshness and query performance. For high-frequency read-only queries involving report aggregation or complex JOINs, materialized views essentially take on the role of caching, and since the data always comes from a single source, they naturally avoid the consistency problems between Redis cache and the database—which is precisely the most common engineering pain point of the Cache-Aside pattern.
The Cache-Aside pattern is the most common way Redis is used: the application first checks the cache, and on a miss, queries the database and writes the result back to the cache. This pattern inherently has a consistency window between the cache and database—there's a brief risk of data inconsistency between writing to the database and updating the cache. In high-concurrency write scenarios, you also encounter classic problems like "cache penetration" (a flood of requests hitting the database the instant a hot key expires) and "cache avalanche" (a surge of database pressure when many keys expire simultaneously), which require additional engineering measures (such as distributed locks, hot key sharding, and randomized expiration times) to counter. Materialized view refreshes, on the other hand, happen entirely within the database transaction framework, with no synchronization problem between two independent systems.
On the Difference Between JSONB and Document Databases: jsonb stores JSON in a binary format, completing parsing at write time so no re-parsing is needed at read time, and it supports GIN indexes for fast querying of arbitrarily nested key-value pairs. Compared to MongoDB, its core advantage is the ability to mix structured columns and semi-structured JSON fields in the same table, and to perform JOIN operations between them—something pure document databases struggle to achieve.
In other words, a single Postgres instance can often replace an entire combination of "database + queue + search + cache," while the operational cost is only that of one system.
Boring Infrastructure Is an Advantage
Debuggable, Monitorable, Backupable, Reasonable
There's a line in the original post worth chewing on repeatedly: "The longer I build and maintain systems, the more I appreciate boring infrastructure—easy to debug, easy to monitor, easy to back up, easy to reason about."
This is precisely the most underrated value of Postgres. It carries decades of engineering accumulation:
- Mature Tooling: From
pg_dumpbackups toEXPLAIN ANALYZEquery analysis, nearly every step has battle-tested tools. - Predictable Behavior: ACID transaction guarantees let developers reason deterministically about system state, rather than wrestling with distributed consistency problems.
- Vast Knowledge Base: Almost any problem can find an answer on Stack Overflow or in the official documentation, dramatically lowering the team's learning curve.
The Trade-off Between ACID Guarantees and Distributed Complexity: ACID refers to the four core guarantees of database transactions—Atomicity, Consistency, Isolation, and Durability. Single-node Postgres natively provides full ACID guarantees; developers simply wrap business logic in BEGIN/COMMIT without worrying about partial failures. Achieving similar guarantees in a distributed system requires introducing two-phase commit (2PC, which creates performance bottlenecks and single-point-of-failure risk at the coordinator node) or the Saga pattern (which requires manually writing compensation logic and can only guarantee eventual consistency rather than strong consistency), significantly increasing code complexity. The CAP theorem states that a distributed system cannot simultaneously guarantee Consistency, Availability, and Partition tolerance—which means choosing a distributed architecture is essentially trading engineering complexity for horizontal scalability. This trade is only worthwhile when a single machine truly cannot meet the requirements.
Every new component you introduce adds another service to monitor, another dataset to back up, and another failure mode to understand. Complexity isn't free—it continuously levies a tax on operations, troubleshooting, and team cognitive load. Each additional component increases MTTR (Mean Time To Recovery), because diagnosing problems requires crossing more systems; the onboarding cost for new team members also grows linearly with the complexity of the tech stack. Cognitive load itself has an upper limit—when engineers need to simultaneously understand Kafka's partitioning mechanism, Redis's memory eviction strategy, and Elasticsearch's shard routing, the cognitive resources available for understanding the business logic itself get heavily crowded out.
When Do You Actually Need "Something Else"?
Of course, Postgres is not a silver bullet. Acknowledging this is equally important. The following situations may genuinely require dedicated solutions:
- Massive-Scale Full-Text Search: When search involves complex relevance ranking, aggregation analysis, and enormous data volumes, dedicated engines like Elasticsearch still have their value.
- Extremely High-Throughput Real-Time Stream Processing: Kafka has an irreplaceable advantage in scenarios involving massive-scale event streams, multiple consumer groups, and long-term log retention.
- Ultra-Low-Latency Caching: When QPS reaches extreme levels and sub-millisecond response is required, an in-memory database like Redis is still the more suitable choice.
- True Horizontal Scale-Out Writes: A single Postgres node has a write ceiling; only when write pressure exceeds the limits of a single instance do you need to consider sharding or a distributed database.
The Scenario Where Kafka Is Truly Irreplaceable: Kafka's core design is a distributed log system—messages are persisted in partitioned logs via append-only writes, and consumers independently track their consumption progress via offsets. This allows the same message to be independently consumed by multiple consumer groups, and to be replayed from any historical position. When you need to distribute the same event stream simultaneously to multiple downstream systems like a data warehouse, notification service, and search index; or when you need event sourcing and audit logging; or when you need to process hundreds of thousands of messages per second—these are the real technical signals for introducing Kafka.
There's a subtle but important engineering detail worth noting here: Kafka consumers record consumption progress via offset commits, and this mechanism is completely decoupled from the business database's transactions. This means that a message being acknowledged as consumed by Kafka does not equate to the business logic having successfully executed and written to the database. In cases of network jitter or service crashes, you can end up with a message consumed from Kafka but a failed business write—the "Dual Write Problem"—requiring additional patterns like idempotent processing and the Transactional Outbox Pattern to guarantee end-to-end data consistency.
A Detailed Look at the Transactional Outbox Pattern: The Transactional Outbox Pattern is the classic engineering solution to the Dual Write problem—the "event to be sent" is written as a database record into an outbox table within the same business database transaction, and a separate Relay process (or a CDC tool like Debezium) polls the outbox table and pushes events to the message queue. This way, the business data write and event generation complete atomically within the same database transaction, completely eliminating the consistency window between two independent systems. This is also one of the most reliable engineering practices for guaranteeing "exactly-once" semantics when introducing a message queue like Kafka is unavoidable. By contrast, a Postgres queue's task state changes and business data writes can complete atomically within the same database transaction, naturally avoiding this problem—which is the most intuitive demonstration of the "single source of truth" architectural advantage in the realm of distributed reliability.
The key point is: these should all be "needs proven by data," not "choices driven by an imagined future."
How to Judge Where That Line Is
The original post ends with an open-ended question: how do you distinguish between "Postgres is enough" and "falling into the technology sprawl trap"? Drawing on community discussion, we can distill a few practical principles:
Start Simple, Evolve as Needed
By default, solve problems with Postgres, and only introduce new components when you hit a concrete, measurable bottleneck. Prematurely optimizing your architecture is often the biggest invisible killer of team productivity. Donald Knuth's famous line, "Premature optimization is the root of all evil," holds equally true at the architectural design level: introducing distributed components without real data to back them up incurs potential engineering debt that far exceeds the hypothetical problems they solve.
This principle is of a piece with the concept of Evolutionary Architecture. Systematically articulated by Neal Ford, Rebecca Parsons, and others in Building Evolutionary Architectures, its core idea is to use "fitness functions" to measure whether an architecture meets current business goals, and to treat architectural change as continuous iteration rather than a one-time grand design. Together with the notion of "Just Enough Architecture" in agile development, it forms the methodological foundation for combating over-engineering: architecture should evolve rather than be predetermined, and every introduction of new complexity should be triggered by a real business pain point.
Decide With Data, Not Intuition
Before introducing Redis, ask: has the current query latency truly become a bottleneck? Before introducing Kafka, ask: can the existing queue solution really not hold up? Let monitoring data answer these questions, rather than the inertial thinking of "that's how big tech does it." Building habits of benchmarking and profiling is a prerequisite for making rational architectural decisions. An effective practice is to set up "threshold criteria for introducing new components"—for example, only consider introducing a cache layer when P99 latency exceeds the SLA requirement and EXPLAIN ANALYZE has confirmed the problem cannot be solved through index optimization.
Calculate Total Cost of Ownership
The cost of each new service is not just the service itself, but also the hidden costs of operations, monitoring, backups, team learning, and troubleshooting. Engineering economics has a concept called Total Cost of Ownership (TCO), which applies equally to technology selection: the monthly cloud service fee for a Kafka cluster may be just the tip of the visible-cost iceberg. The real costs include engineers' hours debugging consumer lag, the on-call cost of handling partition rebalancing failures, and the training cost of getting newcomers to understand Kafka semantics. A solution that's "good enough" is often, in the long run, far less troublesome than a "theoretically superior" distributed one.
Conclusion
The essence of the "Postgres is enough" viewpoint is not to disparage other excellent technologies, but to remind us: architectural decisions should serve the real product needs of the present, not fantasies about future scale.
In the wave of chasing microservices, distributed systems, and all kinds of middleware, daring to choose "boring but reliable" infrastructure is itself a mark of mature engineering judgment. As countless systems' lessons show—the biggest risk most teams face has never been "insufficient scalability," but "excessive complexity." This judgment echoes the software engineering consensus that "Simplicity is the hardest design goal to achieve": true architectural skill lies not in how complex a system you can wrangle, but in clearly knowing which complexities are unnecessary and having the courage to reject them.
Next time, before reaching for a new component, ask yourself one question first: Is Postgres really no longer enough?
Key Takeaways
Related articles

From Chat to Agent: Automating Your Entire Business Workflow with AI Agents
Veteran AI practitioner Remy breaks down the leap from chat models to AI agents: how agents work, the three pillars of context, tools, and skills, MCP connections, and hands-on architecture to make you a 100x employee.

Understand Anything: The AI Skill That Turns Code into Interactive Knowledge Graphs
Understand Anything is a high-star open-source GitHub skill that runs static analysis on any codebase and generates interactive knowledge graphs. It supports Claude Code, Cursor, Copilot and other agents, letting engineers ask questions in natural language with path references.

Kimi K3 Released: How a 2.8 Trillion Parameter Open Model Reshapes AI Cost-Effectiveness
Moonshot AI unveils Kimi K3: a 2.8 trillion parameter, 1M context, natively multimodal open model. With KDA architecture and ultra-low cost, it rivals GPT-5.6 and Fable 5, redefining AI cost-effectiveness.