OLAP vs OLTP Workloads

Imagine a retail company running one database for two very different jobs. When a shopper checks out, the system must record that single sale in milliseconds. It must never lose it. When a regional manager asks "what were total sales by region for every Tuesday in the last three years," the same database must scan billions of rows. It must answer in seconds, not days. These two demands — fast individual updates versus fast massive aggregation — pull database design in opposite directions. No single physical layout serves both well. This tension has a name: OLTP (Online Transaction Processing) versus OLAP (Online Analytical Processing). OLTP systems run the operational heartbeat of a business — orders, payments, inventory decrements, account updates. OLAP systems run the reflective side — dashboards, trend reports, forecasting queries, executive summaries. Confusing the two, or trying to force one engine to do both jobs at scale, is a common architectural mistake in real systems. Understanding why is essential before studying Data Warehousing Foundations or Star Schemas and Dimensional Modeling. The split matters because it drives concrete engineering decisions. These include which storage layout to use (row-oriented versus column-oriented), how to design indexes, and whether to normalize or denormalize a schema. They also include how to size hardware, and how to think about consistency and latency under load. It also explains why organizations maintain separate systems — an operational database plus a data warehouse. One feeds into the other through extract-transform-load (ETL) or streaming pipelines, rather than running analytics directly against production data. This article builds a precise vocabulary for contrasting OLTP and OLAP along three axes — access pattern, latency profile, and schema design. It connects each axis to concrete storage and indexing consequences. It builds on Distributed Database Fundamentals and Column-Family and Column Stores. It sets up the reasoning needed for dimensional modeling and warehouse architecture.

Key takeaways
  • OLTP handles many small, short transactions (inserts/updates on single rows); OLAP handles few large, long-running scans and aggregations.
  • OLTP favors row-oriented storage and normalized schemas; OLAP favors column-oriented storage and denormalized (often star) schemas.
  • Latency goals differ by orders of magnitude: OLTP wants millisecond response per transaction; OLAP tolerates seconds-to-minutes for whole-table analysis.
  • Indexing strategy flips: OLTP relies on B+-tree point-lookup indexes; OLAP relies on columnar compression, zone maps, and bulk scan techniques.
  • Mixing both workloads on one system causes resource contention — a classic reason organizations separate operational databases from data warehouses.
  • HTAP (Hybrid Transactional/Analytical Processing) systems attempt to unify both, at the cost of engineering complexity.
The core insight

OLTP optimizes for many fast row-level writes; OLAP optimizes for few fast column-level scans — and these two optimization goals require different storage layouts, indexes, and schemas.

The idea

OLTP (Online Transaction Processing) describes workloads dominated by short, atomic operations that read or write a small number of rows, typically identified by a primary key. A bank transfer, an e-commerce checkout, an airline seat reservation — each touches a handful of records and must complete with strict correctness guarantees (the ACID-style transaction properties: atomicity, consistency, isolation, durability). The defining metric is throughput of transactions per second combined with low per-transaction latency.

OLAP (Online Analytical Processing) describes workloads dominated by complex queries that read large fractions of a table (or many tables), aggregate over millions or billions of rows, and often join across several dimensions. A query like "average order value by month, region, and product category over five years" is typical. The defining metric is query throughput over huge data volumes, where a single query may legitimately take seconds to minutes, and the acceptable trade-off is that writes (data loading) happen in scheduled batches rather than continuously. The contrast is captured along three axes:

  • Access pattern. OLTP access is point-oriented: "fetch the row where `order_id = 5231`."

OLAP access is set-oriented: "scan all rows where `year = 2023` and compute `SUM(revenue)` grouped by `region`." OLTP touches a tiny fraction of the table per operation; OLAP touches most or all of a (typically much larger) table per query, but usually only a few columns of it.

  • Latency and concurrency. OLTP demands millisecond-scale response times under high

concurrency — thousands of transactions per second, each independent, each requiring strict isolation from the others. OLAP tolerates second-to-minute latencies for a handful of concurrent analytical queries, each one expensive but relatively rare compared to OLTP traffic.

  • Schema design. OLTP schemas are usually normalized (third normal form or close to

it) to avoid update anomalies and keep single-row writes cheap and consistent. OLAP schemas are usually denormalized, often organized as a star schema with a central fact table and surrounding dimension tables, because normalization forces expensive joins during large scans — a cost analytical queries cannot afford. These three axes are not independent; they are causally linked. A workload of frequent small writes (access pattern) demands low tail latency (latency profile), which in turn favors a schema and storage layout optimized for locating and updating single rows cheaply (schema design). A workload of infrequent giant scans favors the opposite chain of decisions. The rest of this article traces exactly how each choice follows from the others.

How it works

The clearest way to see why OLTP and OLAP diverge is to trace what happens physically when each workload executes a representative operation, and to see how the underlying storage engine must be shaped to make that operation efficient.

OLTP execution path. A transaction such as `UPDATE accounts SET balance = balance - 100 WHERE account_id = 42` needs to: 1. Locate the exact row for `account_id = 42` — ideally via a B+-tree index in O(log n) time. 2. Acquire a lock (or use multi-version concurrency control) on just that row. 3. Read the current value, apply the update, write the new value plus a log record. 4. Commit: flush the write-ahead log entry so the change is durable, then release the lock. This path is optimized when data is stored row-wise — all columns of a given row placed contiguously on disk/in a page — because a single I/O fetches everything needed to read or update that one row. Row-store engines (as used by traditional OLTP databases) are built around this exact access pattern.

OLAP execution path. A query such as `SELECT region, SUM(revenue) FROM sales WHERE year = 2023 GROUP BY region` needs to: 1. Identify which columns are actually needed: `region`, `revenue`, `year`. Ignore every other column in the table (which may have dozens of columns: customer id, product id, payment method, shipping address, etc.). 2. Scan only those columns across potentially billions of rows. 3. Apply a filter (`year = 2023`), then aggregate (`SUM`, grouped by `region`). 4. Return a small, summarized result. This path is optimized when data is stored column-wise — all values of a single column placed contiguously — because the engine can skip every column not mentioned in the query entirely, dramatically reducing I/O, and can apply strong compression since values within one column tend to be similar (see Column-Family and Column Stores). The pseudocode below sketches the essential difference in a query planner's cost reasoning when it must decide how to physically execute each type of workload. ``` function EXECUTE_OLTP_TRANSACTION(table, key, update_fn):

# Step 1: point lookup via B+-tree index -- O(log n) row_ptr = BTREE_INDEX_LOOKUP(table.index, key) if row_ptr is NULL: abort("row not found")

# Step 2: acquire row-level lock (or begin MVCC snapshot) lock = ACQUIRE_ROW_LOCK(row_ptr)

# Step 3: read-modify-write on ONE row (row-store layout: all columns contiguous) row = READ_ROW(row_ptr) new_row = update_fn(row) WRITE_AHEAD_LOG(row_ptr, new_row) # durability before commit WRITE_ROW(row_ptr, new_row)

# Step 4: commit COMMIT_LOG_FLUSH() RELEASE_ROW_LOCK(lock) return SUCCESS

function EXECUTE_OLAP_QUERY(table, needed_columns, filter_fn, group_by_col, agg_fn):

# Step 1: column pruning -- only touch columns actually referenced column_stores = [ COLUMN_STORE(table, c) for c in needed_columns ]

# Step 2 (optimization): skip whole blocks using zone maps / min-max metadata relevant_blocks = [] for block in ZONE_MAP_BLOCKS(table, filter_fn.column): if block.min <= filter_fn.bound <= block.max or OVERLAPS(block, filter_fn): relevant_blocks.append(block)

# Step 3: vectorized scan + filter + partial aggregation, block by block partial_results = {} for block in relevant_blocks: decompressed = DECOMPRESS(block) # column encodings: RLE, dictionary, etc. filtered = FILTER(decompressed, filter_fn) for row in filtered: key = row[group_by_col] partial_results[key] = agg_fn(partial_results.get(key, agg_fn.identity), row)

# Step 4: merge partials across parallel workers (this scan is embarrassingly parallel) final_result = MERGE_PARTIALS(partial_results) return final_result ``` The key structural difference is visible in the pseudocode itself: `EXECUTE_OLTP_TRANSACTION` is dominated by a single indexed lookup and a locking protocol on one row, while `EXECUTE_OLAP_QUERY` is dominated by a block-parallel scan across an entire column, with column pruning and zone maps used specifically to avoid touching irrelevant data.

Figure
Row-store vs column-store layout and the two workloads they serve
OLTP: Row Store optimized for point lookups & single-row writes Row 1: id=42, name="A. Ray", bal=500, city="Pune" Row 2: id=43, name="B. Ito", bal=220, city="Kobe" Row 3: id=44, name="C. Diaz", bal=990, city="Lima" Row 4: id=45, name="D. Wei", bal=150, city="Xi'an" B+-tree index on id → O(log n) lookup of ONE full row UPDATE accounts SET bal = bal-100 WHERE id=42 1 row locked, updated, logged — millisecond latency OLAP: Column Store optimized for scanning & aggregating one column id bal name city 42 43 44 45 500 220 990 150 scan ONLY bal SELECT SUM(bal) FROM accounts other 3 columns never touched — I/O reduced ~4× Row store: 1 disk page = full row (all columns) → cheap point writes Column store: 1 disk page = one column's values → cheap wide scans + compression

Complexity & correctness

OLTP complexity. A single transaction on an indexed table costs `O(log n)` for the index traversal (B+-tree depth is logarithmic in the number of rows `n`), plus O(1) amortized for the row read/write itself, plus a constant amount of log-writing for durability. Under `k` concurrent transactions, a well-designed OLTP engine achieves throughput close to linear in available cores/disks, bounded by lock contention on hot rows. The correctness requirement is the strongest in the field: the ACID properties must hold under arbitrary interleavings of concurrent transactions. This is normally proved via serializability: a concurrency-control protocol (two-phase locking, or multi-version concurrency control) is correct if every allowed schedule is conflict-equivalent to some serial (one-at-a-time) execution of the same transactions. The proof sketch for two-phase locking is the classic one: if every transaction acquires all its locks before releasing any (the "growing" phase precedes the "shrinking" phase), then the precedence graph over transactions (an edge `Ti → Tj` whenever `Ti` releases a lock that `Tj` then acquires on a conflicting item) is guaranteed acyclic, and an acyclic precedence graph is exactly the condition for conflict-serializability.

OLAP complexity. A full-table aggregation query costs `O(n)` in the number of rows touched, but the constant factor is what matters practically: a column store touches only the `c` columns referenced by the query rather than all `C` columns in the table, giving an I/O reduction factor of roughly `C / c`. With zone maps (per-block min/max metadata) and predicate filters, entire blocks can be skipped without decompression, often reducing effective work far below `O(n)` in practice — though the worst case (no selective filter) remains `O(n)`. Aggregation across a partitioned/parallel column store parallelizes almost perfectly: with `p` workers scanning disjoint blocks, wall-clock time approaches `O(n/p)` plus a cheap `O(p)` merge step, because summation and grouping are associative and can be computed as partial aggregates and combined. Correctness here is simpler than OLTP's: OLAP queries are typically read-only against a (mostly) static snapshot loaded in batches, so there is no concurrent-write interleaving to reason about — the correctness question reduces to "does the query plan compute the mathematically correct aggregate over the scanned rows," which is a matter of query-plan equivalence (e.g., pushing filters below joins preserves the relational algebra's semantics) rather than a concurrency proof.

Honest tradeoffs. Row stores make single-row I/O cheap but waste I/O on wide scans (every column is dragged along even if unused). Column stores make scans and compression cheap but make single-row point updates expensive, since one logical row's values are scattered across many separate column files — appending or updating one row may require touching every column file. This is precisely why OLTP systems do not adopt column stores for their primary store, and why OLAP systems do not run their reporting queries against a live, row-oriented, highly-normalized production database: each storage layout is asymptotically and constant-factor optimized for one access pattern and penalized on the other.

Worked example

Consider a simplified `orders` table with 4 columns (`order_id`, `customer_id`, `amount`, `order_date`) and suppose it holds 100 million rows, stored in 8 KB pages.

OLTP scenario: "Update the amount of order #58213901 due to a discount."

StepRow store (used in production OLTP)Column store (hypothetical, for contrast)
Locate rowB+-tree lookup, ~`log2(100,000,000) ≈ 27` comparisonsSame index cost for the row's position
Fetch data1 page read fetches full row (all 4 columns together)4 separate page reads, one per column file
Apply updateIn-place write of 1 pageMust update 1 value inside a compressed column block — may require decompress/recompress of the whole block
Approx. cost~1 I/O + log write~4+ I/Os, plus recompression overhead

The row store wins decisively here — this is why OLTP engines use row-oriented layouts.

OLAP scenario: "Total order amount per month, for orders in 2023."

StepRow store (hypothetical, for contrast)Column store (used in production OLAP)
Columns needed`amount`, `order_date` (2 of 4)same 2 columns
I/O if row storeMust read all 4 columns for every row that might match → reads 100% of table bytesReads only the `amount` and `order_date` column files → ~50% of table bytes, before compression
FilteringRow-by-row date check, no block skipping without extra indexingZone maps on `order_date` blocks let the engine skip blocks entirely outside 2023
CompressionRow store gets little benefit (mixed data types per page)`order_date` and `amount` columns compress well (sorted/dictionary-encoded), often 5–10×
Approx. relative cost100 units of I/O (baseline)roughly 50 / 10 (compression) ≈ 5 units of I/O after zone-map skipping and compression

Here the column store wins by roughly an order of magnitude or more, which is exactly why data warehouses built for this scenario use columnar storage (foreshadowing the star-schema fact/dimension design covered in Star Schemas and Dimensional Modeling, where the fact table — like this `orders` table — is precisely the table subjected to these large scans). The two scenarios use the same table but demand opposite physical layouts — this is the concrete reason production systems typically maintain a row-oriented OLTP database and a separate column-oriented OLAP warehouse, connected by a batch or streaming pipeline that periodically copies and reshapes the data.

Common pitfalls & traps

PitfallWhy it happensConsequence
Running heavy analytical reports directly on the production OLTP databaseSeems simpler than building a warehouseLong-running scans hold locks / consume I/O, starving live transactions, causing checkout failures or timeouts
Fully normalizing a data warehouse schema like an OLTP databaseHabit from OLTP schema design (avoid redundancy)Forces many joins per analytical query, destroying scan performance; motivates denormalized star schemas instead
Assuming a column store is always fasterColumn stores excel at wide scans, not point lookupsSingle-row updates or high-frequency small transactions on a column store can be far slower than on a row store
Ignoring index cost on OLTP tablesMore indexes seem to only help readsEvery additional index adds write amplification, slowing down inserts/updates — a core OLTP cost
Treating batch ETL latency as "real-time"ETL jobs run periodically (e.g., nightly)Warehouse data can be stale; users may draw conclusions from out-of-date numbers, mistaking OLAP for OLTP freshness
Choosing HTAP naively to "avoid the ETL problem"Seems like a free lunch — one system, no pipelineHTAP systems must engineer around the row/column tension internally, often trading off peak OLTP or OLAP performance versus specialized systems

Pioneers & history

The OLTP/OLAP distinction crystallized as businesses' operational databases grew large enough that reporting queries against them became a performance liability, and as relational theory matured enough to support both transactional guarantees and large-scale analytical modeling as distinct, well-defined disciplines.

Origins · 1 minEdgar F. Codd and how this was discovered
EF
Edgar F. Codd
1923–2003 · Formulated the relational model and later coined "OLAP" to describe analytical query requirements distinct from transactional access

Codd's 1970 paper on the relational model gave databases the mathematical foundation — relations, normalization, and relational algebra — on which both transactional and analytical query processing are built. In 1993, Codd (working with Arbor Software) published a paper defining OLAP and proposing rules distinguishing it from standard transactional database use, formalizing the vocabulary the field still uses.

History
1970
— Codd publishes the relational model, establishing normalization and relational algebra.
1985
— Business intelligence and decision-support systems emerge as distinct commercial categories from OLTP databases.
1993
— Codd coins "OLAP" and proposes criteria distinguishing analytical from transactional processing.
1996
— Ralph Kimball's dimensional modeling methodology popularizes the star schema for data warehouses.
2000
s — Column-oriented database engines (C-Store and successors) demonstrate large scan speedups for OLAP workloads.
2010
s — HTAP (hybrid transactional/analytical processing) systems emerge, attempting to serve both workloads from one engine.

Where this matters

Every large e-commerce, banking, or telecommunications company runs this split in practice. The operational database — often a relational OLTP engine — processes purchases, transfers, and account changes with strict durability and low latency. Periodically (nightly, hourly, or via continuous streaming), that data is extracted, transformed, and loaded into a separate analytical store: a data warehouse or column-oriented OLAP engine, often organized with a star schema as covered in Star Schemas and Dimensional Modeling. Business intelligence dashboards, fraud-detection models, and executive reporting all query the OLAP side, never the live transactional system, precisely to avoid the resource contention described above. Modern cloud data platforms explicitly market themselves along this divide: managed relational databases advertise OLTP throughput and latency guarantees, while managed data warehouses advertise scan speed over petabytes and cost-per-query for analytics. Streaming platforms sit in between, feeding OLTP change events into OLAP stores in near-real time, narrowing (but never eliminating) the staleness gap between "what just happened" and "what the dashboard shows." Understanding this workload split is the foundation for designing any system that must serve both operational and analytical needs — the very problem Data Warehousing Foundations takes up next.

Ask the tutor: How would you design a system that supports both fast transactions and fast analytics with minimal staleness between them?

Can a single database system handle both OLTP and OLAP well?

Some systems, called HTAP (Hybrid Transactional/Analytical Processing) systems, attempt this by maintaining both row-oriented and column-oriented representations internally, or by using in-memory techniques to serve both patterns from one engine. They reduce the need for separate ETL pipelines and shrink the staleness gap between operational and analytical data. However, they typically involve significant engineering complexity, and in practice often still trail specialized OLTP or OLAP systems on their respective peak workloads. Most large organizations still separate the two physically, using HTAP selectively where staleness or pipeline complexity is the dominant pain point rather than raw peak performance.

Why can't we just add more indexes to an OLTP database to make analytical queries fast?

Indexes accelerate point lookups and range scans on specific columns, but analytical queries typically aggregate over columns and rows using patterns that indexes cannot fully anticipate, and every additional index makes writes slower, since each index must also be updated on every insert or update. OLTP systems are tuned to keep write amplification low, which directly conflicts with adding many indexes to support ad hoc analytics. Column stores solve the scan problem structurally, through layout and compression, rather than through more indexes, which is why OLAP systems favor that architecture instead.

What does "denormalized" mean in the context of a data warehouse schema?

Denormalization means intentionally storing redundant or repeated data to avoid joins at query time. A classic example is a star schema, where a large fact table (e.g., sales transactions) references smaller dimension tables (e.g., customer, product, date), but the dimension tables themselves are kept flat rather than further normalized into sub-tables. This trades some storage redundancy and update complexity for dramatically simpler and faster analytical queries, since the query engine can join a large fact table against a few small dimension tables rather than many deeply normalized ones.

Does OLAP mean the data is always stale compared to OLTP?

In classic batch-ETL architectures, yes — data is loaded into the warehouse on a schedule (nightly, hourly), so OLAP queries reflect a snapshot from the last load rather than the current instant. Streaming ETL and change-data-capture pipelines narrow this gap to seconds or minutes, and some HTAP systems approach near-real-time consistency between the two sides. But there is almost always some lag, because continuously merging live transactional writes into a scan-optimized columnar structure has real computational cost, and most systems choose to batch that cost rather than pay it on every single write.

Why does the same query run at O(log n) in one context and O(n) in another?

A point lookup on an indexed key runs in O(log n) because a B+-tree index narrows the search space by a constant factor at each level, giving logarithmic depth in the number of rows. An aggregation query like SUM or GROUP BY, by contrast, must in principle examine every row that could contribute to the result, since there is no way to know the answer without touching the relevant data — this is inherently O(n) in the number of qualifying rows. Zone maps and compression reduce the constant factor and sometimes the effective n (by skipping whole blocks), but they do not change this fundamental asymptotic difference between targeted lookups and full aggregations.

How does this relate to column-family and column stores covered earlier?

Column-family and column stores describe the physical storage layout — grouping values by column rather than by row. OLAP workloads are the primary reason that layout exists: since analytical queries touch few columns but many rows, storing data column-wise lets the engine skip irrelevant columns and compress similar values efficiently. OLTP workloads, needing fast access to whole rows, instead favor row-oriented storage. The OLTP/OLAP distinction is the workload-level "why," while column-family stores are one major storage-level "how" that follows directly from the OLAP side of that distinction.

Is it accurate to say OLTP is "write-heavy" and OLAP is "read-heavy"?

It's a reasonable first approximation but imprecise. OLTP is dominated by many small, transactional read-modify-write operations on individual rows, with strict latency and durability requirements on every single one. OLAP is dominated by large, read-only scans and aggregations, with writes happening in bulk, infrequent batches (loads) rather than continuously. So the more precise contrast is not simply "write vs. read" but "many small transactional operations under concurrency" versus "few large bulk scans over mostly static data," which is why access pattern, not just read/write ratio, is the defining axis.

Before this

Where this leads