You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hudi.apache.org by vi...@apache.org on 2026/07/14 20:33:21 UTC

(hudi) branch asf-site updated: docs(blog): explainer wave 1: open table format, upsert, incremental ETL, ACID (#19269)

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 <vi...@users.noreply.github.com>
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