My Account List Orders Book Page

The Rise of Apache Parquet

Table of Contents

  • Introduction
  • Chapter 1 The Row-Oriented Past: Early Storage in Big Data
  • Chapter 2 The Columnar Breakthrough: Analytical Processing Reimagined
  • Chapter 3 Genesis at Twitter and Cloudera: The Birth of Parquet
  • Chapter 4 Standing on the Shoulders of Dremel: Core Design Principles
  • Chapter 5 Anatomy of a Parquet File: Headers, Footers, and Metadata
  • Chapter 6 Row Groups, Column Chunks, and Pages: The Storage Hierarchy
  • Chapter 7 Modeling Complex Data: The Definition and Repetition Level Algorithm
  • Chapter 8 Shrinking the Footprint: Encodings, Bit-Packing, and Compression
  • Chapter 9 Entering the Apache Incubator: Building an Open Community
  • Chapter 10 The Format War: Parquet, ORC, and Avro Compared
  • Chapter 11 Engine Integration: How Apache Spark Embraced Parquet
  • Chapter 12 Pushing Down Predicates: Accelerating Interactive SQL Engines
  • Chapter 13 The Rise of Presto and Trino: Querying at Scale
  • Chapter 14 The Cloud Migration: Parquet as the Foundation of Object Storage
  • Chapter 15 Cost and Scale: Economic Impacts of Storage Efficiency
  • Chapter 16 Arrow and Parquet: Bridging Disk and In-Memory Analytics
  • Chapter 17 Machine Learning Pipelines: Fueling Modern Feature Stores
  • Chapter 18 Streaming Data Challenges: Micro-Batching and Ingestion Bottlenecks
  • Chapter 19 Schema Evolution: Managing Change in Dynamic Environments
  • Chapter 20 Data Lakehouse Architecture: Delta Lake, Iceberg, and Hudi
  • Chapter 21 Securing the File: Modular Encryption and Access Control
  • Chapter 22 Beyond Java: Porting Parquet to C++, Rust, and Go
  • Chapter 23 Real-World War Stories: Scale, Failures, and Optimization Lessons
  • Chapter 24 The Modern Data Stack: Parquet as the Invisible Standard
  • Chapter 25 The Road Ahead: The Next Decade of Analytical Storage

Introduction

Every technological revolution leaves behind a trail of quiet infrastructure—the unglamorous, foundational plumbing that makes the extraordinary seem ordinary. In the early days of big data, the industry was consumed by the spectacle of massive scale. Frameworks like Apache Hadoop made headlines by promising to distribute storage and compute across thousands of commodity machines. Yet, beneath the fanfare of distributed processing lay a persistent, crippling bottleneck: data storage was trapped in paradigms designed for a previous era. Systems read entire rows of data from disk sequentially, sifting through mountains of irrelevant bytes just to calculate a simple aggregate over a single metric. As data volumes exploded into petabytes, analytics threatened to collapse under the sheer weight of I/O inefficiencies, spiraling cloud bills, and sluggish query engines.

Into this bottleneck stepped Apache Parquet. Conceived through an open-source collaboration between engineers at Twitter and Cloudera and inspired by Google’s seminal Dremel paper, Parquet introduced a radically efficient, open columnar storage format to the wider software world. Rather than organizing records horizontally like rows in a ledger, Parquet flipped data on its side. By organizing values vertically by column, it unlocked staggering compression ratios, enabled specialized encoding techniques, and allowed query engines to read only the exact fields required for a computation. What seemed at first like an incremental optimization for MapReduce jobs quickly evolved into an architectural paradigm shift that redefined how data systems interact with persistent storage.

The Rise of Apache Parquet: How a Columnar Format Reshaped Big Data Analytics is the story of that transformation. This book traces the technological lineage, engineering triumphs, and ecosystem dynamics that propelled a specialized file format into the ubiquitous storage standard of the modern analytics stack. You will discover how Parquet solved the deceptively hard problem of representing nested, repeated data structures without sacrificing columnar efficiency, how it weathered fierce format rivalries, and how it catalyzed the transition from rigid on-premises data warehouses to flexible, cloud-native object stores. Along the way, we will peel back the byte-level mechanics that give Parquet its speed: definition and repetition levels, metadata footers, dictionary encodings, and predicate pushdown.

Beyond the low-level mechanics, this book explores the broader ecosystem effects that solidified Parquet's dominance. We examine how the adoption of Parquet by heavyweight compute engines—from Apache Spark and Presto to modern vectorized execution runtimes—sparked a golden age of disaggregated compute and storage. You will see how Parquet laid the bedrock for modern table formats like Apache Iceberg, Delta Lake, and Apache Hudi, bridging the divide between low-cost cloud data lakes and ACID-compliant data warehouses. We will also address the operational realities of running Parquet in production: managing schema evolution across distributed teams, streaming ingestion bottlenecks, modular file encryption, and the ongoing cross-language rewrites in Rust and C++ that power next-generation data tools.

Whether you are a data engineer striving to optimize query runtimes and trim cloud infrastructure costs, a systems architect designing modern analytical platforms, or a curious technologist seeking to understand how data formats quietly shape the software industry, this book offers a definitive guide. Parquet is no longer just an open-source project; it is the invisible, indispensable lingua franca of global analytics. By understanding its origins, its internal mechanics, and its evolving role in modern data architectures, you will gain not only a deeper mastery of current data systems, but also a clearer vision of where analytical computing is headed in the decade to come.


CHAPTER ONE: The Row-Oriented Past: Early Storage in Big Data

To understand why a storage format like Apache Parquet feels so natural today, one must first revisit an era when storing data in any format at all felt like a victory against physics. The early days of big data were defined by a relentless, messy struggle against sheer physical volume. When the web exploded in the late 1990s and early 2000s, engineers discovered that the digital universe was producing exhaust at a rate that traditional hardware simply could not digest. Log files, clickstreams, search queries, crawl artifacts, and sensor telemetry poured into operational environments like flash floods. The traditional relational database management systems (RDBMS) that had powered enterprise computing for three decades were suddenly gasping for air.

These relational systems were marvels of engineering, honed through decades of academic research and commercial optimization. They were built around the transactional ideals of ACID guarantees: Atomicity, Consistency, Isolation, and Durability. If you wanted to transfer money between bank accounts or update an inventory counter when someone bought a pair of shoes, systems like Oracle, DB2, and Postgres were unmatched. They accomplished these feats by treating data as collections of discrete, cohesive entities known as rows. A single record represented a single customer, a single transaction, or a single web page. All the attributes belonging to that entity were packed together into contiguous bytes on disk. If an update occurred, the database engine navigated directly to the relevant disk sector, modified the record in place, appended a line to a write-ahead log, and carried on.

This architecture, formally known as Online Transaction Processing (OLTP), made complete intuitive sense. It mirrored the physical ledgers from which computing had evolved. In a physical ledger, you write a row for each entry across a wide page, recording the date, the customer name, the product code, the quantity, and the price side by side. When computing moved to spinning magnetic platters, arranging data horizontally along the tracks of a hard drive preserved spatial locality. A read-write head could sweep across the platters, settle over a cylinder, and read an entire customer profile in a single mechanical stroke. For transactional operations, row-oriented layout was not merely an implementation detail; it was the foundational logic of computer architecture.

When the big data boom arrived, engineers naturally reached for the mental models they already understood. However, the nature of the questions being asked was fundamentally changing. Organizations were no longer asking simple transactional questions like "What is the balance of account number 4029?" Instead, they were asking expansive analytical questions like "What was the average latency across all requests served to users in Western Europe over the last ninety days?" These were not transactional lookups; they were Online Analytical Processing (OLAP) queries. They did not care about a single row in isolation; they cared about specific measurements aggregated across billions of rows.

The infrastructure of the mid-2000s was utterly ill-equipped for this shift. The initial answer to the scaling crisis was the Google File System (GFS) and its open-source offspring, the Apache Hadoop Distributed File System (HDFS). Along with these storage layers came MapReduce, a processing framework that turned the conventional database model on its head. Instead of loading structured data into a specialized, tightly coupled database management system, you dumped raw files into a distributed filesystem across hundreds or thousands of inexpensive commodity servers, running batch computation across them in parallel.

In this early Hadoop ecosystem, simplicity took precedence over storage elegance. Engineers needed to ingest data as fast as humanly possible, and nothing was faster than appending text to flat files. The default storage format of the early distributed data boom was plain text: comma-separated values (CSV), tab-separated values (TSV), and unstructured server logs formatted by string concatenations. If a web server spat out an Apache access log, a cron job or a collection daemon simply bundled those log lines together and piped them straight into an HDFS directory.

The Tyranny of the Delimited Text File

Working with flat text files was delightfully democratic. You could open a data file using standard command-line tools like

head
,
grep
, or
awk
. You could inspect records directly without specialized serialization libraries or complex schemas. If an ingestion script crashed, you could open the corrupted file in a text editor to see exactly where the parsing had failed. For developers accustomed to the rigid ceremony of enterprise data warehouses, the freedom to dump raw text onto disk was intoxicating.

Yet, this simplicity came with a brutal engineering tax. Plain text files are spectacularly inefficient containers for analytical data. To understand the depth of this inefficiency, consider what happens under the hood when a computer reads a delimited text file to evaluate a query.

First, plain text destroys the native binary representation of numbers. In a computer’s processor, a 32-bit integer occupies four bytes of memory. Whether that number is zero, forty-two, or two billion, it consumes the exact same four bytes. When written as an ASCII or UTF-8 string, however, the number 2,147,483,647 requires ten bytes of storage—more than double its native footprint. Floating-point numbers fare even worse, requiring variable strings of characters to represent decimals, signs, and exponents.

Second, text formats lack self-describing structural metadata at the byte level. There is no binary marker indicating where one field ends and the next begins; there is only the delimiter itself, such as a comma, a tab, or a pipe. To extract the value of the twentieth column in a row, a query engine cannot simply jump twenty fields ahead using pointer arithmetic. It must sequentially scan every single byte in the row, evaluating character by character, searching for the delimiter while checking whether that delimiter is safely enclosed within quotation marks or escaped by a backslash.

This character-by-character parsing consumed massive amounts of CPU cycles. In the early days of Hadoop MapReduce, clusters frequently spent more time parsing strings and instantiating memory objects than they did executing analytical logic. The central processing unit was transformed into an expensive text parser, burning clock cycles on regular expressions and delimiter escaping while reading massive data streams off magnetic disks.

Furthermore, text files had no universal standard for handling complex or nested data. Real-world events rarely conform to a flat table. A single user click might contain an array of applied filters, a nested object detailing the device hardware, and a list of active experimental flags. To shoehorn this structure into delimited files, engineers invented fragile conventions. Some stored serialized JSON strings inside a CSV column, requiring secondary parsing passes at query time. Others used nested delimiters—commas between rows, pipes between columns, semicolons between array items, and colons between key-value pairs. A single malformed input string, an unescaped newline character, or an unhandled special character could quietly corrupt downstream analytics or cause entire processing pipelines to crash midway through an eight-hour batch job.

Binary Row Formats: SequenceFile and Beyond

Recognizing the unsustainable overhead of plain text, the early Hadoop community set out to create structured, binary alternatives. The primary objective was to eliminate the cost of text parsing and provide basic binary serialization, while maintaining the record-at-a-time streaming semantics demanded by MapReduce.

The earliest major innovation in this direction was the Hadoop

SequenceFile
. Introduced in the mid-2000s, a SequenceFile was a binary container file format designed specifically to store key-value pairs for MapReduce jobs. Instead of relying on human-readable text delimiters, SequenceFiles stored records as raw length-prefixed binary buffers. Each record in a SequenceFile began with a four-byte integer representing the length of the key, followed by the key bytes, a four-byte integer representing the length of the value, and the value bytes.

SequenceFiles were an undeniable step forward. By incorporating length prefixes, an execution engine could skip an entire record simply by reading the length headers and advancing the file read pointer, without scanning every individual byte in the payload. SequenceFiles also introduced the concept of sync markers—distinctive, pseudo-random byte sequences written into the file at regular intervals (typically every few megabytes). These markers solved a major architectural challenge in distributed computing: splitting a massive, multi-gigabyte file across multiple parallel worker nodes. A distributed mapper could seek directly to an arbitrary byte offset within a file, scan forward until it encountered a sync marker, and confidently begin reading aligned records from that exact point without needing to read the file from the very beginning.

To address storage bloat, SequenceFiles offered three different storage modes: uncompressed, record-compressed, and block-compressed. In uncompressed mode, raw keys and values were written sequentially. In record-compressed mode, the value payload of each individual record was passed through a compression algorithm like Deflate or Gzip.

The block-compressed mode was the most sophisticated of the three. Rather than compressing each record individually—which produced poor compression ratios because individual records were too small to saturate the dictionary of the compression algorithm—a block-compressed SequenceFile buffered multiple records in memory until a configurable buffer size (often one megabyte) was reached. It then compressed all the buffered keys together into one contiguous compressed block, and all the buffered values together into another contiguous compressed block.

Despite these improvements, SequenceFiles remained deeply flawed for general-purpose analytical workloads. First and foremost, a SequenceFile was tightly coupled to the Java programming language. The serialization format depended on Java’s

Writable
interface. If you wanted to read or write a SequenceFile from a program written in C++, Python, or Ruby, you were forced to implement a custom parser that meticulously replicated the binary idiosyncrasies of Hadoop’s internal Java classes. In an industry rapidly diversifying beyond pure Java MapReduce, this tight coupling created friction.

Moreover, SequenceFiles were completely blind to the internal schemas of the values they were storing. To the SequenceFile container, the payload was simply an opaque blob of bytes. The container had no intrinsic awareness of the fields inside the record, their data types, or their relationships. If a dataset contained one hundred fields and an analytical query only required two of them, the query engine had no choice: it had to read the entire binary blob off disk, decompress the entire block, and deserialize every single field in the Java object before discarding the ninety-eight fields it did not care about.

The Serialization Evolution: Thrift, Protocol Buffers, and Avro

As distributed systems matured, engineering teams outside the pure Hadoop bubble were solving the structured data problem from a different angle. High-scale service-oriented architectures required fast, cross-language RPC (Remote Procedure Call) mechanisms. Engineers needed to pass rich, strongly typed data structures between microservices written in different programming languages without incurring the overhead of XML or JSON.

At Google, this effort produced Protocol Buffers (Protobuf). At Facebook, engineers built Apache Thrift. Both systems operated on the same core principle: data structures were defined upfront using an Interface Definition Language (IDL). A schema compiler then processed this IDL file to generate high-performance serialization and deserialization code in multiple programming languages, including Java, C++, Python, and Go.

Under the hood, these serialization systems used compact, tag-based binary encodings. Instead of storing field names alongside data values, the schema compiler assigned each field an integer identifier known as a field tag. When a record was serialized to disk or sent across the network, the output stream contained only the field tag, a type wire identifier, and the raw payload. Variable-length zigzag integer encodings (such as varints) were used to compress numbers, ensuring that small integers consumed only a single byte rather than their full native allocation.

The arrival of IDL-based serialization changed the way big data platforms modeled complex domains. Datasets were no longer limited to flat, brittle rows; they could cleanly represent nested structs, optional fields, maps, and recurring lists. Companies like Twitter and Facebook standardized their entire data architectures around Thrift and Protocol Buffers. Ingestion pipelines received structured binary payloads directly from edge web services and persisted them directly onto disk.

However, using RPC-focused serializations for long-term analytical storage introduced a subtle architectural friction: the problem of external schema management. Protocol Buffers and Thrift files did not carry their own schema definitions within the file payload. The binary stream contained field tags (like field 1, field 2, field 3), but it did not store the field names, the semantic types, or the documentation. To read a Protobuf or Thrift file written three years earlier, you had to possess the exact, compatible IDL file that was used to compile the reader software. If the IDL definition was lost, the binary file was rendered nearly unreadable—a collection of anonymous tags with no semantic context.

To solve this gap in the Hadoop ecosystem, Doug Cutting, the co-creator of Hadoop, spearheaded the development of Apache Avro. Avro was designed explicitly to serve as a data serialization framework tailored for long-term data storage and batch processing.

Avro retained the horizontal, row-oriented paradigm of its predecessors, but it introduced a transformative architectural rule: the schema was always present. An Avro file contains a comprehensive JSON schema embedded directly in its file header, alongside metadata and sync markers. Following the header, the file stores records grouped into binary blocks, optionally compressed with codecs like Snappy or Deflate.

Because the schema is physically baked into the file itself, an Avro file is entirely self-describing. Any processing engine in any programming language can open an Avro file, parse the embedded JSON schema from the header, and immediately deserialize the payload without requiring access to external code artifacts or compiled classes.

Avro also provided an exceptionally robust framework for schema evolution. It defined rigorous mathematical rules for how schemas could change over time. A producer could add a new optional field with a default value, remove an old field, or rename an attribute. When a reader encountered a file written with an older schema version, the Avro library would compare the writer’s schema (embedded in the file) with the reader’s schema (provided by the query application) and dynamically reconcile the differences on the fly. Missing fields were populated with defaults, and removed fields were skipped cleanly.

With Avro, the industry had reached the theoretical pinnacle of row-oriented storage design. It was compact, self-describing, split-friendly for distributed processing, language-agnostic, and natively resilient to evolving operational schemas. For ETL (Extract, Transform, Load) pipelines, streaming ingress, and transactional event streams where records needed to be written individually and preserved in their entirety, Avro became—and largely remains—the gold standard.

The Analytical Wall

Even as row-oriented formats evolved from raw delimited text to refined binary systems like Avro, an inescapable architectural crisis was brewing in the analytical world. The underlying problem was not the efficiency of the serialization encoding; the problem was the orientation itself.

To understand why row-oriented formats hit an unyielding performance wall, one must analyze the physical interaction between analytical software and server hardware. Consider a modern enterprise maintaining a centralized dataset of user events. A single record in this dataset might easily contain two hundred distinct columns: user identifiers, geolocation data, session tokens, browser fingerprints, screen resolutions, referrers, A/B test variants, internal routing timestamps, and countless contextual metadata attributes.

Now, imagine a business analyst running a query against this dataset to calculate the daily count of active users:

SELECT event_date, COUNT(DISTINCT user_id) 
FROM user_events 
WHERE event_date BETWEEN '2012-01-01' AND '2012-01-31' 
GROUP BY event_date;

This query touches exactly two fields:

event_date
and
user_id
. The other one hundred and ninety-eight fields in the schema are completely irrelevant to the computation.

In a row-oriented format—whether it is a CSV, a SequenceFile, or an Avro file—every row is stored contiguously on the physical storage medium. The bytes representing column one are immediately followed by column two, which are followed by column three, all the way to column two hundred, at which point the bytes for the next row begin.

Because computer storage interfaces read data in sequential physical blocks (sectors on spinning disks, or pages on flash-based solid-state drives), an engine executing this query has no physical way to selectively isolate the two required columns. To inspect the

event_date
and
user_id
of a single record, the storage controller must physically read the entire two-hundred-column row from the persistent disk into the operating system’s page cache. The CPU must then decompress the entire block, deserialize the complete record into memory, extract the two desired attributes, and immediately discard the remaining ninety-nine percent of the data.

The mathematical inefficiency of this process is staggering. If the two required columns account for twenty bytes of data within a two-kilobyte row, the analytical engine is forced to execute an I/O payload that is one hundred times larger than necessary. At a scale of ten gigabytes, an organization might tolerate this waste. But as datasets expanded into tens and hundreds of terabytes across shared clusters, this architecture became a disaster.

Hard disk drives (HDDs), which formed the physical backbone of all early big data clusters, were mechanical devices limited by seek times and sustained read throughput. A standard enterprise magnetic disk of the era could deliver sustained read speeds of roughly 100 to 150 megabytes per second. When a distributed query engine attempted to scan hundreds of terabytes of row-oriented data, the execution was inevitably and completely bottlenecked by disk I/O. Racks of servers hummed at maximum capacity, burning kilowatts of power, with their central processors sitting idle for significant stretches of time, waiting for magnetic platters to spin and deliver gigabytes of discarded bytes into main memory.

The compression dynamics of row-oriented files amplified this penalty. General-purpose compression algorithms—such as Lempel-Ziv-Markov chain algorithms (LZMA), Deflate, or Snappy—operate by identifying recurring patterns, phrases, and redundancies within a localized sliding window of data. In a row-oriented layout, adjacent bytes represent entirely different semantic concepts. An integer timestamp is immediately followed by a variable-length string containing a URL, which is followed by a floating-point latitude coordinate, which is followed by a boolean flag.

Because adjacent fields possess entirely different data types, byte distributions, and entropy characteristics, the sliding window of a compression algorithm struggles to find long, repetitive sequences. A dictionary compressor can compress text against text reasonably well, and it can compress series of integers against integers, but an interleaved mishmash of text, integers, timestamps, and floats presents high entropy. As a result, the compression ratios achieved on raw row-oriented data were modest, rarely exceeding 2x or 3x without incurring prohibitive CPU costs.

As the industry entered the 2010s, the analytical bottleneck reached a breaking point. Organizations were building sprawling Hadoop deployments containing thousands of nodes, yet queries that should have taken seconds took hours to complete. Hive, Pig, and custom MapReduce pipelines groaned under the weight of full-table scans over row-oriented datasets.

Data engineers attempted various structural workarounds to bypass


This is a sample preview. The complete book contains 27 sections.