This is an automated email from the ASF dual-hosted git repository.

vinothchandar pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/hudi.git


The following commit(s) were added to refs/heads/asf-site by this push:
     new 4ba23d1275fc docs(blog): explainer wave 1: open table format, upsert, 
incremental ETL, ACID (#19269)
4ba23d1275fc is described below

commit 4ba23d1275fcdb0a7434a448b75be2e57e9e73b1
Author: vinoth chandar <[email protected]>
AuthorDate: Tue Jul 14 13:33:14 2026 -0700

    docs(blog): explainer wave 1: open table format, upsert, incremental ETL, 
ACID (#19269)
---
 ...18-hudi-incremental-processing-on-data-lakes.md |   4 +
 .../2026-07-14-what-is-an-open-table-format.md     | 113 +++++++++++++++
 .../2026-07-15-what-is-upsert-on-a-data-lake.md    | 116 +++++++++++++++
 ...07-16-what-is-incremental-etl-on-a-data-lake.md | 156 +++++++++++++++++++++
 .../blog/2026-07-17-what-is-acid-on-a-data-lake.md | 128 +++++++++++++++++
 website/static/llms.txt                            |   4 +
 6 files changed, 521 insertions(+)

diff --git 
a/website/blog/2020-08-18-hudi-incremental-processing-on-data-lakes.md 
b/website/blog/2020-08-18-hudi-incremental-processing-on-data-lakes.md
index a39e3f612db1..5461c357c128 100644
--- a/website/blog/2020-08-18-hudi-incremental-processing-on-data-lakes.md
+++ b/website/blog/2020-08-18-hudi-incremental-processing-on-data-lakes.md
@@ -17,6 +17,10 @@ Hudi joined the Apache incubator for incubation in January 
2019, and was promote
 of Hudi to the data lake from the perspective of "incremental processing". 
More information about Apache Hudi's framework functions, features, usage 
scenarios, and 
 latest developments can be found at [QCon Global Software Development 
Conference (Shanghai Station) 
2020](https://qconplus.infoq.cn/2020/shanghai/presentation/2646).
 
+:::info
+For a current, ground-up explainer of this idea, see [What is Incremental ETL 
on a Data Lake?](/blog/2026/07/16/what-is-incremental-etl-on-a-data-lake).
+:::
+
 ### NOTE: This article is a translation of the infoq.cn article, found 
[here](https://www.infoq.cn/article/CAgIDpfJBVcJHKJLSbhe), with minor edits
 
 <!--truncate-->
diff --git a/website/blog/2026-07-14-what-is-an-open-table-format.md 
b/website/blog/2026-07-14-what-is-an-open-table-format.md
new file mode 100644
index 000000000000..601c6efdeb22
--- /dev/null
+++ b/website/blog/2026-07-14-what-is-an-open-table-format.md
@@ -0,0 +1,113 @@
+---
+title: "What is an Open Table Format?"
+excerpt: "What open table formats like Apache Iceberg, Apache Hudi and Delta 
Lake actually are — table metadata plus a transaction protocol that turns files 
on object storage into transactional tables."
+description: "An open table format is table metadata plus a transaction 
protocol that turns files on cloud storage into database tables with ACID 
commits, schema evolution and time travel — Apache Iceberg, Hudi, Delta Lake 
and Paimon compared."
+authors: [vinoth-chandar]
+category: how-to
+image: /assets/images/blog/dlh_1200.png
+tags:
+- table format
+- data lakehouse
+- apache iceberg
+- delta lake
+- open architecture
+- apache xtable
+---
+
+An open table format is an openly governed specification with two inseparable 
parts: **table metadata** — which files make up the table, what schema they 
follow, what statistics they carry, at every version — and a **transaction 
protocol** — the rules by which writers commit changes atomically and readers 
see consistent snapshots. Together they turn a collection of files on cloud 
object storage into a database-like table with ACID transactions, schema 
evolution, and time travel. Apache H [...]
+Because the specification is open, any query engine — Apache Spark, Apache 
Flink, Trino, and many others — can read and write the same table without going 
through a proprietary system.
+
+Open table formats emerged to fix a specific gap. Cloud object stores such as 
Amazon S3, Azure Blob Storage, and Google Cloud Storage made it cheap to store 
virtually unlimited data, and columnar file formats made it efficient to scan. 
But a pile of Parquet files is not a table: no atomic commits, no record-level 
updates or deletes, no history to query, and no protection for a reader 
scanning a directory mid-rewrite.
+
+Table formats close that gap by layering a transaction log and rich table 
metadata over the files, and in doing so became the foundational component of 
the [data lakehouse](/blog/2024/07/11/what-is-a-data-lakehouse) architecture — 
warehouse-grade reliability and management over open data on the lake.
+
+This post covers the problem, the anatomy of a table format, the major open 
projects, and how the pieces fit together.
+
+## The Problem Before Table Formats
+
+Until roughly a decade ago, the de-facto "table" abstraction on data lakes was 
the Apache Hive model: a table was a directory, partitions were subdirectories, 
and the Hive Metastore recorded the schema and the list of partitions. This 
worked well enough on HDFS for append-only batch workloads, but it had 
structural weaknesses that cloud object storage made acute.
+
+- **No atomicity.** A failed job left partial files behind, and readers could 
not distinguish committed data from garbage; jobs resorted to fragile rename 
tricks that object stores do not support atomically.
+- **No record-level mutations.** Updating or deleting a record meant rewriting 
entire partitions, making change data capture (CDC) from operational databases 
and GDPR deletions painful and expensive.
+- **Coarse, expensive planning.** Query planning relied on listing directories 
— slow and costly on object stores — and on partition values as the only 
pruning mechanism; statistics about the actual data in each file lived nowhere.
+- **No isolation or history.** A query could read an inconsistent mix of old 
and new files while a pipeline rewrote the table, and with no table versions 
there was no time travel, rollback, or incremental "what changed since 
yesterday" queries.
+
+The fix was to stop treating a table as a directory listing and treat it 
instead as versioned metadata plus a commit protocol: a transactionally 
maintained record of the files that make up the table. Apache Hudi pioneered 
this idea in production, built at Uber in 2016 for near-real-time ingestion 
with updates, deletes, and incremental processing on one of the world's largest 
data lakes, and described publicly in [Uber's engineering 
blog](https://www.uber.com/blog/hoodie/) in early 2017.  [...]
+
+## What a Table Format Actually Consists Of
+
+Strip away the branding and every open table format is a specification for a 
handful of cooperating pieces of metadata stored alongside the data files.
+
+![Table format layer in the Apache Hudi 
stack](/assets/images/blog/hudistack/table_format_1.png)
+<p align = "center">Figure: The table format as a metadata layer above open 
data file formats</p>
+
+**A log of changes to the table.** The heart of a table format is an ordered 
record of every commit — data writes, deletes, schema changes, and maintenance 
operations. In Hudi this is the [timeline](/docs/timeline); Delta Lake calls it 
the transaction log; Iceberg maintains a chain of snapshots referenced from 
table metadata files. Whatever the name, this log is the source of truth: a 
change is part of the table if and only if it is committed to the log, and 
commit is an atomic operation [...]
+
+**Snapshots and versioning.** Every commit produces a new consistent view of 
the table, so readers can query it "as of" any retained version — enabling time 
travel for debugging and audits, rollback of bad writes, and, where the format 
tracks record-level changes, incremental queries that return only what changed 
between two points in time.
+
+**File-level statistics and indexes.** Table formats track per-file and 
per-column statistics — min/max values, null counts, record counts — that 
engines use to skip files that cannot contain matching rows, often eliminating 
most I/O for selective queries. Hudi generalizes this into a [multi-modal 
indexing subsystem](/docs/indexes) stored in an internal [metadata 
table](/docs/metadata), including column statistics, bloom filters, and 
record-level indexes that also accelerate the write pa [...]
+
+**Schema tracking and evolution.** The format stores the table's schema and 
defines rules for [evolving it](/docs/schema_evolution) — adding, renaming, or 
promoting column types — without rewriting existing data files, plus schema 
enforcement on write to catch pipeline breakages before bad data lands in the 
table.
+
+**Concurrency control.** Finally, the specification defines how multiple 
writers and background jobs coexist. All major formats give readers snapshot 
isolation — a query sees a consistent version of the table regardless of 
in-flight writes — while for concurrent writers they define mechanisms from 
optimistic concurrency control to non-blocking approaches for jobs touching 
disjoint or reconcilable data; Hudi's [concurrency 
control](/docs/concurrency_control) documentation covers these tra [...]
+
+How data is physically organized under this metadata varies too. Hudi, for 
example, defines two [table types](/docs/table_types): Copy-on-Write, which 
rewrites base files on update for maximum read performance, and Merge-on-Read, 
which writes changes to compact log files that are periodically merged — a 
trade-off between write latency and read cost that other formats have since 
adopted variants of.
+
+Note, though, that everything above is *specification* — passive metadata and 
protocol rules. A table format defines what a valid table looks like; it does 
not keep the table healthy. In database terms, that job belongs to a **storage 
engine**: the machinery that compacts small files, clusters data, cleans up old 
versions, and builds and maintains indexes. Every table on a lake needs this 
running somewhere — built into the project, supplied by the query engine, or 
bought from a vendor —  [...]
+
+## The Major Open Table Formats
+
+Four open source projects dominate the space today. All are openly governed 
(three at the Apache Software Foundation, one at the Linux Foundation), and all 
deliver the core capabilities above; they differ in design center and scope.
+
+**[Apache Hudi](https://hudi.apache.org)** originated at Uber in 2016 for 
fast, mutable ingestion — upserts, deletes, and incremental processing — on the 
data lake. Hudi is accurately described as a table format plus a storage 
engine: beyond the storage specification (timeline, file layout, metadata 
table), it ships a writer runtime with pluggable indexing for fast updates, and 
built-in [table services](/docs/hudi_stack) — compaction, clustering, cleaning, 
and indexing — that run inline  [...]
+
+**[Apache Iceberg](https://iceberg.apache.org)** originated at Netflix and was 
donated to the ASF in 2018. Iceberg's design center is an engine-agnostic 
specification for table metadata, with immutable snapshots, hidden partitioning 
(partition values derived from column transforms), and well-defined metadata 
evolution. It deliberately scopes itself to the format and libraries, leaving 
table maintenance to engines and vendors, and has attracted the broadest 
catalog and vendor ecosystem vi [...]
+
+**[Delta Lake](https://delta.io)** was created at Databricks and open-sourced 
in 2019; it is governed under the Linux Foundation. Delta centers on a 
JSON-based transaction log with periodic Parquet checkpoints, offering ACID 
transactions, schema enforcement, and time travel. Like Hudi, it has always 
been more than a format on its home platform, pairing the log with maintenance 
operations such as optimize and vacuum. It is the native format of Databricks 
and has first-class Spark integrat [...]
+
+**[Apache Paimon](https://paimon.apache.org)** is the newest of the four, 
grown out of the Flink Table Store subproject and a top-level Apache project 
since 2024. Paimon is narrower in scope than the other three: its design center 
is LSM-tree (log-structured merge tree) storage on the lake, optimizing write 
speed for high-frequency updates and changelog-producing tables consumed by 
Apache Flink, while remaining readable by batch engines like Spark and Trino. 
The trade-off is a classic on [...]
+
+An honest summary: the formats have converged substantially on core 
capabilities — ACID commits, schema evolution, time travel, statistics-based 
pruning — and continue to borrow ideas from one another. But write performance 
and read performance remain distinct problems with different trade-offs, even 
today: a design that makes writes cheap taxes readers unless compaction and 
read-side indexing keep pace, while one that optimizes purely for scans makes 
every update a rewrite. Because read [...]
+
+
+## How Query Engines Integrate
+
+A table format is only useful insofar as engines speak it. Integration happens 
at two levels.
+
+At the **data plane**, each format ships libraries or specifications that 
engines embed. Apache Spark and Apache Flink integrate with all four formats 
for both reading and writing; distributed SQL engines such as Trino and Presto 
ship native connectors. Cloud warehouses — Amazon Athena and Redshift, Google 
BigQuery, Snowflake — can query open table formats directly, with capabilities 
varying by format and vendor, and Python-native engines (DuckDB, Daft, Ray) 
extend the same reach to ligh [...]
+
+At the **control plane**, a catalog tracks which tables exist and points 
engines at each table's current metadata. Options range from the venerable Hive 
Metastore and AWS Glue Data Catalog to newer catalogs like Apache Polaris, 
Unity Catalog, and Apache Gravitino. The catalog is what turns `SELECT * FROM 
orders` into "resolve `orders` to a storage location and metadata pointer, then 
plan the scan." Hudi syncs its table metadata to these catalogs through catalog 
sync tools, keeping tables [...]
+
+## Interoperability: You Don't Have to Pick One Forever
+
+A common source of anxiety is that choosing a table format is a one-way door. 
Increasingly, it is not. [Apache XTable](https://xtable.apache.org) 
(incubating) is an open source project that translates table metadata between 
Hudi, Iceberg, and Delta Lake — in any direction — without copying or rewriting 
the underlying data files. Because all three formats ultimately describe 
Parquet files plus metadata, XTable can read one format's metadata and write 
out the equivalent for another, as a o [...]
+
+This is how major vendors ship interoperability today. XTable was co-launched 
by Microsoft, Google, and Onehouse before its donation to the ASF; [Microsoft 
OneLake](https://xtable.apache.org/docs/fabric/) in Fabric uses it under the 
hood to surface tables across Delta and Iceberg formats, and Onehouse uses it 
to keep customer tables continuously queryable as Hudi, Iceberg, and Delta at 
once. Databricks takes the same one-copy approach within Delta Lake through 
UniForm, which generates Ic [...]
+
+## Table Format vs File Format vs Lakehouse
+
+These three terms get conflated constantly, so a short disambiguation is worth 
the space.
+
+- A **file format** (Apache Parquet, ORC, Avro) defines how records are 
encoded *inside a single file* — columnar layout, compression, per-file 
statistics. It knows nothing about other files.
+- A **table format** (Hudi, Iceberg, Delta Lake, Paimon) defines how *many 
files plus metadata* form a versioned, transactional table. It sits directly 
above the file format.
+- A **data lakehouse** is the overall *architecture* that combines cloud 
object storage, open file formats, a table format, catalogs, and query engines 
into a warehouse-like platform on the lake. The table format is its 
load-bearing component, alongside storage engines/table services, catalogs, and 
compute.
+
+![Data lakehouse architecture](/assets/images/blog/dlh_new.png)
+<p align = "center">Figure: Where the table format sits in a lakehouse 
architecture</p>
+
+So Parquet is not a table format, and a table format alone is not a lakehouse 
— it is the layer that makes one possible. For the architectural view, see the 
companion post on [what a data lakehouse 
is](/blog/2024/07/11/what-is-a-data-lakehouse).
+
+## Conclusion
+
+An open table format — table metadata plus a transaction protocol — turns 
cheap, scalable object storage into a real analytical database substrate: files 
become tables, jobs become transactions, and a directory of Parquet becomes 
something you can update, evolve, audit, and query as of any point in time, 
from any engine that implements the open specification. Apache Hudi proved the 
idea in production first while building the transactional data lake; Apache 
Iceberg, Delta Lake, and Apache [...]
+
+## FAQ
+
+<PostFAQ heading={null} items={[
+  {question: 'What is an open table format?', answer: 'An open table format is 
an openly governed specification combining table metadata (which files form the 
table, their schema and statistics, at every version) with a transaction 
protocol (how writers commit atomically and readers see consistent snapshots). 
Together they turn files on cloud object storage into a database-like table 
with ACID transactions, schema evolution, and time travel. Apache Hudi, Apache 
Iceberg, Delta Lake, and A [...]
+  {question: 'Is Parquet an open table format?', answer: 'No. Apache Parquet 
is an open file format: it defines how records are encoded inside a single 
file. A table format like Apache Hudi or Apache Iceberg sits above Parquet and 
defines how many files plus transactional metadata form a versioned table.'},
+  {question: 'What is the difference between a table format and a file 
format?', answer: 'A file format defines the byte layout of one file, including 
encoding, compression, and per-file statistics. A table format defines how a 
collection of such files behaves as a single transactional table, tracking 
committed files, schema, versions, and statistics across the whole table. They 
are complementary layers, not alternatives.'},
+  {question: 'Which open table format should I use?', answer: 'It depends on 
your workloads, and note that write and read performance involve different 
trade-offs. Hudi is strongest for mutable, incremental ingestion at high 
volume, with built-in indexing for fast writes and automated compaction and 
clustering to keep reads fast; Iceberg emphasizes an engine-neutral 
specification with broad catalog support; Delta Lake offers deep Spark and 
Databricks integration; Paimon targets LSM-based [...]
+  {question: 'Do open table formats support ACID transactions?', answer: 'Yes. 
All major open table formats provide atomic commits and snapshot isolation for 
readers, so queries always see a consistent version of the table. They differ 
in how they handle concurrent writers, using techniques such as optimistic 
concurrency control and non-blocking concurrency mechanisms.'},
+  {question: 'Is Apache Hudi just a table format?', answer: 'No. Hudi includes 
an open table format, but pairs it with a built-in storage engine: a writer 
runtime with pluggable indexing plus table services such as compaction, 
clustering, and cleaning that keep tables optimized automatically. Hudi was 
also the first open table format in production, built at Uber in 2016 for its 
transactional data lake.'},
+]} />
diff --git a/website/blog/2026-07-15-what-is-upsert-on-a-data-lake.md 
b/website/blog/2026-07-15-what-is-upsert-on-a-data-lake.md
new file mode 100644
index 000000000000..0c3851ddaf19
--- /dev/null
+++ b/website/blog/2026-07-15-what-is-upsert-on-a-data-lake.md
@@ -0,0 +1,116 @@
+---
+title: "What is Upsert on a Data Lake?"
+excerpt: "An upsert updates a record if it exists and inserts it if it does 
not. This post explains why that was historically hard on data lakes, and how 
open table formats like Apache Hudi make it fast."
+description: "What an upsert is, why record updates were historically hard on 
data lakes, and how table formats like Apache Hudi implement fast, indexed 
upserts."
+authors: [vinoth-chandar]
+category: how-to
+image: 
/assets/images/blog/2023-05-10-top-3-things-you-can-do-to-get-fast-upsert-performance-in-apache-hudi.png
+tags:
+- upsert
+- dml
+- indexing
+- data lakehouse
+- cow
+- mor
+---
+
+An upsert is a write operation that updates a record if it already exists and 
inserts it if it does not — a single atomic operation that combines *update* 
and *insert*, keyed on a unique record identifier. Databases have offered this 
for decades (`MERGE` in SQL, `ON CONFLICT DO UPDATE` in PostgreSQL, `replace 
into` in MySQL). On a data lake, however, upserts were effectively impossible 
for most of the technology's history, and making them fast is still what 
separates data lake storage sy [...]
+
+## Why Data Lakes Couldn't Update Records
+
+Data lakes are built on files — typically columnar formats like Apache Parquet 
or ORC — sitting on distributed file systems or cloud object storage such as 
Amazon S3. Two properties of this foundation make record-level updates hard.
+
+First, the files themselves are immutable. A Parquet file is written once, 
with column chunks, compression, and statistics computed over the whole file; 
there is no way to reach into it and change one row in place. Second, the 
storage layer underneath offers no transactional semantics across files. Early 
cloud object stores were even only eventually consistent, so a reader listing a 
directory mid-write could see a partial, corrupt view of the table. There were 
no primary keys, no notion  [...]
+
+The Hive-era workaround was coarse-grained rewriting: partition the table 
(say, by day), and when any record in a partition changed, recompute and 
overwrite the *entire partition*. Pipelines re-read hours or days of data to 
apply what might be a handful of changed rows, then swapped directories and 
hoped no query was reading at that moment. This pattern wasted enormous 
compute, delayed data freshness from minutes to hours or days, and provided no 
isolation guarantees. It is precisely the [...]
+
+## How Open Table Formats Make Upserts Possible
+
+Open table formats — [Apache Hudi](https://hudi.apache.org), Apache Iceberg, 
and Delta Lake — introduced a transactional metadata layer on top of immutable 
files, turning a directory of Parquet files into a proper table. That metadata 
layer is what makes upserts possible, through a few key mechanisms (covered 
more broadly in [What is a Data 
Lakehouse?](/blog/2024/07/11/what-is-a-data-lakehouse)):
+
+- **Atomic commits.** Every write produces new file versions, and a commit 
protocol atomically publishes them. In Hudi, each write is an action on a 
[timeline](/docs/timeline); queries only ever see fully committed data, so a 
failed or in-flight write is invisible to readers.
+- **Snapshot isolation and versioning.** Because old file versions are 
retained until cleaned, readers see a consistent snapshot while writers produce 
the next one. Updating a record no longer risks corrupting a running query.
+- **Record keys.** Hudi brings the database notion of a primary key to the 
lake: every record has a key (record key, plus optionally a partition path) 
that uniquely identifies it. This is the precondition for "update if exists" to 
even be well defined.
+- **Fine-grained file management.** Instead of rewriting a whole partition, 
the system can identify exactly which files contain the affected records and 
rewrite — or log changes against — only those.
+
+With these pieces in place, an upsert on a data lake becomes what it is in a 
database: hand the system a batch of records, and it transactionally reconciles 
them against what is already stored. In Hudi, `upsert` is the [default write 
operation](/docs/write_operations#upsert): incoming records are tagged as 
inserts or updates via an index lookup, sized into files, written, and 
committed atomically. The target table never shows duplicates.
+
+## The Anatomy of an Upsert
+
+Understanding the cost of an upsert requires walking through what actually 
happens. Hudi's [write path](/docs/write_operations#write-path) breaks it into 
distinct steps.
+
+**1. Key definition and deduplication.** Each incoming record carries a record 
key. Since an input batch may itself contain multiple versions of the same key 
(common with change streams), records are first combined by key, keeping the 
winner according to configured ordering fields.
+
+**2. Index lookup.** The system must answer: *does this key already exist, and 
if so, in which file?* Hudi maintains a mapping from each record key to a *file 
group* — the unit of storage that holds all versions of a set of records. The 
[index lookup](/docs/indexes) tags each incoming record as an update (routed to 
its existing file group) or an insert (assigned to a new or under-sized file 
group). This step is the heart of the upsert, and — as the next section shows — 
the main determina [...]
+
+**3. Merge and write.** Tagged records are then written, and here the [table 
type](/docs/table_types) determines the strategy:
+
+- **Copy-on-Write (CoW)** rewrites the affected base files: the writer reads 
the current base file of each touched file group, merges in the updates, and 
writes a new versioned base file. Queries stay simple and fast — they read pure 
columnar data with zero merge overhead — but write amplification is high: 
updating even a few records in a file means rewriting the whole file. CoW suits 
read-heavy tables with moderate update rates.
+- **Merge-on-Read (MoR)** instead appends the changed records to compact delta 
log files attached to each file group, deferring the expensive base-file 
rewrite. Writes become lightweight and fast, enabling commits every few 
minutes; queries merge the logs with base files at read time, and a background 
[compaction](/docs/compaction) process periodically folds logs into new base 
files to restore pure-columnar read performance. MoR suits high-frequency 
updates and streaming ingestion, tradi [...]
+
+The trade-off is summarized well by the [comparison 
table](/docs/table_types#comparison) in the Hudi docs: CoW pays `O(file groups 
written)` in write amplification for zero read amplification; MoR pays 
`O(records changed)` on both sides.
+
+**4. Commit.** Finally, the write — new base files, new log blocks, and index 
updates — is committed atomically to the timeline, at which point queries can 
see it.
+
+## Why Indexing Is the Difference Between Minutes and Hours
+
+Step 2 above deserves its own section, because it is where naive upsert 
implementations fall down. Without an index, the only way to find which files 
contain the incoming keys is to *join the input batch against the entire table* 
— reading key columns from every file, every time you write. For a large table 
receiving a small trickle of updates, this means the cost of each write is 
proportional to the size of the table, not the size of the change.
+
+![Comparison of merge cost for updates against base files, with and without an 
index](/assets/images/blog/hudi-indexes/with_without_index.png)
+<p align = "center">Figure: Merge cost for updates (dark blue) against base 
files (light blue), with and without an index</p>
+
+Hudi treats [indexing](/docs/indexes) as an integral part of its storage 
engine and offers several index types tuned to different workloads:
+
+- **Bloom index**: stores bloom filters and key ranges (in file footers, or 
centrally in the metadata table) so most files can be pruned without reading 
data. Works very well when keys have some ordering — e.g., event tables with 
timestamp-prefixed keys — where range pruning eliminates most candidate files.
+- **Simple index**: a lean join against keys extracted from existing files; a 
better fit for random-update workloads like dimension tables, where bloom 
filters would return true for nearly every file anyway.
+- **Bucket index**: hashes keys directly to file groups, eliminating the 
lookup entirely at the cost of a fixed (or consistent-hashed) bucket layout.
+- **[Record Level Index](/blog/2023/11/01/record-level-index)**: a scalable, 
exact key-to-file-group mapping stored in Hudi's metadata table, sharded by 
hash. Instead of pruning candidate files probabilistically, it answers the 
location question with a point lookup, delivering order-of-magnitude speedups 
on large deployments where index lookup dominates write latency.
+
+The practical impact is dramatic. In a table with tens of thousands of files, 
an untagged upsert must consider all of them; a well-chosen index narrows the 
work to only the file groups that actually contain updated records. 
Merge-on-Read benefits doubly: the index bounds how many change records any 
base file must be merged against at query time, not just at write time. For 
concrete tuning guidance, see [Top 3 Things You Can Do to Get Fast Upsert 
Performance in Apache Hudi](/blog/2023/05/ [...]
+
+## What Upserts Unlock
+
+Fast record-level mutation is not a niche feature; it changes what a data lake 
can be used for.
+
+- **Change data capture (CDC) replication.** Streaming a database's change log 
(via Debezium, AWS DMS, or similar) into the lake requires applying inserts, 
updates, and deletes in order, continuously. Upserts make it possible to 
maintain a query-ready mirror of an operational database with minute-level 
freshness, instead of nightly full dumps.
+- **Compliance deletes (GDPR/CCPA).** "Delete this user's data" is a targeted 
mutation across potentially every partition of a table. With keyed 
[deletes](/docs/write_operations#delete) — implemented on the same indexed path 
as upserts — this becomes a routine operation rather than a table-rewriting 
project.
+- **Deduplication.** Event pipelines deliver duplicates; upserting by event 
key means the table enforces uniqueness on write, so consumers never need to 
deduplicate at query time.
+- **Mutable fact and dimension tables.** Late-arriving corrections, order 
status changes, transaction settlements, slowly changing dimensions — all 
become incremental updates against the affected records, rather than periodic 
partition rewrites.
+- **Incremental pipelines.** Because upserts record exactly what changed and 
when, downstream jobs can consume just the changes via [incremental 
queries](/docs/table_types#query-types), replacing full-table rescans with 
streaming-style processing.
+
+## How Hudi, Iceberg, and Delta Lake Handle Mutation
+
+All three major open table formats support upserts today, but with different 
designs and different cost profiles. Delta Lake and Iceberg expose mutation 
primarily through `MERGE INTO`: the engine joins the source batch against the 
target table to find affected files, then either rewrites them (copy-on-write) 
or, in newer versions, writes delete files/deletion vectors that mark rows as 
removed (merge-on-read style reads). Neither maintains a persistent 
record-level index, so locating affe [...]
+
+## Conclusion
+
+An upsert — update if the record exists, insert if it does not — is the 
operation that turns a data lake from an append-only archive into a live, 
continuously updated foundation for analytics. It was historically out of reach 
because lakes stored immutable files with no transactions, keys, or 
record-level metadata, forcing wasteful partition rewrites. Open table formats 
supplied the transactional layer; what determines real-world performance is 
everything layered on top: how records are  [...]
+
+## FAQ
+
+<PostFAQ heading={null} items={[
+  {
+    question: 'What is the difference between upsert and merge?',
+    answer: 'They achieve similar outcomes through different interfaces. MERGE 
is a SQL statement where you write an explicit join condition and specify what 
happens on match or no match, giving full control including conditional logic. 
Upsert is a keyed write operation: the system uses the table\'s record key to 
decide automatically whether each incoming record is an update or an insert. In 
Apache Hudi, upsert is the default write operation and uses an index to locate 
existing records,  [...]
+  },
+  {
+    question: 'Can you update a record in a Parquet file?',
+    answer: 'Not in place. Parquet files are immutable once written, so there 
is no way to modify a single row inside one. Table formats work around this by 
rewriting the affected file with the change applied, or by logging the change 
separately and merging it with the base file when the data is read. Either way, 
the update is expressed as new files plus a metadata commit, never as an 
in-place edit.',
+  },
+  {
+    question: 'Is upsert slow on a data lake?',
+    answer: 'It depends almost entirely on how the system finds existing 
records and how it applies changes. Without an index, every upsert must scan or 
join against the whole table to locate matching records, so write cost grows 
with table size. With an index such as Hudi\'s bloom index or record level 
index, the write only touches file groups that actually contain updated 
records. Choosing Merge-on-Read further reduces write latency by logging 
changes instead of rewriting columnar file [...]
+  },
+  {
+    question: 'What is the difference between upsert and insert in Apache 
Hudi?',
+    answer: 'Upsert performs an index lookup to tag each incoming record as an 
update or an insert, guaranteeing the table never shows duplicate keys. Insert 
skips the index lookup entirely, which makes it faster but allows duplicates 
unless the input is pre-deduplicated. Upsert is recommended for change data 
capture and other update-heavy workloads, while insert suits append-mostly data 
that can tolerate or separately handle duplicates.',
+  },
+  {
+    question: 'How do GDPR deletes work on a data lake?',
+    answer: 'A GDPR or CCPA delete request means removing specific records, 
identified by key, from potentially every partition of a table. Table formats 
implement this on the same path as upserts: an index lookup finds the files 
containing the keys, and the delete is applied either by rewriting those files 
or by logging delete markers that compaction later reconciles. Hudi supports 
both soft deletes, which null out non-key fields, and hard deletes, which 
remove the records entirely.',
+  },
+  {
+    question: 'Do Apache Iceberg and Delta Lake support upserts?',
+    answer: 'Yes. Both support row-level mutation, primarily through the SQL 
MERGE INTO statement, with copy-on-write file rewrites or merge-on-read style 
delete files and deletion vectors. The main design difference from Apache Hudi 
is that they do not maintain a persistent record-level index, so finding 
affected files relies on joining the source data against the table with 
statistics-based pruning. Hudi instead defines record keys as a table-level 
concept and maintains indexes that ma [...]
+  },
+]} />
diff --git a/website/blog/2026-07-16-what-is-incremental-etl-on-a-data-lake.md 
b/website/blog/2026-07-16-what-is-incremental-etl-on-a-data-lake.md
new file mode 100644
index 000000000000..c3811c6e6923
--- /dev/null
+++ b/website/blog/2026-07-16-what-is-incremental-etl-on-a-data-lake.md
@@ -0,0 +1,156 @@
+---
+title: "What is Incremental ETL on a Data Lake?"
+excerpt: "Incremental ETL is a pipeline pattern that processes only the data 
that changed since the last run. This post explains how it works, what the 
storage layer must provide, and how it compares to batch and stream processing."
+description: "Incremental ETL processes only changed data instead of 
recomputing entire tables. Learn how it works on a data lake and how Apache 
Hudi enables it."
+authors: [vinoth-chandar]
+category: how-to
+image: /assets/images/blog/incr-processing/image7.png
+tags:
+- incremental processing
+- etl
+- streaming
+- data lakehouse
+- cdc
+---
+
+Incremental ETL is a pipeline pattern that processes only the data that 
changed since the last run, instead of recomputing entire tables or partitions 
on every execution. Each run of an incremental pipeline consumes a delta — the 
records inserted, updated, or deleted after a known checkpoint — applies its 
transformation logic to just that delta, and merges the results into the target 
table. The pipeline's work is proportional to how much the data changed, not 
how large the tables have grown.
+
+That distinction matters because most data pipelines today still run as 
full-recompute batch jobs: every night (or every hour), they re-scan source 
tables end to end and rewrite the derived tables downstream, even when only a 
tiny fraction of the rows actually changed. As data volumes grow, that 
recomputation gets slower and more expensive at exactly the moment businesses 
are asking for fresher data.
+
+Incremental ETL occupies the middle ground between classic batch processing 
and full stream processing. It keeps the familiar economics and tooling of 
batch — scheduled jobs, SQL or DataFrame transformations, cheap [data 
lakehouse](/blog/2024/07/11/what-is-a-data-lakehouse) storage — while borrowing 
the core idea of stream processing: treat data as a continuous flow of changes, 
and process each change roughly once.
+
+## The Problem with Full Recomputation
+
+Consider a typical medallion-style warehouse or lakehouse: raw data lands in 
bronze tables, gets cleaned into silver tables, and is aggregated into gold 
tables for reporting. In a full-recompute design, each layer is rebuilt by 
scanning its inputs wholesale. Three problems compound as the tables grow:
+
+- **Cost.** A daily job that scans a multi-terabyte fact table to pick up one 
day's worth of changes does orders of magnitude more I/O and compute than the 
size of the change justifies. Joins are the worst offenders: rewriting a large 
joined table means re-shuffling both sides in full, every run. Organizations 
routinely spend a large share of their data platform budget on pipelines that 
mostly reprocess data they already processed yesterday.
+
+- **Latency.** If rebuilding a table takes four hours, the data in it can 
never be fresher than four hours plus the scheduling interval. Teams respond by 
running the job less often (making data staler) or throwing more compute at it 
(making it more expensive). Neither addresses the root cause: the work grows 
with table size rather than change size.
+
+- **Missed SLAs and cascading delays.** Derived pipelines form DAGs. When an 
upstream full-recompute job runs long, every downstream job either starts late 
or reads stale inputs. Late-arriving data makes this worse: when records for an 
old partition show up, the whole partition — and every downstream partition 
derived from it — must be recomputed, silently blowing through freshness SLAs.
+
+Stream processing solves the latency problem but replaces batch's cost profile 
with a different one: always-on clusters, state stores to manage, and a second 
storage system (typically a log store like Kafka) holding another copy of the 
data. For the large class of pipelines where "fresh within a few minutes" is 
sufficient, that operational overhead buys latency headroom no one uses.
+
+## What Makes ETL "Incremental"
+
+An ETL pipeline is incremental when both its input and its output are 
expressed as changes:
+
+1. **Change streams in.** Instead of scanning the full source table, the 
pipeline asks the storage layer a question a plain file listing cannot answer: 
*"give me all records that changed since commit X."* The result is a delta — 
new inserts, the latest values of updated records, and deletes — along with a 
new checkpoint to resume from next run.
+
+2. **Incremental writes out.** Instead of rewriting the target table, the 
pipeline applies its transformed delta using record-level operations — upserts 
and deletes — so only the affected file groups are rewritten. Crucially, this 
makes the *output* table itself an incremental source: the next pipeline in the 
DAG can consume its changes the same way.
+
+Because deltas flow in and deltas flow out, the pattern composes. A chain of 
incremental pipelines behaves like a topology of stream processors — each stage 
consuming a change stream and emitting one — except the "streams" are ordinary 
tables on cheap cloud storage, queryable by any engine at any time. This is the 
[duality between change logs and 
tables](/blog/2020/08/18/hudi-incremental-processing-on-data-lakes) that stream 
processing literature has long described, realized directly on  [...]
+
+The efficiency win is easy to see with numbers. If a 10 TB fact table receives 
50 GB of changes per hour, an hourly incremental pipeline processes 50 GB per 
run instead of 10 TB — a 200x reduction in data scanned, while *improving* 
freshness from daily to hourly.
+
+## What the Storage Layer Must Provide
+
+Incremental ETL is not primarily a compute-engine feature; it is a storage 
contract. Plain files in cloud storage — even columnar formats like Parquet — 
cannot tell a consumer what changed, and cannot accept record-level updates 
without rewriting whole files or partitions. Open table formats close this gap 
to varying degrees. Concretely, the storage layer must provide three primitives:
+
+- **Record-level change retrieval.** The table must be able to answer "what 
changed between time T1 and T2" efficiently, without scanning everything. This 
comes in two flavors: *incremental queries*, which return the latest value of 
each changed record (ideal for driving downstream upserts), and *CDC queries*, 
which return before/after images of every change (needed when the 
transformation must see each individual mutation). Apache Hudi supports [both 
query types](/docs/sql_queries#incre [...]
+
+- **Upserts and deletes to apply changes.** Producing a delta is only useful 
if the next table can absorb it cheaply. That requires primary keys and indexes 
to locate which files contain the affected records, so a small change touches a 
small amount of storage. Hudi's [write operations](/docs/write_operations) are 
built around indexed upserts and deletes, with merge modes to handle 
out-of-order and late-arriving data correctly.
+
+- **Ordering and commit points.** Consumers need a reliable notion of "where I 
left off." Hudi's [timeline](/docs/timeline) records every action on the table 
as an atomically committed instant; incremental queries use commit completion 
times to define exact, repeatable change windows, and checkpoints are managed 
automatically by tools like Hudi Streamer's incremental sources. Hudi 
additionally tracks record-level metadata (commit time and sequence number on 
every record), so change histo [...]
+
+These primitives — upsert and incremental consumption — are exactly the pair 
that defines the pattern: with them, you can maintain one table from a change 
stream, then derive another table from *its* change stream, end to end. The 
[use cases documentation](/docs/use_cases) covers how this plays out across 
ingestion, warehouse offload, and derived pipelines.
+
+### Frequent Table Maintenance Becomes the Norm
+
+There is a fourth requirement that follows directly from the first three: the 
format must be designed for frequent table maintenance, because incremental 
processing fundamentally increases how often the table is written. A table that 
once absorbed one nightly batch now commits every few minutes — producing small 
files, accumulating deltas in log files, and continuously churning the storage 
layout. At that write frequency, maintenance work — 
[compaction](/docs/compaction), clustering, cle [...]
+
+And it must run *without blocking the writers it serves*. If compacting or 
clustering a table means pausing ingestion, or if maintenance jobs contend with 
writers as just another optimistic transaction — conflicting and forcing 
retries on exactly the hot file groups that change most — then every 
maintenance pass reintroduces the latency and cost the pipeline went 
incremental to eliminate. Hudi builds table services into the storage engine 
for this reason: compaction, clustering, cleaning [...]
+
+## Incremental ETL vs. Stream Processing
+
+Incremental ETL and stream processing share semantics but differ sharply in 
infrastructure and economics:
+
+| | Stream processing (Flink, Kafka Streams) | Incremental ETL (Hudi on a data 
lake) |
+|---|---|---|
+| Latency | Sub-second to seconds | Minutes |
+| Compute model | Always-on jobs with managed state | Scheduled or continuous 
mini-batch jobs; state lives in tables |
+| Storage | Event log (Kafka) + state stores + eventual sink | One copy: 
lakehouse tables on cloud object storage |
+| Reprocessing/backfill | Replay the log, rebuild state | Re-run the job over 
a commit range or full table |
+| Query access to intermediate results | Requires sinking to another system | 
Every intermediate table is directly queryable |
+
+A useful way to summarize the trade: **incremental ETL delivers streaming 
semantics at minute-level batch economics.** If a use case genuinely needs 
sub-second reactions — fraud blocking, operational alerting — a stream 
processor is the right tool. But a large majority of analytics pipelines have 
freshness requirements in the minutes-to-hour range, where paying for always-on 
stream infrastructure and a second copy of the data buys nothing. The two also 
compose rather than compete: Flink  [...]
+
+## A Concrete Pipeline Walkthrough
+
+Here is the pattern end to end, using a familiar CDC scenario: an operational 
database (say, PostgreSQL) whose changes must feed an analytics table.
+
+**Step 1 — CDC into a bronze table.** A capture tool such as Debezium streams 
the database's change log into Kafka, and an ingestion job — for example [Hudi 
Streamer](/docs/hoodie_streaming_ingestion) — continuously upserts those 
changes into a bronze Hudi table. The bronze table is now a queryable, 
transactional mirror of the source, and every commit on its timeline is a 
consumable change point.
+
+**Step 2 — incrementally read the bronze table.** The silver pipeline wakes up 
on its schedule (or runs continuously) and pulls only what changed since its 
last checkpoint:
+
+```scala
+// Read only the records that changed since the last run
+val ordersDelta = spark.read.format("hudi").
+  option("hoodie.datasource.query.type", "incremental").
+  option("hoodie.datasource.read.begin.instanttime", lastCheckpoint).
+  load(bronzeOrdersPath)
+```
+
+The same read is available in SQL through the `hudi_table_changes` 
table-valued function:
+
+```sql
+SELECT * FROM hudi_table_changes('bronze_orders', 'latest_state', 
'20260716083000000');
+```
+
+**Step 3 — transform and upsert into a silver table.** The transformation runs 
over just the delta, and the result is applied as an upsert keyed on the record 
key:
+
+```scala
+val silverDelta = ordersDelta.
+  filter(col("order_status") =!= "CANCELLED").
+  withColumn("order_total", col("quantity") * col("unit_price"))
+
+silverDelta.write.format("hudi").
+  option("hoodie.datasource.write.operation", "upsert").
+  option("hoodie.table.name", "silver_orders").
+  mode("append").
+  save(silverOrdersPath)
+```
+
+The job then persists the new checkpoint (the latest commit completion time it 
consumed) for the next run. Because the silver table was written with upserts, 
it exposes its own change stream — a gold-layer aggregation job can consume 
`silver_orders` incrementally in exactly the same way, re-aggregating only the 
keys whose inputs changed.
+
+![Incremental ETL: upserts into an upstream table, incrementally consumed to 
maintain a derived projected 
table](/assets/images/blog/incr-processing/image7.png)
+<p align = "center">Figure: Changes are upserted into an upstream table and 
incrementally consumed to maintain a derived table</p>
+
+One honest caveat: not every transformation is trivially incremental. Simple 
projections, filters, and enrichments map one-to-one onto deltas. Aggregations 
require merging new partial results into existing ones, and some full-table 
operations (percentiles, complex window functions) may still warrant periodic 
full recomputation. A pragmatic design runs the same logic incrementally for 
freshness and occasionally in full for correction — on the same tables and 
infrastructure.
+
+## A Brief History: Pioneered at Uber
+
+Incremental processing on the data lake is not a recent idea bolted onto table 
formats — it is the problem Hudi was created to solve. In 2016, Uber's data 
team faced exactly the trade-off described above: petabyte-scale Hadoop tables 
that could only be rewritten wholesale, and business use cases (like 
driver-partner incentives and trip pricing) that needed data in minutes, not 
hours. The case for a third processing style — incremental, on top of batch 
storage — was laid out in the O'Reil [...]
+
+## Conclusion
+
+Incremental ETL replaces "recompute everything, always" with "process what 
changed, continuously." It requires a storage layer that can serve record-level 
change streams, absorb upserts and deletes efficiently, and expose reliable 
commit points to checkpoint against — the primitives Apache Hudi has provided 
on data lakes since its inception. The payoff is pipelines whose cost tracks 
the rate of change in the business rather than the accumulated size of history, 
with data freshness measur [...]
+
+## FAQ
+
+<PostFAQ heading={null} items={[
+  {
+    question: 'What is incremental ETL?',
+    answer: 'Incremental ETL is a data pipeline pattern that processes only 
the records that were inserted, updated, or deleted since the pipeline\'s last 
run, instead of re-scanning and rewriting entire tables. Each run consumes a 
delta from a checkpoint, transforms it, and merges the result into the target 
table with upserts and deletes. This makes pipeline cost proportional to the 
volume of change rather than total table size.'
+  },
+  {
+    question: 'What is the difference between incremental ETL and streaming?',
+    answer: 'Both process data as a flow of changes, but stream processors 
like Apache Flink run always-on jobs with managed state and typically require a 
separate event log such as Kafka, delivering sub-second latency. Incremental 
ETL runs as scheduled or continuous mini-batch jobs directly against lakehouse 
tables on cloud storage, delivering minute-level freshness at much lower 
infrastructure cost. A common summary is that incremental ETL provides 
streaming semantics at batch economics.'
+  },
+  {
+    question: 'How does incremental ETL reduce cost?',
+    answer: 'It eliminates repeated scans and rewrites of data that did not 
change. If a large table receives a small volume of changes between runs, an 
incremental pipeline reads and processes only that delta instead of the whole 
table, often reducing data scanned per run by orders of magnitude. Compute 
clusters also only run for the duration of each small job rather than staying 
on permanently.'
+  },
+  {
+    question: 'What does the storage layer need to support incremental ETL?',
+    answer: 'Four things: the ability to efficiently retrieve record-level 
changes between two points in time, support for upserts and deletes so 
downstream tables can absorb deltas cheaply, an ordered log of commits that 
consumers can checkpoint against, and table maintenance that runs without 
blocking writers — because incremental processing multiplies write frequency, 
compaction, clustering, and cleaning become continuous background work rather 
than occasional jobs. Plain Parquet file [...]
+  },
+  {
+    question: 'Can every transformation be made incremental?',
+    answer: 'No. Projections, filters, enrichments, and key-based joins map 
naturally onto change streams, and aggregations can be maintained by merging 
partial results for the affected keys. Some computations, like exact 
percentiles or complex full-table window functions, are hard to update 
incrementally, so teams often run those periodically in full while keeping the 
rest of the pipeline incremental.'
+  },
+  {
+    question: 'Which Apache Hudi features enable incremental ETL?',
+    answer: 'Hudi provides incremental queries that return the latest values 
of changed records after a given commit, CDC queries that return before and 
after images of each change, and indexed upsert and delete write operations to 
apply deltas downstream. Its timeline records every commit with completion 
times that define exact change windows, and record-level metadata preserves 
change history across compaction and clustering. Ingestion tools like Hudi 
Streamer manage incremental checkp [...]
+  }
+]} />
diff --git a/website/blog/2026-07-17-what-is-acid-on-a-data-lake.md 
b/website/blog/2026-07-17-what-is-acid-on-a-data-lake.md
new file mode 100644
index 000000000000..237c64225182
--- /dev/null
+++ b/website/blog/2026-07-17-what-is-acid-on-a-data-lake.md
@@ -0,0 +1,128 @@
+---
+title: "What is ACID on a Data Lake?"
+excerpt: "What atomicity, consistency, isolation and durability actually mean 
for tables on object storage, and how lakehouse table formats like Apache Hudi 
deliver them."
+description: "What ACID means for tables on S3, GCS or ADLS: how table formats 
like Apache Hudi deliver atomic commits, snapshot isolation and safe concurrent 
writes."
+authors: [sivabalan]
+category: how-to
+image: /assets/images/blog/acid.png
+tags:
+- acid
+- concurrency control
+- hudi timeline
+- data lakehouse
+---
+
+ACID on a data lake means that writes to tables stored on object storage are 
Atomic, Consistent, Isolated and Durable — the same transactional guarantees a 
database provides, applied to files in S3, GCS or ADLS. That sentence is easy 
to say and surprisingly hard to deliver. A database owns its storage engine end 
to end; a data lake is a pile of Parquet files on an object store that offers 
durable single-object PUTs and nothing more. There is no multi-file atomic 
operation, no rename that [...]
+
+This post walks through what each of the four letters means concretely at the 
file level, how lakes worked before table formats existed, how formats like 
[Apache Hudi](https://hudi.apache.org) deliver these guarantees, and — just as 
important — what ACID on a lake does *not* promise.
+
+## What A, C, I and D Mean on Object Storage
+
+The textbook definitions of ACID were written for databases processing many 
small transactions. On a data lake, the transactions are large batch or 
streaming writes that produce files, so it is worth restating each property in 
file terms.
+
+**Atomicity** means a write either publishes completely or not at all. 
Consider a Spark job writing 500 Parquet files into a table and dying at file 
320 — an executor is lost, a spot instance is reclaimed, someone kills the job. 
Atomicity requires that those 320 orphaned files are never visible to any 
query, and that the failed write can be cleanly rolled back. Without it, every 
downstream consumer inherits a partially written dataset and no reliable way to 
detect it.
+
+**Consistency** means every write moves the table from one valid state to 
another valid state. On a lake this covers schema enforcement (a write with an 
incompatible schema is rejected rather than quietly producing unreadable 
files), key constraints like upsert semantics (a record with the same key 
updates in place rather than duplicating), and the invariant that the table's 
metadata always describes exactly the set of files that constitute the table.
+
+**Isolation** means concurrent readers and writers do not observe each other's 
intermediate states. Two failure modes matter here. First, two writers 
targeting the same partition at the same time — say, a backfill job and a 
streaming ingest job — must not interleave their files such that the table ends 
up with a mix of both writes' partial output. Second, a reader that starts 
listing files mid-write must not see half of a commit; it should see the table 
exactly as it was at some committe [...]
+
+**Durability** means a committed write survives failures. Object stores 
already provide excellent durability for individual objects — S3, GCS and ADLS 
replicate objects across zones. The lake-specific part of durability is that 
the *commit record itself* is persisted to the same durable storage, so that 
"was this write committed?" has a crash-proof answer, and a committed 
transaction is never silently rolled back by a subsequent cleanup process.
+
+## Life Before ACID on Data Lakes
+
+The Hadoop and early cloud-lake era answered these problems with conventions, 
not guarantees. It is worth remembering how fragile those conventions were, 
because they explain why table formats exist.
+
+The classic Hive pattern was the **partition swap**: write data into a staging 
directory, then "atomically" move it into place and update the Hive metastore 
partition location. On HDFS a directory rename was genuinely atomic, so this 
mostly worked. On object stores there are no directories and no atomic rename — 
a "rename" is a copy-then-delete of every object — so the same pattern became a 
slow, non-atomic window during which readers could see anything.
+
+The **`_SUCCESS` file** convention had MapReduce and Spark drop an empty 
marker file when a job finished, and downstream jobs would poll for it. This 
signaled completion but protected nothing: readers that listed the directory 
directly saw partial data, a rerun of a failed job could mix old and new files, 
and there was no rollback story at all — cleanup after a failed job meant a 
human with a delete command deciding which files were orphans.
+
+Layered on top of this, early S3 offered only **eventual consistency** for 
listings, so even a fully written directory might not be fully *visible* yet. 
Whole projects (S3Guard, committers with DynamoDB bookkeeping) existed just to 
paper over that. S3 became strongly consistent in 2020, which removed one class 
of bugs — but strong single-object consistency still does not give you 
multi-file atomicity, isolation between writers, or rollback. Those require a 
transaction log.
+
+## How Table Formats Deliver ACID
+
+The shared insight behind Apache Hudi, Apache Iceberg and Delta Lake is that 
the table's state should be defined by a **metadata log with an atomic publish 
step**, not by whatever files happen to be in a directory. Data files are 
written first, invisibly; then a single, small metadata operation makes them 
visible all at once. Because that final operation is one atomic object-store 
action (a single PUT, or a conditional/compare-and-swap write), the entire 
multi-file write inherits its atomicity.
+
+In Hudi, that log is the [timeline](/docs/timeline) — an ordered event log of 
every action performed on the table, stored under the table's 
`.hoodie/timeline` directory. Each write is an action that moves through 
explicit states: `REQUESTED` (the write is planned), `INFLIGHT` (data files are 
being produced), and `COMPLETED` (the write is published). The transition to 
`COMPLETED` is a single atomic object-store operation, and Hudi timestamps 
every action with monotonically increasing, [Tr [...]
+
+![Actions in the Hudi timeline](/assets/images/hudi-timeline-actions.png)
+<p align = "center">Figure: Writes and table services recorded as actions on 
the Hudi timeline</p>
+
+This structure delivers each letter directly:
+
+- **Atomicity**: our 500-file Spark job that dies at file 320 leaves an 
`INFLIGHT` action on the timeline and 320 uncommitted files that no query will 
ever read, because queries resolve visible files through completed timeline 
actions — never by listing directories. A rollback action later removes the 
orphaned files. Nothing partial is ever served.
+- **Consistency**: the timeline is the single source of truth for table state, 
and schema and key constraints are enforced as part of the write before the 
commit is published.
+- **Isolation**: readers get **snapshot isolation**. A query binds to the set 
of completed commits as of its start and sees exactly that state for its entire 
duration, no matter how many writes complete concurrently. Multi-version 
concurrency control (MVCC) keeps prior file versions around so long-running 
queries are never yanked out from under.
+- **Durability**: the commit metadata lives on the same replicated object 
storage as the data, so a completed instant is as durable as the data it 
describes.
+
+For a deeper walkthrough of how the timeline underpins each transaction state 
transition, see the earlier post [Hoodie Timeline: Foundational pillar for ACID 
transactions](/blog/2023/07/09/Hoodie-Timeline-Foundational-pillar-for-ACID-transactions).
+
+To be fair to the broader ecosystem: Apache Iceberg and Delta Lake achieve 
ACID through the same fundamental idea with different mechanics. Iceberg builds 
immutable snapshot trees of manifest files and commits by atomically swapping a 
pointer to the new table metadata (typically via a catalog's compare-and-swap). 
Delta Lake maintains a JSON transaction log (`_delta_log`) where writing log 
entry *N+1* is the atomic commit point, using conditional writes on the object 
store. All three give [...]
+
+## Concurrency Control Models
+
+Atomic commits solve the single-writer case. The harder question — and where 
table formats differ most — is what happens when multiple processes write to 
the same table. Hudi's [concurrency control](/docs/concurrency_control) 
documentation lays out a spectrum of models:
+
+**Single writer with table services.** Most pipelines have one writer, plus 
background table services (compaction, clustering, cleaning) that rewrite data 
for performance. Hudi distinguishes these by design: table services run 
lock-free alongside the writer, coordinated through the timeline via MVCC, 
either inline or fully async in the same process. For this very common case, no 
external lock infrastructure is needed at all.
+
+**Optimistic concurrency control (OCC).** When genuinely separate jobs write 
to one table — a streaming ingest plus a batch backfill, say — Hudi supports 
OCC at file-group granularity. Each writer proceeds without blocking, and at 
commit time takes a short-lived distributed lock to check whether a 
concurrently completed commit touched the same files. Non-overlapping writes 
both succeed; overlapping writes cause one to abort and retry. The lock is held 
only around the commit-time critical [...]
+
+![Optimistic concurrency 
control](/assets/images/blog/concurrency_control/OCC.png)
+<p align = "center">Figure: Optimistic concurrency control — conflicts 
detected at commit time</p>
+
+OCC works well when conflicts are rare, but it has an honest weakness for 
streaming: two high-frequency writers that routinely touch the same file groups 
will repeatedly abort each other, wasting compute and starving progress.
+
+**Non-blocking concurrency control (NBCC).** For multi-writer streaming, Hudi 
supports a mode where multiple writers can write into the *same* file groups 
simultaneously without aborting each other. Instead of preventing the conflict, 
resolution is deferred: commits are ordered by completion time on the timeline, 
and the query reader and the compactor merge overlapping writes 
deterministically. A lock is needed only for the instant-metadata write to the 
timeline itself. NBCC currently ta [...]
+
+## What ACID on a Data Lake Does Not Mean
+
+Honesty matters here, because "ACID" invites comparisons to OLTP databases 
that lakes are not designed to win.
+
+- **It is not OLTP.** Commit latency on a lakehouse is measured in seconds — 
an object-store round trip plus metadata work — not the milliseconds of a 
transactional database. Lakehouse transactions are designed for batches and 
micro-batches of records, not single-row point writes at high QPS.
+- **Transactions span one table.** The unit of atomicity in Hudi (and its 
peers, by and large) is a single table's commit. Multi-table transactions, 
cross-table foreign keys, and interactive multi-statement transactions with 
`BEGIN`/`COMMIT` semantics are not part of the model.
+- **Isolation is snapshot isolation, not serializability of arbitrary 
read-write transactions.** Writers are serialized against each other per table, 
and readers see consistent snapshots — which is exactly what analytical 
workloads need — but you should not port an OLTP application's locking 
assumptions onto a lake.
+- **ACID does not fix bad pipelines.** Atomic commits guarantee that what was 
written is either fully visible or invisible; they do not validate that the 
data itself was correct.
+
+If your workload is high-QPS point reads and writes, use an operational 
database and land the changes onto the lake via CDC — which, conveniently, is a 
workload where lake ACID shines.
+
+## Why It Matters
+
+These guarantees are not academic; they are what make several load-bearing 
patterns possible at all.
+
+**CDC correctness.** Replicating a database table into the lake via change 
data capture means a continuous stream of upserts and deletes. Without atomic 
commits and key-based upserts, a mid-batch failure leaves the lake copy in a 
state the source database never had — and there is no way to reconcile. With 
ACID, every commit is an all-or-nothing application of a batch of changes, and 
incremental consumers downstream see change batches in a well-defined order, 
never partially.
+
+**Concurrent ingestion and table services.** Real tables need continuous 
compaction, clustering and cleaning to stay queryable. Running those alongside 
ingestion without transactions means either stopping the world or risking 
corruption. MVCC on the timeline lets Hudi rewrite data in the background while 
writers write and readers read, with each process pinned to a consistent 
snapshot.
+
+**Regulatory deletes.** GDPR and CCPA erasure requests require deleting 
specific records from immutable files — physically, a read-modify-rewrite of 
affected files. ACID makes each deletion pass atomic and auditable: the 
timeline records exactly when the delete committed, queries never see a 
half-deleted state, and the commit history serves as evidence of compliance.
+
+## Conclusion
+
+ACID on a data lake is a commit protocol over object storage: data files are 
written invisibly, then published through one atomic metadata operation, with 
snapshot isolation for readers and explicit concurrency control between 
writers. Hudi implements this with its [timeline](/docs/timeline) — an ordered, 
durable event log of every action on the table — plus a spectrum of 
[concurrency control](/docs/concurrency_control) modes ranging from lock-free 
single-writer deployments to OCC and no [...]
+
+## FAQ
+
+<PostFAQ heading={null} items={[
+  {
+    question: 'Does S3 support ACID transactions?',
+    answer: 'Not by itself. S3 provides durable, strongly consistent 
operations on individual objects, but no atomic operation spanning multiple 
objects and no isolation between concurrent writers. ACID transactions on S3 
come from a table format layered on top, such as Apache Hudi, Apache Iceberg or 
Delta Lake, which publishes multi-file writes through a single atomic metadata 
commit.'
+  },
+  {
+    question: 'Is a data lake ACID compliant?',
+    answer: 'A raw data lake of Parquet or ORC files is not ACID compliant: 
readers can observe partial writes, concurrent jobs can corrupt each other, and 
failed jobs leave orphaned files. A data lake becomes ACID compliant when 
tables are managed by a transactional table format, at which point it is 
usually called a lakehouse.'
+  },
+  {
+    question: 'How does Hudi guarantee atomicity?',
+    answer: 'Hudi records every write as an action on its timeline, moving 
through requested, inflight and completed states. Data files are written first 
but remain invisible; the transition to completed is a single atomic 
object-store operation. Queries only read files referenced by completed 
actions, so a failed write is never visible and is later rolled back cleanly.'
+  },
+  {
+    question: 'What is snapshot isolation on a data lake?',
+    answer: 'Snapshot isolation means every query reads the table as of a 
specific committed state and sees exactly that state for its entire run, even 
while new writes commit concurrently. Table formats implement it with 
multi-version concurrency control, keeping prior file versions available until 
no reader needs them.'
+  },
+  {
+    question: 'Can multiple jobs write to the same lakehouse table at once?',
+    answer: 'Yes, with concurrency control. Hudi supports optimistic 
concurrency control, where writers proceed in parallel and conflicts on 
overlapping files are detected at commit time using a short-lived lock, and 
non-blocking concurrency control, where multiple writers can even target the 
same file group and conflicts are resolved at read and compaction time.'
+  },
+  {
+    question: 'Is ACID on a data lake the same as ACID in a database like 
PostgreSQL?',
+    answer: 'The guarantees are the same in kind but different in scope and 
latency. Lakehouse transactions cover batches of records within one table and 
commit in seconds, whereas an OLTP database offers millisecond, 
multi-statement, often multi-table transactions. Lakes are built for analytical 
and streaming workloads, not high-QPS point reads and writes.'
+  }
+]} />
diff --git a/website/static/llms.txt b/website/static/llms.txt
index 138ed0fcc51f..72ac2077ebd7 100644
--- a/website/static/llms.txt
+++ b/website/static/llms.txt
@@ -39,6 +39,10 @@ It can also synchronize your data to half dozen data 
catalogs to keep table cons
 - [Apache Hudi: 21 Unique 
Differentiators](https://hudi.apache.org/blog/2025/03/05/hudi-21-unique-differentiators):
 The technical crux of what sets Hudi apart — indexing, non-blocking 
concurrency control, async compaction, read-side indexes and built-in table 
services.
 - [Apache Hudi 1.2: The Open Lakehouse for AI and Multimodal 
Data](https://hudi.apache.org/blog/2026/06/07/apache-hudi-release-1-2-announcement):
 First-class support for vectors, blobs/unstructured data, and semi-structured 
data on the lakehouse — for Spark pipelines doing unstructured data processing 
or generating embeddings.
 - [What is a Data Lakehouse & How does it 
Work?](https://hudi.apache.org/blog/2024/07/11/what-is-a-data-lakehouse): The 
lakehouse architecture explained — evolution, components, use cases and key 
technologies.
+- [What is an Open Table 
Format?](https://hudi.apache.org/blog/2026/07/14/what-is-an-open-table-format): 
How open table formats like Hudi, Iceberg and Delta Lake bring database 
semantics to files on object storage.
+- [What is Upsert on a Data 
Lake?](https://hudi.apache.org/blog/2026/07/15/what-is-upsert-on-a-data-lake): 
Record-level updates and deletes on immutable cloud storage, and how Hudi makes 
them fast.
+- [What is Incremental ETL on a Data 
Lake?](https://hudi.apache.org/blog/2026/07/16/what-is-incremental-etl-on-a-data-lake):
 Processing only the data that changed instead of recomputing whole tables.
+- [What is ACID on a Data 
Lake?](https://hudi.apache.org/blog/2026/07/17/what-is-acid-on-a-data-lake): 
What atomicity, consistency, isolation and durability mean when your database 
is a bucket of files.
 
 ## Blog — Migration guides
 

Reply via email to