Data
Use Postgres until it actually hurts
Queues, search, caching, JSON, scheduled jobs. Postgres does all of it well enough for longer than you think, and every service you add is a service you operate.

The reference architecture people reach for by default now: Postgres for relational data, Redis for cache, a dedicated queue, a search service, maybe a document store because part of the data is unstructured. Five systems before the first customer.
Each of those is a genuinely good product. Together they're five things to deploy, monitor, back up, upgrade, secure, and reason about when something is slow at 2am. My default now is to start with one and add the second only when I can point at the specific pain.
The queue you probably don't need yet
Postgres has had SKIP LOCKED for years and it turns a table into a perfectly serviceable job queue.
UPDATE jobs SET status = 'running', claimed_at = now()
WHERE id = (
SELECT id FROM jobs
WHERE status = 'pending' AND run_after <= now()
ORDER BY run_after
FOR UPDATE SKIP LOCKED
LIMIT 1
)
RETURNING *;Multiple workers, no double processing, retries and scheduling are just columns. It comfortably handles thousands of jobs a minute, and you get something the dedicated queue can't give you: enqueueing a job in the same transaction as the data change that caused it.
That last point is worth more than it sounds. With a separate queue you can commit the row and fail to enqueue, or enqueue and fail to commit. In Postgres both happen or neither does. A whole category of 'the invoice exists but the email never sent' bugs simply never occurs.
Search, until it's really search
Full-text search in Postgres with a tsvector column and a GIN index is genuinely fine for finding a customer by name or a vehicle by registration. That's what most applications mean by search.
It's not a competitor to a real search engine once you need fuzzy matching, relevance tuning, faceting, and typo tolerance across millions of documents. But 'find the thing the user is thinking of' is not that problem, and reaching for a search cluster to power a customer lookup is how you end up operating a search cluster.
The cache is usually a missing index
Most of the times I've been asked to add Redis, the actual issue was a query doing a sequential scan or an N+1 in an ORM. Caching that makes the symptom go away and leaves the cause in place — plus now you have invalidation, which is its own permanent tax.
Read the query plan first. A properly indexed query returning in two milliseconds doesn't need a cache in front of it, and a cache in front of a slow query is a slow query that's now also sometimes stale.
Every service you add is a service you operate at 2am. Make it earn that.
When to actually add the thing
This isn't purity. I do reach for Redis — for genuine session and rate-limit workloads where the access pattern is wrong for a relational database and the data is disposable. That's a real reason. 'The reference architecture has it' is not.
- Add it when you can name the query or workload that's failing, with numbers
- Add it when the operational cost is smaller than the pain you're removing
- Don't add it because a scaling post said you'd need it at a size you aren't
JSONB is not a schema, but it is a good escape hatch
The other thing people spin up a second database for is semi-structured data — payloads that vary by customer or integration and don't fit neatly into columns. Postgres has handled that with JSONB for years, and you can index into it.
Where it goes wrong is when JSONB becomes the design rather than the exception. If a field is queried, filtered, or sorted on by every user, it wants to be a column with a constraint, not a key buried in a blob you can't validate. My rule: structured data in columns, genuinely variable data in JSONB, and any time a JSONB key shows up in a WHERE clause twice, promote it.
The systems I've been happiest to maintain years later were the boring ones. One database, understood well, indexed properly. It scaled further than I expected every single time, and when it eventually didn't, I knew exactly which part hurt — because there was only one place to look.