Postgres, DynamoDB, Redis, Elasticsearch, Snowflake. We use all five for different workloads. The decision criteria, not the marketing comparison.
We run roughly five databases in production: Postgres (RDS), DynamoDB, Redis (ElastiCache), Elasticsearch / OpenSearch, and Snowflake. Each handles a slice of our workload that the others don't fit. After a few years of operating them, this is the working decision tree we use when a new service comes up and asks "what should I store this in?"
Most workloads start in Postgres unless there's a specific reason otherwise. The reasons:
We have ~30 Postgres databases (mostly RDS multi-AZ). They store user data, transactional data, anything that benefits from relational queries.
When Postgres isn't the answer:
DynamoDB is right for:
We use DynamoDB for:
What DynamoDB is bad at:
The DynamoDB modeling discipline matters. Single-table designs, GSIs (Global Secondary Indexes) for alternate access patterns, sort keys to encode hierarchy. Done well, DynamoDB scales beautifully. Done poorly (treated like a relational DB), it's expensive and slow.
Redis is right for:
We use Redis for:
What Redis is bad at:
We don't use Redis as a primary store. It always has a backing source-of-truth elsewhere (Postgres, DynamoDB) that can rebuild the cache.
Elasticsearch (or OpenSearch) is right for:
We use Elasticsearch for:
What Elasticsearch is bad at:
ES is a heavyweight piece of infrastructure. Operating it well is non-trivial. We've cycled through running it ourselves vs managed; managed is currently winning on operational simplicity even at the cost premium.
Snowflake is right for:
We use Snowflake for:
What Snowflake is bad at:
Snowflake is the OLAP side of the OLTP/OLAP split. Operational data goes to Postgres / DynamoDB; aggregated for analytics, it lands in Snowflake. We use Fivetran for most of the replication.
When a new workload comes up:
Most workloads end at step 1 or step 2. The other databases are for specific access patterns the first two don't fit.
Mistakes we've made (or seen):
Using Postgres for high-volume event ingestion. A service writing 50k events/sec to Postgres will have a bad time. Either move to DynamoDB or write to Kafka and aggregate.
Using DynamoDB as a relational DB. "I'll just add a GSI for every query I need." 6 GSIs later, costs explode and consistency becomes weird. If you're doing this, Postgres was the right answer.
Using Redis as a primary store. "We don't need persistence; we'll just cache it." Until the Redis goes down and you've lost data. Always have a source of truth.
Using Elasticsearch for transactional data. Not strongly consistent, not transactional. ES is for search and analytics, not for storing the canonical user state.
Using Snowflake for operational queries. A user-facing dashboard hits Snowflake; queries are 5-10s. Frustrated users. Cache aggregations to Postgres / DynamoDB for operational use.
Rough operational shape per database:
| DB | Managed cost | Ops time | Restore time |
|---|---|---|---|
| Postgres (RDS multi-AZ) | $$ | Low | Hours (point-in-time) |
| DynamoDB | $$$ | Very low | Minutes (PITR) |
| Redis (ElastiCache) | $$ | Low | Fast (rebuild from source) |
| Elasticsearch (managed) | $$$ | Medium | Slow (snapshot restore) |
| Snowflake | $$$$ | Very low | Time travel built in |
Cost-per-query / cost-per-record varies wildly. DynamoDB at high throughput is much cheaper per-query than Postgres scaled up. Snowflake at low query volume is much more expensive per-query than Postgres.
Migrations we've done:
Postgres → DynamoDB for a session storage system. Postgres was struggling at 30k writes/sec. DynamoDB handled it without breaking a sweat at lower cost.
DynamoDB → Postgres for a feature that grew query complexity. Started simple in DynamoDB; the queries got more complex over time; Postgres became the right fit.
Self-managed Elasticsearch → managed. Operational cost of running ES well wasn't worth the savings. Switched to AWS OpenSearch managed.
Custom analytics on Postgres → Snowflake. As our analytics scaled, Postgres queries got slow. Snowflake handles the scale; we use Fivetran to replicate.
Migrations are real work — typically weeks per service. Pick the right tool initially when possible.
Default to Postgres. Most workloads fit. Mature ops. Familiar query language.
DynamoDB when the access pattern is clear and stable. Single-table design, careful modeling.
Redis only with a source of truth. Never the only place your data lives.
Elasticsearch for search; Snowflake for analytics. Don't try to make Postgres do these at scale.
Be willing to migrate. When the access pattern outgrows the database, switch. Migrations are expensive but staying on the wrong tool is more expensive over time.
The right database is the one that fits the access pattern, scale, and consistency requirements. The wrong database is the one that fits "what we already use" without checking. Most teams don't really need 5 databases; we ended up with that many because each workload has different shape. A team starting fresh probably wants Postgres + Redis (caching) and adds the rest only as specific needs arise.
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
A real story of removing console-only changes, adding drift detection, and getting Terraform back in charge.
A field report from rolling out retrieval-augmented generation in production, including cache bugs, bad embeddings, and how we fixed them.
Explore more articles in this category
There are two hard problems in computer science." We've worked on the cache-invalidation one for a while. The patterns that hold up at scale and the ones that look clean and aren't.
We use Step Functions for batch processing, document ingestion, and a few agentic workflows. The patterns that work, the limits we hit, and where we'd reach for something else.
After two years of running Karpenter on production EKS clusters, the NodePool patterns that survived, the ones we replaced, and the tuning that matters.