Filter concepts by levelShowing all levels.

System Design · Data Warehouse vs Operational Database

Data Warehouse vs Operational Database

Two workloads with opposite shapes, the engine choices each one justifies, and the rule for where a reporting query is allowed to run

  • OLTPmany small transactions, integrity in the database
  • OLAPfew long scans, aggregates, columnar storage
  • Estimate firstthe rule is not "never" — it is "not without a number"

1.OLTP: frequent small transactions and strong integrity

OLTP stands for online transaction processing, and it describes the workload your application generates: a high rate of small, short transactions, each touching a few rows, each expected to complete in milliseconds. A checkout, a login, a comment, a status change. Two things characterise it beyond size. First, the operations are transactional — several writes must succeed or fail together, and the database is expected to guarantee that even under concurrency and crashes. Second, integrity is enforced rather than assumed: foreign keys, unique constraints, check constraints and isolation levels exist so that invalid states cannot be stored, no matter which code path attempts it. Those guarantees are why an OLTP store is designed the way it is — row-oriented layout so a whole record is one read, B-tree indexes so a lookup by key is a seek rather than a scan, careful concurrency control so thousands of small transactions interleave safely, and durable write-ahead logging so a committed transaction survives a crash. Every one of those choices favours precision and latency over throughput on large scans, which is exactly the right trade for the workload and exactly the wrong one for analytics.

Key features

  • OLTP is a high rate of small, short transactions, each touching a few rows and expected to finish in milliseconds
  • Operations are transactional: several writes commit or roll back together, guaranteed under concurrency and crashes
  • Integrity is enforced by the database — foreign keys, unique and check constraints, isolation levels — so invalid states cannot be stored by any code path
  • Row-oriented storage suits it, because a request usually wants a whole record rather than one column across many records
  • B-tree indexes make lookups a seek rather than a scan, which is what keeps latency flat as the table grows
  • Write-ahead logging makes a committed transaction survive a crash, which is the durability half of the guarantee

What an OLTP store is optimised for

What an OLTP store is optimised for
Design choiceServesCosts
Row-oriented storageReading a whole record in one goReads unused columns during a scan
B-tree indexesConstant-ish lookup latency as data growsWrite amplification and index maintenance
Constraints and foreign keysInvalid states become unstorableA small per-write check
Isolation levels and row locksSafe concurrent interleavingContention on hot rows
Write-ahead loggingDurability across crashesA synchronous write per commit
The properties an OLTP workload depends on

Small and fast

a few rows, milliseconds

Atomic

several writes, all or nothing

Constrained

invalid states cannot be stored

Isolated

safe under concurrency

Durable

a commit survives a crash

  1. Small and fast — a few rows, milliseconds
  2. Atomic — several writes, all or nothing
  3. Constrained — invalid states cannot be stored
  4. Isolated — safe under concurrency
  5. Durable — a commit survives a crash

Command

sql
-- the shape of an OLTP unit of work
BEGIN;
  UPDATE accounts SET balance = balance - 5000
   WHERE id = $1 AND balance >= 5000;   -- 1 row
  UPDATE accounts SET balance = balance + 5000
   WHERE id = $2;                        -- 1 row
  INSERT INTO transfers (from_id, to_id, cents)
  VALUES ($1, $2, 5000);
COMMIT;
-- two rows changed, one row inserted, all or
-- nothing, in single-digit milliseconds

OLTP is a high rate of small, short, transactional operations with integrity enforced by the database rather than by the application. Row storage, B-tree indexes, isolation control and write-ahead logging all exist to serve that: per-operation correctness and low latency, guaranteed under concurrency and crashes. The same choices make large column-selective scans expensive, which is the trade the workload is meant to make.

2.OLAP: scans, aggregations and large datasets

OLAP stands for online analytical processing, and it describes a workload with the opposite shape to OLTP: a small number of long queries, each scanning a very large number of rows, reading a few columns from each, and producing an aggregate rather than a record. "Revenue by month for four years", "conversion rate by acquisition channel", "median session length by cohort". Individual rows are rarely interesting; the answer is a summary. That shape leads to different engineering everywhere. Storage is columnar, so a query reading two columns from a billion rows reads two columns' worth of bytes rather than every column of every row, and compresses well because values within a column are alike. Data is usually appended in bulk rather than updated in place, so the store is optimised for large sequential writes and rarely for single-row updates. Queries are expected to take seconds or minutes, so throughput matters far more than per-query latency, and work is parallelised aggressively across many machines. And integrity constraints are largely absent, because the data arrives from a pipeline that already validated it and enforcing constraints per row would cost more than it protects. Recognising which of the two workloads a query belongs to is the practical skill — it is what tells you whether a slow report is a tuning problem or a system-choice problem.

Key features

  • OLAP is a small number of long-running queries, each scanning many rows, reading few columns, and returning an aggregate rather than records
  • Columnar storage reads only the referenced columns and compresses far better, because values within one column are similar in type and range
  • Data arrives in bulk appends rather than single-row updates, so the store is tuned for large sequential writes
  • Seconds-to-minutes latency is acceptable, so the engine optimises throughput and parallelism rather than per-query response time
  • Constraints and foreign keys are typically absent — the pipeline validated the data, and per-row enforcement would cost more than it protects
  • Denormalised, wide tables are normal, because a join across billions of rows is expensive and storage is comparatively cheap

OLTP and OLAP side by side

OLTP and OLAP side by side
PropertyOLTPOLAP
Query shapeFew rows, most columnsMany rows, few columns
ResultRecordsAggregates
Latency targetMillisecondsSeconds to minutes
Write patternSmall transactional updatesBulk appends
Storage layoutRow-orientedColumnar
NormalisationNormalised, constrainedDenormalised, wide
Integrity enforcementIn the databaseIn the pipeline, upstream
What an OLAP engine optimises, in order

Columnar layout

read only the referenced columns; compress values that are alike

Bulk append writes

large sequential loads rather than single-row updates

Parallel execution

split the scan across many machines; throughput over latency

Denormalised wide tables

avoid joins across billions of rows; storage is cheaper than the join

  1. Columnar layout — read only the referenced columns; compress values that are alike
  2. Bulk append writes — large sequential loads rather than single-row updates
  3. Parallel execution — split the scan across many machines; throughput over latency
  4. Denormalised wide tables — avoid joins across billions of rows; storage is cheaper than the join

Command

sql
-- the shape of an OLAP query
SELECT channel,
       date_trunc('month', occurred_at) AS month,
       count(*)                        AS sessions,
       sum(revenue_cents) / 100.0      AS revenue
  FROM events
 WHERE occurred_at >= now() - interval '4 years'
 GROUP BY 1, 2
 ORDER BY 1, 2;
-- billions of rows scanned, four columns read,
-- a few hundred rows returned

OLAP is few queries, each scanning many rows and reading few columns to produce an aggregate. That shape justifies columnar storage, bulk appends, aggressive parallelism, denormalised wide tables and validation moved upstream into the pipeline rather than enforced per row. The practical skill is naming which workload a given query belongs to, because that determines whether a slow report is a tuning problem or a wrong-system problem.

3.Keeping heavy reporting off the transactional database

The rule is not "never run a report against the production database" — it is "never do it without knowing what it costs". A reporting query on an OLTP primary competes for the same finite resources every application query needs: buffer cache, disk bandwidth, CPU, connections. A large scan pulls pages into the cache and evicts the working set that keeps ordinary lookups fast, so the impact continues after the report finishes, as live queries re-read from disk what they used to find in memory. On a multi-version store a long-running read also holds a snapshot open, which prevents cleanup of old row versions and can make tables and indexes grow measurably during a long report. And a report that hits a connection limit takes capacity from the requests the business actually depends on. The resolution is a ladder, taken in order of cost. First, ask whether the query is genuinely heavy — a well-indexed report over yesterday's data may be trivial. Second, move it to a read replica, which removes competition with writes and with other readers on the primary but shares the same row-oriented layout and can lag. Third, move it to a warehouse fed by a pipeline, which is where genuinely analytical work belongs. Whatever you choose, the query needs a timeout and a resource limit, because the failure mode being prevented is one report degrading the entire product, and an unbounded query on a primary is exactly that risk left unmanaged.

Key features

  • A reporting query competes with live traffic for buffer cache, disk bandwidth, CPU and connections — all finite and all shared
  • A large scan evicts the working set from the cache, so ordinary queries stay slower after the report has finished
  • On a multi-version store a long-running read holds a snapshot open, delaying cleanup of dead row versions and growing tables and indexes
  • The ladder, in order: confirm the query is actually heavy, then a read replica, then a warehouse fed by a pipeline
  • A read replica removes competition on the primary and keeps the same row-oriented layout, so it helps with contention and not with scan efficiency
  • Whatever the target, give reporting queries a statement timeout and a resource limit, so one query cannot degrade everything

The ladder, in increasing order of cost and capability

The ladder, in increasing order of cost and capability
OptionRemovesStill costsUse when
Run it on the primaryNothingCache, CPU, connections, snapshot ageThe query is measurably small and time-bounded
Run it on a read replicaCompetition with writes and other primary readersRow-oriented scan cost; replica lagModerate reports that need near-current data
Run it in a warehouseCompetition entirely; scan cost drops with columnar storageA pipeline to build and run; freshness set by the pipelineGenuine analytical work over history

Guardrails that apply wherever the query runs

Guardrails that apply wherever the query runs
GuardrailPrevents
Statement timeoutOne query running for hours and holding resources
Separate credentials and connection poolReporting exhausting the application's connections
Read-only roleA reporting session writing to production data
Query cost estimate before schedulingDiscovering the cost during the incident
A stated freshness expectationSilent disagreement between a report and the application
Where a reporting query should land
smallmoderatelarge orhistoricalno estimate

A reporting query

Estimate its cost

rows scanned, columns read, runtime

Primary

only if measurably small, timeout enforced

Read replica

no competition with writes; still a row-store scan; lags

Warehouse

columnar, isolated, fed by a pipeline

Product degradation

what happens when nobody estimated

  • A reporting query
    • leads to Estimate its cost
    • on error, leads to Product degradation (no estimate)
  • Estimate its cost — rows scanned, columns read, runtime
    • leads to Primary (small)
    • leads to Read replica (moderate)
    • leads to Warehouse (large or historical)
  • Primary — only if measurably small, timeout enforced
  • Read replica — no competition with writes; still a row-store scan; lags
  • Warehouse — columnar, isolated, fed by a pipeline
  • Product degradation — what happens when nobody estimated

Command

sql
-- give reporting its own bounded budget
CREATE ROLE reporting LOGIN;
GRANT pg_read_all_data TO reporting;

ALTER ROLE reporting SET statement_timeout = '60s';
ALTER ROLE reporting SET idle_in_transaction_session_timeout = '30s';
-- plus its own connection pool, sized separately
-- from the application's, so a runaway report
-- exhausts only its own allocation

The rule is not "never report on production" but "never without estimating the cost". A large scan competes for cache, bandwidth, CPU and connections, evicts the working set so the slowdown outlasts the query, and on a multi-version store holds a snapshot that blocks cleanup. Take the ladder in order — confirm the query is small, then a replica, then a warehouse — remembering a replica fixes contention and not scan cost. And wherever it runs, give reporting its own read-only role, its own connection pool and a statement timeout.

Remember

  • OLTP: few rows with most of their columns, in milliseconds, with invalid states made unstorable by the database.
  • OLAP: many rows with few of their columns, in seconds or minutes, returning aggregates rather than records.
  • A large scan evicts the buffer cache, so the slowdown it causes outlasts the query that caused it.
  • A read replica fixes contention with the primary; it does not make a column-selective scan any cheaper.

Pro tip

  • Give reporting its own read-only role, its own connection pool and a statement timeout — a runaway dashboard should exhaust its own budget, not the application's.
  • Name which workload a slow query belongs to before tuning it; that answer decides whether tuning is even the right response.

Next up

  • Search vs Database
  • When a database index is the right tool
  • Keeping the system of record in the primary store

Sources

  • PostgreSQL — Multiversion Concurrency Control
  • PostgreSQL — Hot Standby
  • Google BigQuery — Storage overview
  • Checked 2026-08-28

Spotted a mistake on this page? Tell us.