Stackbook Logo
data-stateestablished · medium operational burden

Columnar Storage

Also known as: column-store, parquet-layout

Intent

Store each column contiguously so analytical queries read and compress only the columns they need.

Problem

Analytical queries typically aggregate a few columns across billions of rows. Row-oriented storage forces the engine to read every column of every row, wasting I/O. Mixed value types in a row also compress poorly.

Forces

  • •Analytics touch few columns but many rows
  • •Same-typed adjacent values compress far better
  • •Single-row inserts and updates are expensive in column layout
  • •Row reconstruction costs extra work for point lookups

Solution

Overview

Columnar formats such as Parquet and ORC, and engines such as ClickHouse, Redshift and BigQuery, store values column by column. A query summing revenue by region reads two columns instead of fifty.

How it works

Same table, two layouts.

Row layout:    [id1,US,9.5,...] [id2,EU,4.0,...] [id3,US,7.2,...]
Column layout: id:     [id1,id2,id3]
               region: [US,EU,US]    -> dictionary + RLE
               amount: [9.5,4.0,7.2]

SELECT region, SUM(amount) FROM sales GROUP BY region;
-- reads only region and amount blocks

Each block stores min and max values, so a filter on a date range skips blocks entirely.

Encoding choices are chosen per column. A low-cardinality country column compresses to a dictionary of ids further squeezed by run-length encoding, a monotonic timestamp column compresses with delta encoding, and a sorted column benefits most because equal values sit together. Engines process data in vectors of a thousand or so values, keeping loops tight and CPU caches warm. Many warehouses also use table partitioning by date and clustering by a secondary key, so most queries read only a small part of the files.

Pitfalls

  • Streaming one row at a time directly into column files, producing thousands of tiny files.
  • Sorting data on a column nobody filters by, so block statistics are useless.
  • Running OLTP-style updates against a column store.
  • Selecting every column with SELECT star, which removes the main advantage.

Key takeaways

  • Read only the columns you need; that is the whole point.
  • Sort and partition data to match the common filters.
  • Load in batches and compact small files.
  • Keep OLTP and analytics in separate engines, linked by CDC.

✓ When to Use

  • •Data warehouses and BI dashboards
  • •Wide tables where queries select a handful of columns
  • •Large scans with aggregations and filters

✗ When Not to Use

  • •OLTP workloads with frequent single-row updates
  • •Point lookups returning entire rows
  • •Small datasets where scan cost is irrelevant

Pros

  • +Large reduction in I/O for analytical scans
  • +High compression ratios
  • +Vectorized execution and block skipping

Cons

  • −Slow, awkward row-level inserts and updates
  • −Row reassembly cost for wide selects
  • −Needs batch loading pipelines

Cost Profile

Infrastructure

Medium - lower storage per row but compute-heavy scans

Operational

Medium - ingestion batching and compaction

Cognitive

Medium - sort keys and partitioning choices matter

Failure Modes

  • Trickle inserts create many tiny files and slow queries

  • Poor sort key choice defeats block skipping

  • Using it for transactional updates causes write contention

Real-World Examples

Alternatives

    Related Patterns

    • partitioning
    • cdc

    Competency Domains

    data stateeconomics evolution