Apache Hudi

Apache Hudi (Hadoop Upserts Deletes and Incrementals) is an open-source data management framework that simplifies large-scale data ingestion and provides ACID transaction support on data lakes. It’s designed for scenarios that require efficient data upserts (updates and inserts) and deletes in big data environments, while also enabling near real-time ingestion and querying of data.

Key Features of Apache Hudi:

Use Cases:

Conclusion:

Apache Hudi is ideal for environments where frequent updates, incremental processing, and ACID guarantees are necessary on top of a scalable data lake. It bridges the gap between traditional batch processing systems and real-time analytics by enabling near real-time ingestion and querying, making it a powerful tool for modern data architectures.

Architecture

Hudi Table Layout and Write Path Upsert batchrecords + key Index lookupkey to file group File groupbase + log files Committimeline instant Timeline.hoodie/ metadata Compactionmerge logs to base Cleaningdrop old versions Query enginesSpark, Flink, Trino Every record key maps to one file group. Each write is recorded as an instant on the timeline in the .hoodie directory, which is what gives atomic commits, incremental pulls and time travel.

A Hudi table is a directory of data files plus a .hoodie metadata folder holding the timeline: an ordered log of actions (commits, delta commits, compactions, cleans, rollbacks) each with a state of requested, inflight or completed. Readers only see data from completed instants, which provides snapshot isolation. Data is organised into file groups, each identified by a file id and holding all versions of a set of record keys. Hudi maintains an index (Bloom filters in file footers, a simple key join, HBase, or the metadata-table record index) that maps each incoming record key to its file group, so an upsert touches only the files that actually contain those keys.

Hudi also bundles table services that other formats leave to external jobs: clustering (re-sorting and resizing files), cleaning (removing file versions beyond a retention window), compaction and an internal metadata table that stores file listings and column statistics to avoid slow object-store listings.

Table Types

Copy-on-Write vs Merge-on-Read Copy-on-Writerewrite Parquet Slower writesfast reads Merge-on-Readappend Avro logs Fast writesmerge at read / compact CoW suits read-heavy analytics with modest update rates; MoR suits streaming upserts where write latency matters.

In a Copy-on-Write table, an update rewrites the affected Parquet base file with the changed records merged in. Reads are plain Parquet scans. In a Merge-on-Read table, updates are appended to row-based log files beside the base file and merged during reads (snapshot queries) or periodically by compaction into a new base file. MoR tables also offer a read-optimised query that reads only compacted base files, trading freshness for speed. Hudi also exposes incremental queries that return only records changed after a given instant, which is the foundation for chaining low-latency pipelines.

Example: Upserts with PySpark

# spark-submit with the hudi-spark bundle matching your Spark version
from pyspark.sql import SparkSession
spark = (SparkSession.builder
         .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
         .getOrCreate())

opts = {
    "hoodie.table.name": "orders",
    "hoodie.datasource.write.recordkey.field": "order_id",
    "hoodie.datasource.write.precombine.field": "updated_at",
    "hoodie.datasource.write.partitionpath.field": "order_date",
    "hoodie.datasource.write.table.type": "MERGE_ON_READ",
    "hoodie.datasource.write.operation": "upsert",
}
path = "s3://lake/hudi/orders"

updates = spark.read.json("s3://landing/orders/2026-09-30/")
updates.write.format("hudi").options(**opts).mode("append").save(path)

# Incremental read: only rows committed after a given instant
changes = (spark.read.format("hudi")
           .option("hoodie.datasource.query.type", "incremental")
           .option("hoodie.datasource.read.begin.instanttime", "20260930000000")
           .load(path))
changes.show()

The record key identifies a row, the precombine field decides which version wins when the same key appears more than once (largest value wins), and the partition path sets the directory layout. Spark SQL also supports MERGE INTO, UPDATE and DELETE on Hudi tables, and time travel via TIMESTAMP AS OF.

Hudi vs Other Table Formats

FormatStrengthTypical fit
Apache Hudirecord-level upserts, built-in indexing, incremental pulls, table servicesCDC and streaming ingestion into a lake
Delta Laketransaction log, tight Spark and Databricks integrationDatabricks-centric lakehouses
Apache Icebergengine-neutral spec, hidden partitioning, broad catalog supportmulti-engine analytics platforms
Apache PaimonLSM-tree primary-key tables, Flink-native streaminghigh-frequency CDC with Flink

Pitfalls