06.3 Delta Lake ACID Transactions, Concurrency & Isolation
The transaction log explains how Delta records table state. This topic explains how Delta safely changes that state when multiple readers and writers operate concurrently.
Databricks documents ACID guarantees for Delta tables and uses optimistic concurrency control. Reads use snapshot isolation, while writes use WriteSerializable isolation by default. Serializable is available when stronger guarantees are required. Databricks — ACID guarantees Databricks — Isolation levels
1. The architect mental model
Think of a Delta write as:
READ SNAPSHOT ↓ PLAN CHANGES ↓ WRITE NEW FILES ↓ VALIDATE AGAINST NEW COMMITS ↓ COMMIT OR FAIL
There is no traditional long-held database lock around the whole operation.
Delta uses optimistic concurrency control (OCC): assume conflicts are uncommon, let work proceed, then validate before commit. Delta Lake concurrency control
2. What ACID means in Delta
Atomicity
A transaction either becomes visible completely or does not become part of the table state.
Consistency
Committed table states obey the Delta table protocol, schema and transaction rules.
Isolation
Concurrent operations are separated according to the table's isolation semantics.
Durability
Committed transaction state is persisted in the transaction log and cloud object storage.
Databricks explains that the transaction log controls commit atomicity: data files are written first, and only become visible to readers when the corresponding transaction-log commit is recorded. Databricks — ACID guarantees
3. Atomicity: why partial files do not corrupt the table
Suppose a job intends to create five files:
file A file B file C file D file E
The job writes these files to object storage.
If the transaction fails before commit, those files are not logically part of the Delta table.
The critical boundary is:
Physical file write ≠ Logical table commit
Only the successful transaction-log commit makes the new files visible as table state.
This is why a failed transaction can leave untracked physical files without corrupting the logical table. VACUUM can eventually remove eligible untracked files. Databricks — ACID guarantees
4. Isolation: two different questions
When people say isolation, ask two questions:
- What does a reader see?
- Which concurrent writes are allowed to commit?
Databricks separates these concerns.
READS ↓ Snapshot isolation
WRITES ↓ WriteSerializable by default or Serializable
This distinction is essential for architect-level discussions.
5. Snapshot isolation for reads
A reader gets a consistent table snapshot.
Imagine:
Reader starts at v10 Writer commits v11 Writer commits v12
The reader can continue working against its original snapshot rather than seeing a half-applied mixture of versions.
Conceptually:
Reader R │ └── Snapshot(v10) │ └── stable logical state
Meanwhile the table can advance to v11 and v12.
Databricks documents snapshot isolation for reads. Databricks — Isolation levels
6. Optimistic concurrency control
Delta OCC has three major phases.
Phase 1 — Read
The transaction reads a table snapshot if its operation requires reading the target table.
It identifies which data it may need to modify.
Phase 2 — Write
The transaction writes new Parquet/data files.
These files are not yet visible as committed table state.
Phase 3 — Validate and commit
Before committing, Delta checks whether relevant concurrent changes occurred after the transaction's read snapshot.
If no conflict is detected:
COMMIT SUCCESS ↓ new table version
If conflict is detected:
COMMIT REJECTED ↓ conflict exception
This protects table integrity without requiring long-lived locks. Delta Lake concurrency control
7. Example: two concurrent writers
Start with:
Version 10 customer_id 100 → amount 500
Two jobs start at roughly the same time.
Job A reads v10 Job B reads v10
Job A updates customer 100. Job B also updates customer 100.
Both may initially write new files.
At validation time, Delta detects that both transactions affect overlapping table state.
One transaction can commit; the other must fail and retry or be handled by the application.
A: READ v10 → WRITE → COMMIT v11 ✓ B: READ v10 → WRITE → VALIDATE ✗
The important point is: the failed writer does not silently corrupt the table.
8. WriteSerializable — the default
Databricks uses WriteSerializable as the default write isolation level.
It guarantees that committed writes can be ordered consistently, while allowing more concurrency than full Serializable isolation.
A subtle but important example is concurrent DELETE and INSERT.
Suppose both start from v0:
t0: DELETE starts t1: INSERT starts t2: INSERT commits v1 t3: DELETE commits v2
Under WriteSerializable, the DELETE can still commit because the resulting behavior can be explained by a serial ordering in which the INSERT logically occurred after the DELETE, even though the physical commit history shows INSERT at v1 and DELETE at v2.
This can surprise engineers who assume history order is always equivalent to serial execution order. Databricks — Isolation levels
9. Serializable isolation
Serializable is the strongest supported isolation level.
It requires the committed operations to correspond to a valid serial execution that also constrains reads.
Use it when the business requirement cannot tolerate the weaker WriteSerializable behavior.
Example: if a DELETE must guarantee that every matching row existing at its commit boundary is removed, a conflicting concurrent INSERT may need to cause the DELETE to fail rather than allowing the weaker ordering.
Configure it with:
ALTER TABLE my_table SET TBLPROPERTIES ('delta.isolationLevel' = 'Serializable');
Serializable provides stronger guarantees but can reduce write concurrency. Databricks — Isolation levels
10. WriteSerializable vs Serializable
| Concern | WriteSerializable | Serializable |
|---|---|---|
| Default | Yes | No |
| Write ordering | Serializable | Serializable |
| Read serializability | No | Yes |
| Concurrent append flexibility | Higher | Lower |
| Throughput | Generally higher | Generally lower |
| Use case | Most workloads | Stronger business consistency |
Architect rule: do not switch to Serializable simply because it sounds safer. First identify the business anomaly you are trying to prevent.
11. Conflict taxonomy
Common concurrency conflicts include:
- write-write conflict
- write-read conflict
- phantom conflict
- metadata conflict
- protocol conflict
- concurrent streaming checkpoint conflict
Databricks documents these transaction-conflict categories and their relationship to isolation levels. Databricks — Transactions
12. Write-write conflict
Two transactions modify overlapping data.
Example:
Job A: UPDATE customer 100 Job B: DELETE customer 100
They cannot both blindly commit their independent versions of the same logical state.
The conflict must be detected and one operation must be retried or rejected.
13. Write-read conflict
A transaction reads data and another transaction modifies data that matters to the first transaction.
Databricks documents write-read conflicts as applying to Serializable isolation.
Architecturally:
T1 reads X T2 changes X T1 attempts commit
Serializable can reject T1 if the changed data invalidates the serial execution requirements. Databricks — Transactions
14. Phantom conflicts
A phantom occurs when another transaction adds rows matching a predicate that another transaction has read.
Example:
T1: DELETE WHERE amount > 1000 T2: INSERT amount = 2000
The inserted row may satisfy T1's predicate even though T1 could not see it in its original snapshot.
Databricks documents phantom-read conflicts for both WriteSerializable and Serializable transaction behavior. Databricks — Transactions
15. Metadata conflicts
Not all conflicts are about data rows.
Metadata changes can include:
- schema changes
- table property changes
- protocol changes
- feature enablement
- feature removal
A metadata change can cause concurrent operations to fail.
For example:
Job A → INSERT Job B → ALTER TABLE ADD COLUMNS
The schema/protocol transition can invalidate assumptions made by Job A.
Databricks recommends changing table properties when concurrent writes are not running. Table properties reference
16. Protocol conflicts
A table feature can change the protocol requirements.
Conceptually:
Writer A uses protocol vX Writer B upgrades table feature ↓ protocol changes ↓ Writer A may need to restart/retry
A ProtocolChangedException can occur when a table protocol is upgraded while another operation is working.
Protocol changes are therefore platform-level events, not ordinary row-level updates. Delta concurrency control
17. Row-level concurrency
Modern Databricks supports row-level concurrency for qualifying tables.
The key benefit is that two operations affecting different rows in the same underlying files can have fewer conflicts than traditional file-level conflict detection.
Conceptually:
Traditional file-level model File F ├── row A ├── row B └── row C ↓ update A + update C may conflict
With row-level conflict detection, the system can reason at a finer granularity for supported workloads.
Databricks documents different conflict matrices for tables with and without row-level concurrency. Databricks — Row-level concurrency
18. Row-level concurrency is not magic
It does not mean every concurrent write is conflict-free.
Metadata changes remain special.
Feature requirements and table design still matter.
There can also be performance trade-offs because finer-grained conflict detection adds work.
Architect rule: design for low-conflict workloads first; treat row-level concurrency as an additional capability, not a substitute for workload design.
19. Conflict exceptions
Common exceptions include:
ConcurrentAppendException
A concurrent operation added files in data your operation reads.
ConcurrentDeleteReadException
A concurrent operation deleted a file your transaction read.
ConcurrentDeleteDeleteException
Two operations attempted to delete/rewrite overlapping files.
MetadataChangedException
Concurrent metadata changed.
ProtocolChangedException
Concurrent protocol/table-feature changes occurred.
ConcurrentTransactionException
Commonly associated with multiple streaming queries using the same checkpoint location concurrently.
The exact exception and circumstances should be diagnosed from the operation, table layout, isolation level, and conflicting commit. Delta concurrency control
20. Partitioning can reduce conflicts
Suppose a table is partitioned by business_date.
Job A:
UPDATE ... WHERE business_date = '2026-11-07'
Job B:
UPDATE ... WHERE business_date = '2026-11-08'
If the operations are restricted to disjoint partitions, conflict probability can be much lower.
However, do not partition blindly by high-cardinality columns. Excessive partitions create many small directories/files and can hurt performance.
Databricks recommends aligning partitioning with common conflict-avoidance predicates when appropriate. Delta concurrency control
21. Explicit predicates matter
A common MERGE anti-pattern is to specify only the business key:
ON s.id = t.id
If the target table is partitioned by date and country, the engine may need to consider a much larger target area than necessary.
A more targeted condition can include known partition boundaries:
ON s.id = t.id AND t.business_date = '2026-11-07' AND t.country = 'IN'
This can reduce both scan work and concurrency conflicts when those predicates genuinely describe the workload.
Databricks specifically documents explicit partition filters as a conflict-reduction technique. Row-level concurrency
22. Append-only writes are special
A pure append can often commit without reading the existing table state.
Conceptually:
Source batch ↓ create new files ↓ validate schema/protocol ↓ commit add actions
This is one reason append-heavy Bronze ingestion can scale well.
Once the operation needs to inspect target rows—for example a conditional MERGE—the concurrency characteristics change.
Databricks documents conditions under which INSERT/append operations do not read the target table state before committing. Databricks — Isolation levels
23. Retry strategy
A concurrency failure is not necessarily a system failure.
For transient write conflicts, an application can often:
- Capture the conflict
- Wait with bounded backoff
- Re-read the latest snapshot
- Recompute the change set if necessary
- Retry
- Stop after a controlled retry limit
- Alert if repeated conflicts continue
Do not blindly retry a MERGE without understanding its business semantics.
A retry must be safe and idempotent.
24. Idempotency and retries
Suppose a source event is processed twice.
Bad design:
retry → duplicate business record
Better design:
deterministic business key + idempotent MERGE/write pattern + transaction semantics
Concurrency control protects the table; application-level idempotency protects the business result.
25. Concurrency architecture for an enterprise lakehouse
A scalable pattern is:
Source systems ↓ Bronze append-only ↓ Silver controlled upserts ↓ Gold serving tables
Reduce the number of independent writers targeting the same Gold table.
Prefer:
many independent Bronze writers ↓ controlled transformation layer ↓ predictable Gold writer topology
This reduces operational contention and makes failures easier to reason about.
26. Streaming concurrency
A particularly dangerous anti-pattern is starting two streaming queries with the same checkpoint location.
Conceptually:
Stream A ─┐ ├── same checkpoint ── ✗ Stream B ─┘
Each streaming query should have its own checkpoint location and ownership boundary.
Databricks documents ConcurrentTransactionException for this situation. Delta concurrency control
27. Multi-cluster writes on cloud storage
Modern Databricks supports concurrent Delta writes across multiple clusters/workspaces, subject to the storage and writer compatibility requirements.
For S3, current Databricks documentation notes that Databricks automatically selects the transaction-log commit mechanism and supports concurrent writes from multiple clusters/workspaces; custom/external writers must use compatible atomic commit coordination. Databricks — Delta Lake S3 limitations
Architect implication: transactional guarantees depend on the entire writer ecosystem, not only the Spark code.
If an external engine writes directly into a Delta table, verify its Delta protocol and commit compatibility before allowing concurrent writes.
28. Multi-table transactions
Modern Databricks also supports transactions spanning multiple statements and multiple tables when the required catalog-commit capability is enabled.
Conceptually:
BEGIN ATOMIC table A change table B change table C change END
The goal is an all-or-nothing logical transaction boundary across the participating tables.
Databricks documents multi-table transactions and catalog commits separately from ordinary single-table Delta transactions. Databricks — Transactions
29. Concurrency vs performance
Stronger consistency is not free.
There is a spectrum:
More concurrency ←────────────→ Stronger isolation
WriteSerializable generally offers higher concurrency. Serializable can reject more operations and therefore increase retry pressure or reduce throughput.
Row-level concurrency can reduce conflicts for supported workloads but can add conflict-detection overhead.
Architect decision should therefore consider:
- business correctness
- write frequency
- conflict rate
- table size
- partitioning
- latency SLA
- retry tolerance
- downstream expectations
30. Troubleshooting workflow
When a Delta write fails with a concurrency exception, do not immediately change configuration.
Use this sequence:
Step 1 — Identify the exception
ConcurrentAppend? ConcurrentDeleteRead? MetadataChanged? ProtocolChanged?
Step 2 — Identify both operations
Which job/table/task was writing concurrently?
Step 3 — Compare predicates
Did both operations read or modify the same partitions/files/rows?
Step 4 — Check isolation level
WriteSerializable or Serializable?
Step 5 — Check table design
Partitioning? Liquid clustering? Row-level concurrency eligibility?
Step 6 — Check writer topology
Multiple jobs? Multiple workspaces? External writer?
Step 7 — Decide
Serialize, narrow predicates, redesign ownership, retry safely, or change isolation only when justified.
31. Hands-on Lab — Simulate competing writers
Create a Delta table with a business key and date column.
Launch two jobs that update overlapping keys concurrently.
Observe:
- which operation commits
- which operation fails
- exception type
- table history
- resulting snapshot
Then repeat with disjoint date predicates.
Compare conflict behavior.
32. Hands-on Lab — Compare isolation levels
Start with default WriteSerializable.
Create a concurrent INSERT + DELETE scenario where the DELETE starts before the INSERT commits.
Observe the final state and history.
Then configure Serializable in a controlled lab environment:
ALTER TABLE demo.delta_concurrency SET TBLPROPERTIES ('delta.isolationLevel' = 'Serializable');
Repeat the scenario.
The goal is not to memorize the result; it is to understand why the result differs.
33. Hands-on Lab — Idempotent retry design
Build a small job that:
- reads a batch ID
- generates deterministic business keys
- MERGEs into a target
- intentionally retries the same batch
- verifies that the business result is unchanged
This connects concurrency handling with production data-engineering correctness.
34. Architect scenario — SAP CDC
Imagine SAP sends CDC changes into Bronze every few minutes.
Multiple Silver jobs attempt to update the same customer dimension.
Poor architecture:
5 independent jobs ↓ same Silver table ↓ frequent MERGE conflicts
Better architecture:
CDC Bronze ↓ partition/time-window isolation ↓ controlled Silver merge ↓ Gold consumers
The goal is not to eliminate concurrency. The goal is to make concurrency predictable.
35. Architect scenario — Finance close
During month-end close, Finance expects a customer balance table to be internally consistent.
Ask:
- Can concurrent updates be tolerated?
- Does the table need Serializable?
- Can the workload be partitioned by fiscal period?
- Can a single controlled writer own the Gold table?
- What is the retry SLA?
- What happens to downstream reports during retry?
The correct isolation level is a business decision translated into a technical guarantee.
36. Common mistakes
Mistake 1 — Assuming Delta means no write failures
Delta prevents corruption; it does not promise every concurrent write will succeed.
Mistake 2 — Using Serializable everywhere
Stronger isolation can reduce concurrency.
Mistake 3 — Blind retry loops
Retries without idempotency can duplicate business effects.
Mistake 4 — Partitioning every table aggressively
Too many partitions can create small-file and metadata problems.
Mistake 5 — Ignoring metadata changes
Schema/protocol changes can conflict with normal writes.
Mistake 6 — Two streams sharing a checkpoint
Each streaming query needs its own checkpoint ownership.
Mistake 7 — Ignoring external writers
Concurrent transactional safety depends on writer compatibility.
37. Architect decision matrix
| Situation | Preferred thinking |
|---|---|
| Append-only Bronze ingestion | Keep high concurrency; avoid unnecessary target reads |
| Frequent independent updates | Reduce overlapping write sets |
| Strong read/write serial semantics | Evaluate Serializable |
| Same-file concurrent updates | Evaluate row-level concurrency and table design |
| Frequent metadata changes | Establish schema/protocol change windows |
| Streaming | Unique checkpoint per query |
| External Delta writers | Validate commit/protocol compatibility |
| High conflict rate | Fix workload topology before increasing retries |
38. Interview questions
Foundation
- What is optimistic concurrency control?
- What are the three OCC phases?
- How does Delta provide atomicity?
- What isolation level is used for reads?
- What is the default write isolation level?
Engineering
- WriteSerializable vs Serializable?
- What is a write-write conflict?
- What is a phantom conflict?
- What causes ConcurrentAppendException?
- What causes MetadataChangedException?
- What is row-level concurrency?
- Why can a transaction fail after successfully writing Parquet files?
- Why are append-only writes often less conflict-prone?
Architect
- How would you design a high-concurrency Delta Silver layer?
- When would you choose Serializable?
- How would you troubleshoot repeated MERGE conflicts?
- How would you design retries?
- How would you handle multiple workspaces writing the same table?
- What changes when an external engine writes Delta?
- How would you translate a business consistency requirement into an isolation-level decision?
39. Production checklist
- Understand snapshot isolation for reads.
- Understand WriteSerializable as the default write isolation.
- Use Serializable only for a defined business requirement.
- Identify the actual conflicting operations before tuning.
- Keep write sets as disjoint as practical.
- Use explicit predicates where they reduce unnecessary target scans.
- Avoid high-cardinality partitioning.
- Evaluate row-level concurrency for qualifying workloads.
- Give every stream a unique checkpoint location.
- Make retries bounded and observable.
- Make business writes idempotent.
- Treat schema/protocol changes as coordinated events.
- Validate external writer compatibility.
- Monitor conflict rate as a production metric.
40. Key takeaway
The most important Delta concurrency model is:
Readers get consistent snapshots. Writers optimistically build changes, validate against concurrent commits, and either create a new version or fail safely.
Then choose the isolation level according to the business requirement:
WriteSerializable → default, high-concurrency choice Serializable → stronger guarantees, potentially more conflicts
At enterprise scale, the best concurrency strategy is usually not “turn on the strongest isolation.” It is designing ownership, predicates, partitioning, writer topology and idempotency so that conflicts are rare and recoverable.