OLAP vs OLTP Workloads

DataDatabaseAnalyticsArchitecture
Share on LinkedIn

The checkout API p99 jumped from 80ms to 2.4 seconds after BI connected Metabase directly to production Postgres and ran SELECT date_trunc('day', created_at), sum(total) FROM orders GROUP BY 1 during Black Friday. Same database, same tables — completely different workload shapes. OLTP optimizes for short, indexed reads and writes; OLAP optimizes for scanning millions of rows and aggregating. Treating them as interchangeable is how transactional systems die quietly under dashboard load.

Defining the two workload types

Dimension OLTP (Online Transaction Processing) OLAP (Online Analytical Processing)
Primary ops INSERT, UPDATE, DELETE, point SELECT SELECT aggregates, scans, joins
Query pattern Short, indexed, high QPS Long-running, batch, low QPS
Data freshness Current state, milliseconds Historical, often delayed
Consistency ACID required Eventual OK for reports
Users Applications, APIs Analysts, dashboards, ML
Schema Normalized (3NF) Denormalized (star/snowflake)

OLTP answers: "What's order #847291's status right now?" OLAP answers: "What was revenue by region and product category for the last 36 months?"

OLTP design characteristics

Normalized schema reduces update anomalies:

-- OLTP: orders + order_items (3NF)
CREATE TABLE orders (
  id          BIGINT PRIMARY KEY,
  user_id     BIGINT NOT NULL REFERENCES users(id),
  status      TEXT NOT NULL,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE order_items (
  order_id    BIGINT REFERENCES orders(id),
  product_id  BIGINT REFERENCES products(id),
  quantity    INT NOT NULL,
  unit_price  NUMERIC(12,2) NOT NULL,
  PRIMARY KEY (order_id, product_id)
);

CREATE INDEX idx_orders_user_created ON orders(user_id, created_at DESC);

Properties:

Typical engines: PostgreSQL, MySQL, CockroachDB, DynamoDB (key-value OLTP patterns).

OLAP design characteristics

Star schema denormalizes for scan speed:

-- OLAP: fact_sales + dimensions
CREATE TABLE fact_sales (
  sale_date_key    INT,      -- FK to dim_date
  product_key      INT,      -- FK to dim_product
  store_key        INT,      -- FK to dim_store
  customer_key     INT,
  quantity         INT,
  revenue          NUMERIC(14,2),
  cost             NUMERIC(14,2)
) ENGINE = columnar;  -- BigQuery, Snowflake, ClickHouse, etc.

CREATE TABLE dim_product (
  product_key      INT PRIMARY KEY,
  sku              TEXT,
  category         TEXT,
  brand            TEXT
);

Analytical query:

SELECT d.year, p.category, SUM(f.revenue)
FROM fact_sales f
JOIN dim_date d ON f.sale_date_key = d.date_key
JOIN dim_product p ON f.product_key = p.product_key
WHERE d.year BETWEEN 2022 AND 2025
GROUP BY 1, 2;

Columnar storage reads only revenue, product_key, sale_date_key — not full rows. Aggregations vectorize across billions of rows.

Typical engines: Snowflake, BigQuery, Redshift, ClickHouse, DuckDB, Apache Druid.

Why mixing workloads fails

Running OLAP on OLTP causes:

  1. Lock contention — long SELECTs hold snapshots or block vacuum (Postgres MVCC bloat)
  2. Buffer pool pollution — analytical scans evict hot OLTP pages from cache
  3. Unpredictable p99 — checkout latency spikes when analyst runs wide join
  4. Index mismatch — OLTP indexes don't help aggregations; analytical indexes hurt write speed

Metabase-on-prod is the classic failure mode. Fix: read replica for BI (still imperfect at scale) or proper pipeline to warehouse.

The modern split architecture

┌─────────────┐    CDC/ETL     ┌──────────────┐    SQL    ┌────────────┐
│  OLTP       │ ─────────────► │  Staging /   │ ────────► │  OLAP      │
│  Postgres   │   Debezium,    │  dbt models  │           │  Snowflake │
│             │   Fivetran     │              │           │            │
└─────────────┘                └──────────────┘           └────────────┘
      ▲                                                          │
      │ short queries                                            ▼
   Application                                              Dashboards

Change Data Capture (CDC) streams inserts/updates/deletes to warehouse without batch windows. Debezium + Kafka → Snowpipe pattern gives near-real-time OLAP without touching OLTP query paths.

dbt transforms raw CDC tables into star schema marts — fact_orders, dim_customers — versioned in Git.

HTAP: when one system tries both.

Hybrid Transaction/Analytical Processing (TiDB, SingleStore, Apache Pinot with upserts) blurs the line. Useful when:

Trade-offs: jack of both trades, expert in neither; licensing cost; harder tuning than pure-play split.

We use Postgres OLTP + ClickHouse OLAP via PeerDB sync — HTAP products weren't worth the lock-in premium at our scale.

Choosing storage.

Signal Lean OLTP Add OLAP
Query scans >1M rows regularly Yes
Dashboards hit production DB Yes
Need point-in-time historical snapshots Yes
All queries indexed, <100ms Yes
ML feature store from raw events Yes
<100GB total data, few analysts Postgres replica maybe enough Maybe later

Operational differences.

Concern OLTP OLAP
Backup Frequent, point-in-time recovery Snapshot + incremental
Scaling Vertical + read replicas + sharding Horizontal, separate compute/storage
Cost driver IOPS, connection count Scanned bytes, warehouse uptime
Testing Transaction integration tests dbt tests, row count reconciliation
SLA 99.99% availability Best-effort refresh by 6am

Reconcile OLTP and OLAP counts nightly — SUM(orders.total) in warehouse vs OLTP snapshot catches pipeline bugs before executives see wrong numbers.

Define SLAs separately: OLTP p99 latency SLOs should exclude analytical query paths entirely. When BI teams request "just one more direct query," redirect to the warehouse or a dedicated read replica with statement_timeout enforced (SET statement_timeout = '30s'). Reconciliation jobs comparing row counts and sum totals between OLTP snapshots and OLAP marts catch pipeline bugs before executives see them in board decks. For early-stage teams, Postgres plus nightly pg_dump to DuckDB or BigQuery may suffice — but set a growth trigger (e.g., analytical queries exceed 5% of CPU for three days) to fund proper CDC. FinOps should tag warehouse compute by team; unconstrained OLAP spend often exceeds OLTP RDS cost once analysts adopt self-serve SQL.

Never run SELECT * analytics against the production OLTP primary without statement_timeout — one analyst's cartesian join becomes everyone's checkout outage.

Resources

Frequently asked questions

Can one database handle both OLAP and OLTP?

Small scale yes — PostgreSQL with read replicas or materialized views works for moderate analytics. At production scale, mixed workloads cause lock contention, unpredictable latency on writes, and expensive vertical scaling. Separate transactional stores from analytical pipelines via CDC or ETL.

What is the main schema difference between OLTP and OLAP?

OLTP uses normalized schemas (3NF) to minimize redundancy and preserve write integrity. OLAP uses denormalized star or snowflake schemas optimized for aggregate scans across dimensions — fact tables plus dimension tables.

When should data move from OLTP to OLAP?

When reporting queries scan millions of rows, require historical snapshots, or need complex joins that slow transactional traffic. Typical pattern: CDC from OLTP to warehouse within minutes (near-real-time) or nightly batch for non-urgent dashboards.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →