Choosing Your Database
A landscape/decision note, not a ranking, and not a syntax reference (that's Part 12) — three categories of database, and the question each one actually answers well.
Three categories, three questions:
- OLTP (PostgreSQL, MySQL, SQL Server, Oracle) — "record this transaction correctly, right now, with many people writing at once."
- Cloud analytical warehouses (Snowflake, BigQuery, Databricks, Fabric, Redshift) — "scan and aggregate billions of rows, cheaply, without me managing a cluster."
- Embedded/local (SQLite, DuckDB) — "run real SQL with no server, for development or a genuinely small dataset."
- In-memory (Redis, Memcached, SAP HANA, Oracle TimesTen) — not a 4th category, an orthogonal property: "make one hot, frequently-read thing sub-millisecond fast." Redis/Memcached aren't SQL at all.
- Existing cloud commitment is usually the strongest real-world constraint — often stronger than any feature comparison.
Category 1 — OLTP Engines (PostgreSQL, MySQL, SQL Server, Oracle)
Row-oriented storage, normalized schemas, strong transactional guarantees (ACID), fast single-row reads/writes — the database behind an application. PostgreSQL is the closest thing to a modern default; Oracle's PL/SQL (Part 11) is still common in banking, telecom, and government systems.
Pros:
- ACID transactions guarantee correctness under concurrent writes — two people can't double-book the same seat, and a payment charge + inventory decrement either both happen or neither does.
- Fast single-row lookups/writes via primary-key indexes — fetching or updating one specific row stays fast even as the table grows.
- Referential integrity (foreign keys, constraints — Part 1) is enforced by the engine itself, not left to application code to get right every time.
- Decades of mature tooling, drivers, and operational knowledge — nothing here is exotic to hire for or operate.
Cons:
- Row-oriented storage makes "scan and aggregate 500M rows" slow and I/O-heavy — the wrong physical shape for wide analytical queries (Part 9).
- Vertical scaling has a real ceiling — a single write-heavy instance eventually needs read replicas or sharding, genuine engineering effort, not a config flag.
- Not built for ad hoc queries touching many columns across huge historical tables — that's a warehouse's job (Category 2).
Real scenario — why it wins: an e-commerce checkout. Each order needs its payment charge and inventory decrement to succeed or fail together (a transaction), thousands of shoppers may write concurrently, and each individual query touches a handful of rows (this one order, this one customer). PostgreSQL wins here because ACID guarantees plus fast single-row access is exactly the job description — a columnar warehouse, optimized for scanning many rows at once, would be both slower and needlessly expensive for this write-heavy, single-row access pattern.
Category 2 — Cloud Analytical Warehouses (Snowflake, BigQuery, Databricks, Fabric, Redshift)
Columnar storage, elastic/serverless compute, mostly-separated storage and compute (Redshift is the architectural exception — Part 12). This is what a BI tool or a scheduled dbt pipeline (Part 10) actually queries.
Pros:
- Columnar storage + elastic compute scan and aggregate billions of rows fast, without anyone provisioning or managing hardware.
- Storage/compute mostly separated (Redshift excepted) means query power scales independently of how much data is stored — and on BigQuery/Snowflake, you largely pay only when actually querying.
- Native integration with the analytics ecosystem — BI tools, dbt (Part 10), orchestration — built for that workflow, not bolted onto it.
Cons:
- Poor fit for high-frequency, low-latency single-row writes — an OLTP-style checkout flow here would be slow and often needlessly costly.
- Real cost risk: a badly written query can scan far more data (and money) than expected, with no query-plan visibility unless you check before running it (Part 9's cost note).
- Some proprietary SQL extensions don't port cleanly between platforms (Snowflake Scripting, BigQuery-specific functions — Part 12) — a real, if partial, lock-in tendency.
Real scenario — why it wins: a BI dashboard aggregating a year of sales across every region. It needs to scan hundreds of millions of rows, group by a dozen dimensions, and return in seconds when an analyst refreshes a Tableau/Power BI report. A cloud warehouse wins because its entire architecture — columnar storage, serverless/MPP compute — is purpose-built for "read a lot, write rarely." An OLTP engine would either time out or need to be over-provisioned well beyond what its transactional design is meant for.
Category 3 — Embedded / Local (SQLite, DuckDB)
SQLite (row-oriented, the engine behind Parts 1-8 of this series) and DuckDB (columnar, "SQLite for analytics," behind Part 9's pruning demo and Part 10's dbt project). Neither replaces a warehouse at production scale — but for local development or a dataset that fits on one machine, reaching for a cloud warehouse is often overkill.
Pros:
- Zero install, zero server — a single file (SQLite) or an in-process engine (DuckDB), running wherever the app or notebook itself runs.
- Genuinely fast at machine scale — DuckDB in particular rivals warehouse-grade analytical performance for data that fits on one machine.
- Ideal for CI test suites and local development — real SQL semantics, none of the setup cost or cloud account this entire series was built to avoid.
Cons:
- No concurrent multi-writer support at production scale — SQLite in particular locks the whole database file for a write.
- No built-in high availability, replication, or access control — not meant to survive a server dying or serve many simultaneous users.
- A genuine data-volume ceiling — doesn't scale past what fits on one machine's disk/memory.
Real scenario — why it wins: writing and testing this entire series' 14 notebooks. Every concept from JOINs to window functions to a real dbt project needed to run reproducibly on any machine, with zero setup and no cloud account or credit card required. SQLite/DuckDB win here because the job isn't "serve production traffic" — it's "verify the SQL is correct, fast, for free, anywhere" — and spinning up a warehouse account for that would add friction for zero benefit.
In-Memory Databases: A Different Axis Entirely
"In-memory" isn't a fourth category alongside the three above — it's an orthogonal property that cuts across all of them. An in-memory database keeps its working data resident in RAM instead of reading it from disk on every query, which is what makes SQLite's :memory: mode and DuckDB's default in-memory connection (both used throughout this series) so fast for small datasets. The same idea scales up in production: Redis and Memcached are the two most widely used in-memory stores by far, almost always deployed as a caching layer in front of a slower OLTP database or warehouse — a session store, a rate limiter, a leaderboard, or a cache of expensive query results. Purpose-built in-memory SQL engines exist too for when the whole workload needs that speed, not just a cache: SAP HANA and Oracle TimesTen are the enterprise names, built for sub-millisecond transactional and analytical SQL at scale.
Accuracy note: Redis and Memcached are not SQL databases — they're key-value stores with their own APIs. They come up constantly in "in-memory database" conversations, but they don't belong next to SQLite/DuckDB/HANA/TimesTen on a "which SQL engine" decision; they solve a different problem (a fast cache in front of a system of record) rather than replacing that system of record.
Pros:
- Sub-millisecond latency — no disk I/O in the read/write path, often 10-100x faster than the same operation against a disk-backed database.
- Very high throughput for simple, repeated lookups — exactly the shape of a cache hit or a session read.
- Simple mental model (for Redis/Memcached specifically) — mostly key-value gets and sets, easy to reason about and operate.
Cons:
- Volatility risk — data in RAM disappears on a crash or restart unless the specific product adds persistence (snapshotting, an append-only log, replication); never treat a cache as the system of record unless it's explicitly built to be one.
- Cost — RAM is priced far higher per gigabyte than disk, so an in-memory dataset that would be cheap to store on a warehouse gets expensive fast at scale.
- A hard ceiling — the dataset has to fit in the memory of the machine (or cluster) running it; this doesn't scale to "billions of rows" the way a disk-backed warehouse does.
Real scenario — why it wins: a product page showing "1,204 people viewed this today." Recomputing that count from the warehouse on every page load would be slow and expensive at any real traffic volume; incrementing a counter in Redis and reading it back is a sub-millisecond operation that can absorb enormous read traffic. Redis wins here because the job is "serve a hot, frequently-read number fast," not "be the durable record of every view event" — the warehouse or OLTP database still owns that.
The Decision Isn't Permanent
Many real architectures run more than one category at once — an OLTP engine feeding a warehouse via CDC (Orchestration & CDC). Don't default to "whatever's newest": an OLTP question answered with a warehouse (or vice versa) usually surfaces as a cost or performance problem months later, not immediately.
| Question | Points toward |
|---|---|
| Does this serve a live application's reads/writes? | An OLTP engine (Category 1) |
| Am I aggregating/analyzing large historical volumes? | A cloud warehouse (Category 2) |
| Am I developing/testing locally, or is the data genuinely small? | SQLite/DuckDB (Category 3) |
| Am I caching a hot, frequently-read value in front of a system of record? | Redis/Memcached (in-memory, not SQL) — keep the system of record elsewhere |
| Do I already have a dominant cloud provider? | Often decides the warehouse for you (BigQuery→GCP, Redshift→AWS, Fabric→Azure) — Snowflake and Databricks are the two credible multi-cloud choices |
| Does my organization already run Oracle/SQL Server for OLTP? | Worth knowing PL/SQL/T-SQL (Part 11) regardless of which warehouse sits alongside it |
Decision framing, not a ranking — the same query engine can be the right answer for one row and the wrong answer for another.
Match the database category to the question, not to what's newest.
The takeaway: Keep the three categories distinct in your own head even when a platform blurs them (Databricks and Fabric both increasingly do more than one job) — know which job a given query is actually doing.
Runnable code for this part: sql-for-data-platforms on GitHub — Module 13 of 14.
← Back to Publications