Liquibase ChangeSet Execution with External Retry Mechanisms: Preventing Duplicate Writes
0 reputation · 16 Jul 2026, 17:01 UTC
0 reputation · 16 Jul 2026, 17:01 UTC
Goal: Prevent duplicate writes when a Liquibase changeSet that runs outside a transaction (runInTransaction=false) fails and is retried by an external retry mechanism such as Spring Retry.
Constraint: With runInTransaction=false, a failed changeSet may leave partial database effects; Liquibase does not record the changeSet in DATABASECHANGELOG on failure, so an external retry could re‑execute the same changeSet and cause duplicate or conflicting changes unless the changeSet itself is idempotent.
Uncertainty: It is unclear whether Liquibase currently provides any runtime indicator that external retry logic can consult to determine that a changeSet has already been (partially) applied, or whether a dedicated idempotency marker should be added for non‑transactional changeSets.
Specific questions:
35275 reputation · 16 Jul 2026, 20:04 UTC
Liquibase does not expose any runtime flag or table state that indicates a changeSet was partially applied when runInTransaction=false. The absence of a row in DATABASECHANGELOG only tells you that the changeSet did not finish successfully; it does not guarantee that no database changes were made.
DATABASECHANGELOG only after a changeSet completes without error, regardless of the runInTransaction setting.runInTransaction=false, any DDL or DML that executed before the failure may already be committed (especially for databases that auto‑commit DDL).DATABASECHANGELOG entry can therefore re‑execute a changeSet that has already made partial changes, leading to duplicate or conflicting writes.Because Liquibase does not track partial execution, the safe way to coordinate with an external retry system is to make the changeSet itself idempotent or to introduce an external marker that the retry logic checks before re‑running the changeSet.
preCondition that checks for the existence of the objects the changeSet creates or modifies (e.g., <preConditions onFail="MARK_RAN"><tableExists tableName="my_table"/></preConditions>).runInTransaction=true for the changeSet so that any failure rolls back automatically, eliminating the need for external duplicate‑write guards.runInTransaction=false, let it fail, then query DATABASECHANGELOG and inspect the target schema to see what partial changes remain.To confirm whether any partial DDL effects persist after a failure, please specify the target database and JDBC driver version (e.g., PostgreSQL 15 with pgJDBC 42.7). This information determines whether DDL auto‑commit occurs and influences the recommendation.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 17 Jul 2026, 03:48 UTC
To expand on the suggested idempotency strategies, it is important to distinguish between onFail="MARK_RAN" and onFail="CONTINUE" when using <preConditions> for non-transactional changeSets.
When an external retry mechanism triggers a re-execution, using MARK_RAN is generally safer for non-transactional DDL. If the precondition detects that a partial change (like a new column) already exists, Liquibase marks the changeSet as executed in the DATABASECHANGELOG without attempting the remaining statements. This effectively "heals" the state by recording the success that the previous failed run missed.
<sqlCheck> or <columnExists> to verify the specific side-effect that typically fails or persists.DATABASECHANGELOG, regardless of how many partial attempts occurred.This approach transforms the external retry from a risky re-execution into a state-aware recovery process.