WHITE PAPER 3 · DATA STORAGE ENGINES
Analytical queries read many rows but few columns, aggregate heavily and rarely change data in place. Columnar engines are built around this pattern.
Bassirah Technology — Innovation & R&D · 26 October 2026 · Free to share with attribution
Analytical queries read many rows but few columns, aggregate heavily and rarely change data in place. Columnar engines are built around this pattern. This paper examines how they turn it into efficient use of the modern CPU: by storing each column separately, encoding it with lightweight schemes that decode quickly, skipping blocks with small summaries, and processing data in cache-sized vectors with SIMD instructions. It reviews storage layout, encoding and compression, vectorized and compiled execution, and the design of scans, joins, aggregation, sorting and out-of-core processing. It then covers open file formats, composable engines such as Apache DataFusion and Velox, embedded engines such as DuckDB, and cloud designs that separate storage from compute. The paper closes with open challenges, framed as research directions and opportunities, and with notes for platform decisions.
A typical analytical query scans a large table, filters on a few attributes, joins with smaller tables and aggregates the result. A row store reads every attribute of every row it touches. A column store reads only the columns the query names, and values of one type stored together compress well [1]. C-Store made the case for this read-optimized design, storing data by column and packing values carefully both on disk and in memory during query processing [2]. In the same year, MonetDB/X100 showed that database engines used modern CPUs poorly, achieving few instructions per cycle on analytical work, and proposed processing data a vector at a time instead of a tuple at a time [3].
Both ideas now underpin most analytical engines. The ClickHouse authors describe its executor as following the X100 vectorization model [4], and DuckDB was designed as an embedded engine for analytical SQL [5]. The shared aim is to keep the CPU busy on useful work, with data arriving in a form it can process in tight loops.
This paper covers engines that store or read columnar data and execute queries with vectorized or compiled code, on one node or across a cluster. It concentrates on peer-reviewed work from 2021 to 2026 and on official documentation, citing earlier papers where they introduced an idea still in use. It does not report benchmark figures. Where a paper reports results, we describe them qualitatively and attribute them to the authors.
Paper 1 placed DuckDB and ClickHouse in Class B of a five-class taxonomy, analytical and read-optimized engines, and named memory bandwidth and CPU cache efficiency as their bottleneck. Paper 2 examined LSM-trees, whose immutable sorted runs and background merges reappear here: the ClickHouse authors describe the storage layer of its MergeTree engine as based on log-structured merge trees [4]. This paper opens Class B.
The row layout, or N-ary storage model, places all attributes of a record together. It suits transactions that read or write whole records. The column layout places all values of one attribute together. It suits scans that touch a few attributes of many records [1].
Most practical formats sit between the two. PAX (Partition Attributes Across) keeps a page’s records together but groups each attribute’s values inside the page, improving cache use without changing which pages are read [6]. Apache Parquet applies a similar idea at file scale: a file is split into row groups, each holding one column chunk per column, divided into pages, with metadata in a footer [7]. The ClickHouse authors note that Snowflake persists data in hybrid PAX pages, whereas ClickHouse’s own format is strictly columnar [4].
A column store must eventually stitch columns back into rows. Late materialization delays this: the engine filters and joins on the columns it needs, tracks the positions of surviving rows, and fetches other columns only for those rows [1]. Liu et al. argue that the advantages of applying this uniformly have narrowed in vectorized engines, and let each attribute choose its own materialization point by cost [8]. In DuckDB, their approach outperformed both pure early and pure late strategies on the authors’ benchmarks.
Small per-block summaries let a scan skip blocks that cannot satisfy a predicate. The simplest is a zone map holding a column’s minimum and maximum in a block. Amazon Redshift evaluates predicates over zone maps before scanning [9]. Parquet offers an optional page index with minimum and maximum values per page, plus an offset index for jumping to the matching pages of other columns [7]. Data Blocks added Positional SMAs, which narrow the scan range inside a block that cannot be excluded as a whole [10].
ClickHouse provides skipping indexes, including min-max, set and Bloom-filter types, each defined over a block of consecutive granules [11]. Its documentation is explicit about the limits: a skipping index helps only when the indexed column correlates with the sort order, and otherwise adds cost at insert and query time while skipping little [11].
Skipping works only if similar values sit together, so sort order is a central design decision. In ClickHouse, each data part is sorted by the primary key, and a sparse primary index stores the key of the first row of each granule. Filters on a key prefix are evaluated over key ranges with three-valued logic, and ranges with mixed outcomes are split recursively [4]. Projections store the same rows under another sort order, at extra insert, merge and storage cost [4], an idea that C-Store’s overlapping projections anticipated [2].
Cloud warehouses that ingest continuously must recluster to keep pruning effective. WAIR reclusters only the micro-partitions on the boundaries of query ranges; its authors report query performance close to a fully sorted layout at a lower, bounded reclustering cost [12].
ClickHouse’s MergeTree engine shows how these elements combine. Each insert creates an immutable part sorted by the primary key, and background merges combine parts [4]. A part is a directory with one file per column. Rows are divided into granules, by default of 8,192 rows, the smallest unit that scan and index operators handle, while I/O works on compressed blocks of neighboring granules [4]. Merges can also collapse or pre-aggregate rows, and time-to-live rules can recompress parts or move them to object storage [4].
The parallel with paper 2 is direct: appending immutable sorted files and merging them later turns random writes into sequential ones, at the cost of merge work and a delay before deleted rows physically disappear.
Lightweight encodings exploit a column’s structure with simple arithmetic. Dictionary encoding replaces values with small integer codes; run-length encoding (RLE) stores a value once with its repeat count; delta encoding stores differences between consecutive values; frame of reference (FOR) stores values relative to a block minimum, and bit-packing then uses only as many bits per value as the block needs. Parquet defines dictionary, RLE and bit-packing hybrid, several delta encodings and byte stream split, among others [7]. Because decoding is cheap, and some operators can work on encoded values directly, these schemes save bandwidth without costing much CPU [1].
General-purpose codecs such as LZ4 and Zstandard treat data as bytes and must decompress a whole block before any value is used. Zeng et al. evaluated Parquet and ORC on real-world data distributions and modern hardware [13]. They recommend dictionary encoding by default, favoring decoding speed over compression ratio for integers, making block compression optional, and embedding finer-grained auxiliary structures. BtrBlocks argues that with high-throughput networks, inefficient decompression makes data-lake scans CPU-bound, raising query time and cost [14]. It cascades lightweight encodings, chosen by sampling, to combine fast decompression with a good ratio.
Engines resolve the trade-off differently. ClickHouse compresses blocks with LZ4 by default and allows specialized codecs, such as delta and Gorilla, to be chained [4]. Its authors reason that with aggressive pruning, I/O costs outweigh decompression costs for the remaining data, and contrast this with DuckDB, which chose the lightweight methods of Data Blocks for workloads that mix analytics with occasional updates [4], [10]. The two choices rest on different premises about the workload.
Classic bit-packing stores values one after another, which limits how many a SIMD instruction can decode at once. FastLanes interleaves bit-packed values to suit a virtual 1,024-bit SIMD register and reorders tuples in all columns into a common transposed order, exposing independent work for lane widths of 8 to 64 bits [15]. Its scalar code can be auto-vectorized by compilers, which avoids maintaining platform-specific SIMD code. A vector of 1,024 decoded values fits in L1 or L2 cache, so decompression happens between memory and the CPU [15].
The layout carries over to GPUs. Afroozeh et al. report that FastLanes decompression could make GPU queries faster than on uncompressed data, but found the 1,024-value granularity too large for a single GPU warp because of register pressure, and used smaller mini-vectors [16].
Strings are often the largest and slowest columns, and dictionaries help only when whole strings repeat. FSST compresses strings with a static table of frequent substrings [17]. Its authors report speeds comparable to speed-optimized codecs such as LZ4 with better compression factors on text. Because the table is static, individual strings can be decompressed independently, which allows query processing on compressed data.
Floating-point data resists delta and FOR encoding because IEEE 754 doubles do not represent most decimal values exactly. ALP encodes doubles that originated as decimals as integers, and otherwise compresses their leading bits [18]. It is designed for vectorized execution, and its authors report better speed and compression ratio than earlier floating-point schemes on their datasets.
These results are shaping new formats. The FastLanes file format avoids general-purpose compression in favor of data-parallel lightweight encodings, cascades encodings, compresses correlated columns together, and decodes in small batches that fit CPU and GPU caches [19].
In the classic iterator model, each operator returns one tuple per call, so interpretation overhead is paid per tuple. X100 kept the iterator structure but passed vectors between operators [3]. Each primitive is a tight loop over arrays that compilers can pipeline and auto-vectorize, and per-call overhead is spread over the vector [15].
DuckDB processes DataChunks, collections of vectors with a default size of 2,048 tuples [20]. A vector can be flat, constant, dictionary or sequence encoded, so compressed data can stay compressed during execution. Velox’s Arrow-compatible vectors support similar encodings plus frame of reference, and lazy vectors that are populated only on first use [21].
Vectors must be large enough to amortize interpretation and small enough for a pipeline’s working set to stay in cache. Kersten et al. compared vectorized and compiled execution inside one test system [22]. They found that vectorization is better at hiding cache-miss latency, while compiled code needs fewer instructions, which favors cache-resident work.
Selective operators complicate this. Qiao and Zhang show that hash joins can leave pipelines full of small, sparse chunks that lose most of the benefit of vectorization [23]. They model when to compact chunks, tune the threshold at run time with a learning-based method, and propose logical compaction to limit data movement.
Vectorized filters rarely copy data. They record surviving rows in a selection vector that later operators read through; Photon’s column batch, for example, holds a position list of active rows [24]. ClickHouse compiles hot loops into several kernels, such as scalar, auto-vectorized AVX2 and hand-written AVX-512 versions, and chooses one at run time from the CPU’s reported features [4]. One binary then runs on older processors while using wider instructions where they exist.
The alternative is data-centric code generation, which compiles each pipeline into machine code that keeps a tuple in registers from scan to pipeline breaker. Kersten et al. concluded that both paradigms are efficient, with different strengths [22]. Compilation latency is the main obstacle for short queries; Umbra reduces it with a single-pass code generator and a backend tuned for low compile time [25].
Production engines increasingly mix the two. Photon chose vectorized interpretation, citing runtime adaptivity, and its authors report that development, debugging and observability were easier than with a code-generating prototype [24]. Photon runs specialized code paths for batch properties such as the absence of NULL values [24]. Redshift blends operators through code generation and adds prefetching and vectorized execution [9]. ClickHouse is vectorized but compiles recurring expressions, aggregate functions and multi-key sort comparisons with LLVM, and its operators can switch to external aggregation or join algorithms at run time when memory runs short [4]. Data Blocks combined a vectorized scan with compiled pipelines [10].
A modern scan prunes blocks with zone maps or skipping indexes, decodes the rest into vectors, evaluates predicates into selection vectors and fetches further columns only for surviving rows. Redshift’s scans also use SIMD to decompress its lightweight encodings [9]. Encodings with random access, such as FSST, let predicates run without decompressing a whole block [17].
Research long compared radix-partitioned hash joins, which partition inputs to fit cache before building, with simpler non-partitioned joins. Bandle et al. integrated a radix join into Umbra, with a Bloom-filter semi-join reducer for selective queries [26]. On TPC-H, partitioning paid off in very few joins, and the benefit faded quickly away from ideal parameters, so they advise against adding it to a code-generating engine for real-world workloads. ClickHouse uses a parallel hash join whose threads build a shared, partitioned hash table [4]. Join order matters as much as join algorithm (Section 10.7).
ClickHouse instantiates many hash-table variants from one template and chooses among them per operator by the types of the grouping columns, the estimated cardinality and other factors [4]. Parallel aggregation runs in two phases: threads build local tables of partial states, and a final step merges them [4].
Kuiper and Mühleisen found that sorting rows is almost always more efficient than sorting columnar data, even when columns must be converted to rows and back, and built DuckDB’s sort on that result [27].
When intermediates exceed memory, many engines switch to an external algorithm and performance drops sharply. Kuiper et al. aim for graceful degradation instead. For aggregation, they unify memory management for temporary and persistent data and use a page layout that is efficient in memory but can be spilled [28]. For joins, they add an adaptive external hash join that compresses materialized intermediates at run time and shares memory dynamically among concurrent operators [29]. In DuckDB, the authors report that performance degrades gradually as data exceeds the memory limit. Umbra’s buffer manager, with variable-size pages, takes a related approach to data on SSDs [30].
Morsel-driven parallelism splits input into small fragments, or morsels, that worker threads take from a dispatcher and push through a whole pipeline [31]. The degree of parallelism can change during execution, and the dispatcher keeps work on memory local to each NUMA node. ClickHouse instead fixes the maximum degree of parallelism in the plan, partitions input into larger ranges, and rebalances work between stages with exchange operators; its authors acknowledge that this can stall when lanes progress unevenly [4].
Parquet and ORC were designed in the early 2010s for the Hadoop ecosystem and became the standard formats of data lakes [13]. Apache Arrow plays the complementary role in memory: a language-independent columnar layout with contiguous buffers, validity bitmaps and aligned, padded buffers suited to SIMD, shareable between processes without copying [32]. Parquet or ORC at rest and Arrow in memory is now a common division of labor.
Several new formats respond to the limits Zeng et al. identified [13] and to new workloads.
– Lance targets AI workloads that need both scans and random access. A 2025 preprint argues that structural encoding, how repetition and validity information are stored, governs random-access performance [33].
– Nimble, from Meta, targets very wide tables such as machine-learning feature tables and separates encodings from the physical layout; its repository describes it as a work in progress [34].
– Vortex combines a file format with an extensible compression framework, cascading encodings and kernels that compute on encoded data; it is an incubation project of LF AI & Data [35].
– FastLanes builds its format on data-parallel encodings (Section 4.5) [19].
Two research proposals address format evolution directly. F3 embeds WebAssembly decoders in each file, so a reader without a native decoder can still decode the data [36]. AnyBlox bundles WebAssembly decoders with datasets, decoupling decoders from engines and format specifications [37].
Execution engines are becoming reusable components. Velox is a C++ library of vectorized, dialect-independent components; at Meta it is integrated with Presto, Spark and machine-learning preprocessing, among others [21]. Prestissimo replaced Presto’s Java workers with Velox-based C++ workers in a phased, in-place migration, and its authors report gains in latency, hardware efficiency and reliability [38]. Apache DataFusion is an embeddable Rust engine that uses Arrow as its memory model and exposes many extension points; its authors argue, with comparisons against DuckDB, that open standards and modular design need not cost performance [39]. Standard plan formats make engines swappable: Sirius accepts Substrait plans so that a GPU engine can replace a CPU engine behind an existing interface [40]. Trino, a distributed SQL query engine, queries heterogeneous sources, including Iceberg tables stored as Parquet, ORC or Avro on object storage [41].
DuckDB showed that analytical SQL can run inside the application process, as SQLite does for transactional work, avoiding data transfer between database and application [5]. ClickHouse has followed with an in-process mode, chDB, which its authors describe as inspired by DuckDB [4].
Cloud warehouses keep data in shared storage and scale compute separately. Snowflake’s engineers describe a design built for elasticity, multi-tenancy and performance, and use production data to show how new hardware and fine-grained billing have changed its design assumptions [42]. Redshift disaggregated storage and compute and added tiered storage, multi-cluster scaling, data sharing, the AQUA acceleration layer and a serverless option [9]. The lakehouse pattern extends separation to open formats read directly from object storage [43], the setting Photon was built for [24].
Object storage has high bandwidth, high latency and a cost per request [13]. Durner et al. found the gap between network and local NVMe bandwidth closing [44]. They derive cost- and performance-optimal retrieval configurations and present AnyBlob, a download manager that reduces CPU use; integrated into Umbra without local caching, it performed comparably to cloud warehouses that cache data on local SSDs. When data arrives this fast, decompression can become the bottleneck, which strengthens the case for lightweight encodings [14].
Self-managed ClickHouse scales out by sharding tables and replicating each shard, coordinated by Keeper, a C++ coordination service based on Raft consensus [4]. For sharded tables, the initiating node pushes as much work as possible to other nodes, which can return raw columns, filtered rows, partial aggregation states or complete results [4].
ClickHouse Cloud uses the SharedMergeTree engine family [45]. Data lives in shared object storage, metadata is coordinated through Keeper, and replicas do not communicate directly, so adding or removing compute does not require copying metadata. The ClickHouse authors plan to describe the cloud architecture in a later publication [4], so this paper relies on the official documentation.
Analytical engines are often asked to serve fresh operational data. TiDB adds Raft learner replicas that convert rows to columns, giving analytical queries a fresh, consistent column store isolated from transactions [46]. F1 Lightning adds HTAP capability as a loosely coupled service over existing transactional systems [47]. ClickHouse, optimized for appends, supports lightweight deletes that mask rows at query time until merges remove them physically [4], [11].
Table 1 compares engine families and design choices discussed in this paper. The ratings are qualitative and relative to each other. Actual behavior depends on data, schema, query mix and configuration.
| Engine family or design choice | Scan and aggregation efficiency | Update and delete handling | Elasticity | Open-format interoperability | Best-fit workloads |
|---|---|---|---|---|---|
| Sorted parts with sparse index and background merges (ClickHouse MergeTree) | High | Medium | Medium; High with shared object storage | Medium | Real-time analytics on append-heavy events, logs and time series with many concurrent queries |
| Embedded vectorized engine (DuckDB) | High | Medium to High | Low (single process) | High | Interactive and local analytics, data science, querying files in place |
| Data-centric compiled engine (HyPer, Umbra) | High | Medium to High | Low to Medium | Medium | Low-latency ad hoc queries where compilation cost is controlled |
| Composable vectorized library (Velox, DataFusion, Photon) | High | Depends on host system | Depends on host system | High | Building or modernizing engines; consistent semantics across systems |
| Federated distributed SQL over open formats (Trino) | Medium | Depends on table format | High | High | Ad hoc SQL across lakehouse tables and multiple sources |
| Disaggregated cloud warehouse (Snowflake, Redshift) | Medium to High (cache-dependent) | Medium | High | Medium | Variable, multi-tenant workloads that benefit from elastic compute |
| HTAP with a columnar replica (TiDB, F1 Lightning) | Medium | High | Medium | Low | Analytics on fresh transactional data |
| GPU-native SQL engine (Sirius) | High when the working set fits accelerator memory | Depends on host system | Low to Medium | Medium | Scan- and join-heavy queries on bounded working sets; early stage |
Table 1: Qualitative comparison of analytical engine families and design choices.
Challenge. Columnar layouts and heavy compression favor appends. Updates and deletes are usually handled with masks, delta structures or rewrites, so deleted data lingers and reads pay for filtering until merges catch up [4], [11]. Research direction. HTAP designs replicate changes into column stores with consistent reads [46], [47], and DuckDB pairs lightweight compression with multi-version concurrency control to support occasional updates [4]. Opportunity. Engines that expose freshness and physical-deletion lag as explicit, bounded properties would help teams serve operational analytics and meet deletion obligations, a theme paper 2 raised for LSM-trees.
Challenge. Parquet and ORC carry design decisions from the early 2010s [13], and new formats such as Lance, Nimble, Vortex and FastLanes each address different gaps [33], [34], [35], [19]. Every new format adds reader and writer work to every engine. Research direction. F3 and AnyBlox ship WebAssembly decoders with the data, decoupling encodings from engines [36], [37]. Opportunity. A stable extension mechanism inside the established formats, rather than a succession of replacements, would let research encodings reach production without breaking the ecosystem. Paper 5 will examine the table formats layered above these files.
Challenge. As network and storage bandwidth grow, decompression can become the bottleneck, and the right balance between compression ratio and decoding speed shifts with each hardware generation [14], [44]. Research direction. Data-parallel layouts and adaptive, sampled encoding choices aim for both a good ratio and fast decoding [15], [18], [14]. Opportunity. Engines could choose encodings per block from measured cost models of the target hardware, and revisit those choices as data ages or moves between tiers.
Challenge. GPUs offer high memory bandwidth and parallelism, but limited memory capacity, data movement over the host interconnect and cost have kept GPU databases niche [40]. Research direction. Sirius argues that larger GPU memory, faster interconnects and mature libraries such as libcudf have removed these barriers; its authors report better cost efficiency than CPU-only DuckDB on their benchmarks [40]. Compressed execution on GPUs is advancing [16], and Redshift’s AQUA layer pushes scans and aggregations to an FPGA-based, off-cluster cache [9]. Opportunity. Composable engines make offload a per-operator decision; the open questions are when data movement pays for itself and how to share accelerators with machine-learning work.
Challenge. Analytical engines are often bound by memory bandwidth, and large intermediates exceed memory capacity. Research direction. Ahn et al. evaluated commercial CXL memory devices with SAP HANA [48]. They found that the effect of CXL’s higher latency and lower bandwidth depends on each data structure’s access pattern, with analytical workloads affected more than transactional ones, and that CXL shared memory can shorten restart times. Graceful spilling to SSD is a complementary path [28], [29], [30]. Opportunity. Placing data structures across DRAM, CXL memory and SSD by access pattern is a natural next step for vectorized engines.
Challenge. In the cloud, every request, byte scanned and core-second has a price, and caching, clustering and scaling choices interact. Research direction. Work on object-storage retrieval [44], workload-aware reclustering [12] and operational studies of elastic warehouses [42] treats cost as a design target. Bachras and Jacobsen argue that energy, carbon emissions and water use should become first-class design criteria alongside performance [49]. Opportunity. Optimizers that weigh monetary cost and energy along with latency, and report them per query, would make trade-offs visible to the teams who pay for them.
Challenge. A poor join order or a misestimated cardinality can make an analytical query much slower than necessary, and sparse batches after selective operators erode vectorization [23]. Research direction. Diamond hardened joins split joins into lookup and expand steps that the optimizer can reorder to avoid oversized intermediates [50]. Robust Predicate Transfer makes acyclic queries provably robust to join order [51]. Zhang et al. found that simple adaptive query processing can compete with reinforcement-learning optimizers, without training and with interpretable behavior [52]. Opportunity. Robust operators combined with runtime adaptation could make ad hoc performance more predictable with less manual tuning.
Challenge. JSON, nested types, embeddings and wide feature tables do not fit fixed relational schemas, and storing them as opaque blobs forfeits columnar benefits. Research direction. JSON Tiles extracts frequent keys into columnar form automatically and collects statistics for the optimizer [53]. ClickHouse’s JSON type stores paths as subcolumns up to a limit and keeps rarer paths in a shared structure [11]. Lance and Nimble target random access and wide tables for machine-learning pipelines [33], [34]. Opportunity. Engines that adapt physical layout to the observed structure of the data, and serve both scans and point lookups, would narrow the gap between analytical and AI storage. Paper 4 examines vector storage engines.
No single analytical engine design fits every workload, so the layout, encoding and execution model should follow from each workload’s scan, freshness, concurrency and cost profile. In Bassirah’s platform work, ClickHouse, a Strategic Technology Partner, serves real-time analytics, either as partner-led ClickHouse or as ClickHouse OSS; federated SQL over Apache Iceberg tables stored as Parquet uses Trino OSS or Starburst, a Strategic Technology Partner; and Apache Superset or Strategy BI (from Strategy, a Strategic Technology Partner) provides business intelligence on top. In each case, sort keys, encodings and engine settings are validated on a representative workload before adoption, and no outcome is assumed from published results.
Product and project names are trademarks of their respective owners. Their mention describes publicly documented technology and does not imply a business relationship, except where Bassirah names a Strategic Technology Partner.
About this paper.
A technology survey of published research and publicly documented systems. It does not report Bassirah benchmarks or customer results. Product and project names are trademarks of their respective owners; their mention does not imply a business relationship, except where Bassirah names a Strategic Technology Partner.
THE SERIES
Each paper examines one class of engine in depth.
Compaction, write amplification and tuning in RocksDB-family engines, with the open challenges and research directions.
ANN indexes, quantization, filtering and updates in vector search, with the open challenges and research directions.
A survey of five engine classes (write-optimized, analytical, hardware-native, vector and lakehouse) and the trade-offs that separate them.
Bring one workload and its constraints to a short architecture workshop. We will map it to the engine classes in this paper and plan how to validate the choice.