Back to Practical SQL Queries
Q3Question 3 of 34

OLTP versus OLAP, and why not to run analytics on your OLTP database

Key Concepts
  • OLTP databases are row-oriented: all columns of one row sit together, optimized for fast single-row transactions.
  • OLAP databases are column-oriented: all values of one column sit together, optimized for scanning and aggregating few columns across billions of rows.
  • Row-oriented storage is poor at analytical scans because it reads all columns even when only one is needed.
  • Column-oriented storage is poor at single-row updates because one logical row is scattered across many column files.
  • Running heavy analytics on the OLTP database causes resource contention: CPU, memory, buffer cache, and lock competition.
  • The correct architecture separates OLTP and OLAP: writes and live reads on PostgreSQL, analytics on a columnar warehouse fed by CDC or ETL.
  • A read replica can handle moderate reporting, but true heavy analytics belong in a dedicated columnar warehouse.

Definition

OLTP serves fast single-row transactions; OLAP scans few columns across billions of rows: keep heavy analytics off your transactional database.
  • OLTP (Online Transaction Processing) – row-oriented stores (PostgreSQL, MySQL) optimized for many small, concurrent read/write transactions touching few rows.
  • OLAP (Online Analytical Processing) – column-oriented stores (Redshift, BigQuery, Snowflake) optimized for scanning and aggregating few columns across billions of rows.
  • Row-oriented storage – all columns of one row sit together on disk; fast for full-row reads and writes.
  • Column-oriented storage – all values of one column sit together; fast for aggregating one column, excellent compression.
  • Resource contention – analytics on the OLTP database compete for CPU, memory, buffer cache, and locks with live transactions.

OLTP is the busy teller counter at the National Bank of India: thousands of quick deposits and withdrawals, each touching one account, needing to be fast and correct. OLAP is the analyst in the back room who asks "what was the total deposit volume across all branches for the last five years?" If you force the teller counter to also run that giant report, the queue freezes while the machine chews through billions of rows. You keep the two jobs on separate systems.

Understanding the Concepts

What is OLTP?

OLTP (Online Transaction Processing) systems handle many small, concurrent transactions. Each transaction typically reads or writes a few rows. The database is row-oriented: all columns of one account sit together on disk.

SQL
-- OLTP: fast single-row transaction
BEGIN;
SELECT balance FROM accounts WHERE account_id = 'SAV2001' FOR UPDATE;
UPDATE accounts SET balance = balance - 25000.00
WHERE account_id = 'SAV2001';
COMMIT;
-- Completes in milliseconds, touches one row

What is OLAP?

OLAP (Online Analytical Processing) systems scan and aggregate large volumes of data. The database is column-oriented: all the amount values across millions of rows sit together on disk.

SQL
-- OLAP: aggregate across billions of rows
SELECT
    branch_id,
    DATE_TRUNC('month', txn_date) AS month,
    SUM(amount) AS total_deposits,
    COUNT(*) AS txn_count
FROM transactions
WHERE txn_date >= '2019-01-01'
GROUP BY branch_id, DATE_TRUNC('month', txn_date)
ORDER BY month;
-- Scans 5 years of data, reads only amount + txn_date + branch_id
-- Columnar store reads just those 3 columns, not all 50

How does the storage layout differ?

The core difference is physical storage layout. This determines what each system is good and bad at.

TEXT
Row-Oriented (OLTP):
  Row 1: [account_id, holder_name, balance, status, branch_id, ...]
  Row 2: [account_id, holder_name, balance, status, branch_id, ...]
  Row 3: [account_id, holder_name, balance, status, branch_id, ...]

  Good at: read/write one full row (all columns together)
  Bad at:  sum one column across millions of rows (reads all columns)

Column-Oriented (OLAP):
  account_id column: [SAV2001, SAV2002, SAV2003, ...]
  balance column:    [100000, 250000, 75000, ...]
  status column:     [ACTIVE, ACTIVE, FROZEN, ...]

  Good at: sum balance across millions of rows (reads only balance)
  Bad at:  update one row (balance is in one file, status in another)

Why is each bad at the other's job?

A columnar store is terrible at updating one account's balance because a single logical row is scattered across many column files. A row store is terrible at summing one column across billions of rows because it reads every full row.

JAVA
package com.bank.model;

/**
 * Why each storage layout fails at the other's job.
 */
public class StorageLayoutTradeoffs {

    // Row store scanning billions of rows for SUM(balance):
    // Must read: [account_id, holder_name, balance, status, branch_id, ...]
    // Needs only: balance
    // Wastes I/O reading 49 unwanted columns per row
    // No columnar compression to help

    // Column store updating one account's balance:
    // balance column file: find and update one value
    // status column file: if also changed, find and update there too
    // Each column is a separate file: one logical row = N file seeks
}

Why must you NOT run heavy analytics on the OLTP database?

Resource contention is the reason. A five-year aggregation reads a huge fraction of the table, evicting hot pages from the buffer cache, burning CPU, and holding long-running snapshots.

TEXT
What happens when you run analytics on OLTP:

  1. Analytical query scans 500M rows
  2. Hot account pages evicted from buffer cache
  3. Live transactions now hit disk instead of cache
  4. CPU and memory consumed by the analytical scan
  5. Long-running snapshot prevents vacuum from reclaiming space
  6. Connection pool fills up waiting for the slow query
  7. Result: customer-facing transactions slow to a crawl
SQL
-- This kills your production database:
SELECT branch_id, SUM(amount)
FROM transactions
WHERE txn_date >= '2019-01-01'
GROUP BY branch_id;
-- Scans 500M rows on the OLTP primary during business hours
-- Every customer transaction slows down

What is the correct architecture?

Separate OLTP and OLAP. Writes and live reads go to PostgreSQL. Data is replicated via CDC (Debezium + Kafka) or nightly ETL into a columnar warehouse for analytics.

DIAGRAM
Correct Architecture:

  ┌──────────────┐                ┌──────────────────┐
  │  Application │                │  Analytics Team  │
  │  (Tellers)   │                │  (Reports)       │
  └──────┬───────┘                └──────┬───────────┘
         │                               │
         ▼                               ▼
  ┌──────────────┐    CDC/ETL     ┌──────────────────┐
  │  PostgreSQL  │ ────────────►  │  Redshift /      │
  │  (OLTP)      │    Kafka       │  BigQuery (OLAP) │
  │  Row-oriented│                │  Column-oriented │
  └──────────────┘                └──────────────────┘
JAVA
package com.bank.config;

import javax.sql.DataSource;

/**
 * Separate data sources for OLTP and OLAP workloads.
 */
public class OltpOlapConfig {

    private final DataSource oltpDataSource;  // PostgreSQL
    private final DataSource olapDataSource;  // Redshift/BigQuery

    public OltpOlapConfig(DataSource oltp, DataSource olap) {
        this.oltpDataSource = oltp;
        this.olapDataSource = olap;
    }

    public DataSource forTransactions() { return oltpDataSource; }
    public DataSource forAnalytics()    { return olapDataSource; }
}

What about using a read replica for reporting?

A read replica of the OLTP database can handle moderate reporting queries. But true heavy analytics still belong in a columnar warehouse because the replica uses the same row-oriented storage.

DIAGRAM
Middle ground: read replica for moderate reports

  Primary (OLTP) ──replication──► Replica (moderate reports)
                                      │
                                      │ Still row-oriented
                                      │ Still reads all columns
                                      │ OK for moderate queries
                                      │ NOT OK for 5-year scans
                                      ▼
                               Warehouse (heavy analytics)
                                      Column-oriented
                                      Compressed
                                      Parallel scan

What should a senior engineer know?

A senior should know that the separation also lets each system scale independently and use the storage format suited to its workload. CDC with Debezium ensures near-real-time data freshness in the warehouse without touching the OLTP primary.

SQL
-- Debezium CDC pipeline:
-- 1. Debezium reads PostgreSQL logical replication
-- 2. Publishes row changes to Kafka topics
-- 3. Kafka consumer loads into Redshift/BigQuery
-- 4. Analytics team queries the warehouse
-- 5. PostgreSQL is never touched by analytics

-- The warehouse query that would kill OLTP:
SELECT branch_id, SUM(amount)
FROM warehouse.transactions
WHERE txn_date >= '2019-01-01'
GROUP BY branch_id;
-- Runs in seconds on columnar storage
-- Zero impact on production PostgreSQL

Visual Overview

DIAGRAM
┌──────────────────────────────────────────────────────┐
│          OLTP vs OLAP Storage Layout                 │
├──────────────────────────────────────────────────────┤
│                                                      │
│  ROW-ORIENTED (OLTP: PostgreSQL)                     │
│  ┌───────────────────────────────────────────┐       │
│  │ Row 1: [id, name, balance, status, ...]   │       │
│  │ Row 2: [id, name, balance, status, ...]   │       │
│  │ Row 3: [id, name, balance, status, ...]   │       │
│  └───────────────────────────────────────────┘       │
│  Fast: read/write one row (all columns together)     │
│  Slow: SUM(balance) reads all columns per row        │
│                                                      │
│  COLUMN-ORIENTED (OLAP: Redshift)                    │
│  ┌─────────────┐ ┌───────────┐ ┌──────────┐         │
│  │ id column   │ │ balance   │ │ status   │         │
│  │ SAV2001     │ │ 100000    │ │ ACTIVE   │         │
│  │ SAV2002     │ │ 250000    │ │ ACTIVE   │         │
│  │ SAV2003     │ │ 75000     │ │ FROZEN   │         │
│  └─────────────┘ └───────────┘ └──────────┘         │
│  Fast: SUM(balance) reads only the balance column    │
│  Slow: update one row across N column files          │
└──────────────────────────────────────────────────────┘

Key Takeaway

OLTP and OLAP differ fundamentally in how they store data on disk. OLTP databases like PostgreSQL are row-oriented: all columns of one account sit together, which is perfect for a transaction that reads or updates a single row quickly under heavy concurrency. OLAP databases like Redshift and BigQuery are column-oriented: all values of one column sit together and are heavily compressed, which is perfect for scanning and aggregating a few columns across billions of rows.

Each is deliberately bad at the other's job. A columnar store is poor at updating one account's balance because a single logical row is scattered across many column files. A row store is poor at summing one column across billions of rows because it reads every full row, dragging in all the unwanted columns.

You must not run heavy analytics on your production OLTP database because of resource contention. A five-year aggregation reads a huge fraction of the table, evicts hot pages from the buffer cache, burns CPU, and holds long-running snapshots. At the National Bank of India, the finance team ran a five-year monthly report directly against the PostgreSQL primary during business hours, and the teller application slowed to a crawl. Pankaj moved that workload into a Redshift warehouse fed by Debezium CDC, so analysts query the warehouse while PostgreSQL stays fast for customers.

The correct architecture keeps writes and live reads on the OLTP primary and replicates data into a columnar warehouse through CDC or batch ETL. Each system scales independently using the storage format suited to its workload.

Remember one sentence: OLTP serves fast single-row transactions and OLAP scans few columns over billions of rows, so keep heavy analytics off the transactional database.

---

Share
Use ← → arrow keys to navigate