Acquiring and Releasing Locks
Relevant source files
The following files were used as context for generating this wiki page:
- src/main/java/liquibase/ext/clickhouse/sqlgenerator/LockDatabaseChangeLogClickHouse.java
- src/main/java/liquibase/ext/clickhouse/sqlgenerator/UnlockDatabaseChangelogClickHouse.java
- src/main/java/liquibase/ext/clickhouse/lockservice/ClickHouseLockService.java
- src/main/java/liquibase/ext/clickhouse/sqlgenerator/CreateDatabaseChangeLogLockTableClickHouse.java
- src/main/java/liquibase/ext/clickhouse/sqlgenerator/InitializeDatabaseChangeLogLockClickHouse.java
- src/main/java/liquibase/ext/clickhouse/database/ClickHouseDatabase.java
- src/main/java/liquibase/ext/clickhouse/sqlgenerator/SqlGeneratorUtil.java
The locking mechanism in the Liquibase ClickHouse extension ensures that only one instance of Liquibase can execute changes against a specific ClickHouse database at any given time. This prevents race conditions and database corruption that could occur if multiple concurrent Liquibase processes attempted to modify the schema simultaneously. The system relies on a dedicated lock table and specialized SQL generators to manage the state of these locks within the ClickHouse environment.
This mechanism is implemented through a combination of a custom LockService and specific SqlGenerator implementations that handle the nuances of ClickHouse syntax, such as ALTER TABLE ... UPDATE and cluster-aware execution.
Lock Infrastructure
The locking system is built upon a specialized table structure and a service that manages the lifecycle of the lock state.
The DatabaseChangeLogLock Table
Locks are tracked in a table (typically named DATABASECHANGELOGLOCK). This table is created using ClickHouse-specific engines like MergeTree or ReplicatedMergeTree depending on the cluster configuration.
| Field | Type | Description |
|---|---|---|
ID | Int64 | Primary identifier for the lock (usually 1). |
LOCKED | UInt8 | Boolean-like flag (0 for unlocked, 1 for locked). |
LOCKGRANTED | Nullable(DateTime64) | The timestamp when the lock was acquired. |
LOCKEDBY | Nullable(String) | Description of the host/process that acquired the lock. |
Sources: CreateDatabaseChangeLogLockTableClickHouse.java:54-57, SqlGeneratorUtil.java:49-56
ClickHouse Lock Service
The ClickHouseLockService extends the StandardLockService to provide ClickHouse-specific logic for checking table existence and initialization status. It uses RawSqlStatement to query the database directly.
The service determines if the lock table is initialized by checking if the row count is greater than zero.
Sources: ClickHouseLockService.java:46-55, InitializeDatabaseChangeLogLockClickHouse.java:59-64
Acquiring Locks
Lock acquisition is performed by updating the status of the first row in the lock table. The extension uses ClickHouse's mutation syntax (ALTER TABLE ... UPDATE) to perform this atomic-like update.
Acquisition Logic
To acquire a lock, the system attempts to update the LOCKED status from 0 to 1. The query includes a WHERE clause ensuring the update only happens if the row is currently unlocked (LOCKED = 0).
- Host Identification: The
LOCKEDBYfield is populated with a string containing the hostname, host description, and host address. - Timestamping: The
LOCKGRANTEDfield uses a ClickHouse specifictoDateTime64function formatted to include milliseconds. - Synchronous Execution: The mutation is executed with
SETTINGS mutations_sync = 1to ensure the lock state is persisted before proceeding.
Sources: LockDatabaseChangeLogClickHouse.java:54-65, ClickHouseDatabase.java:43-46
Releasing Locks
Releasing a lock involves resetting the LOCKED flag to 0 and clearing the metadata fields.
Release Process
The UnlockDatabaseChangelogClickHouse generator produces the SQL required to free the database for other Liquibase instances.
The release query specifically targets the row where ID = 1 and LOCKED = 1, resetting all lock-related columns to their default empty states.
Sources: UnlockDatabaseChangelogClickHouse.java:52-58
Cluster Awareness
A critical feature of the locking mechanism is its support for ClickHouse clusters. If a cluster configuration is detected, the locking operations are applied across the entire cluster using the ON CLUSTER clause.
- Clause Generation: If
ClusterConfigis present, the SQL generators appendON CLUSTER '{clusterName}'to theCREATE,ALTER, andDELETEstatements. - Replicated Tables: When in cluster mode, the lock table is created using the
ReplicatedMergeTreeengine, ensuring the lock state is consistent across all nodes via ZooKeeper.
Sources: SqlGeneratorUtil.java:44-56, ParamsLoader.java:94-106
Lock State Transition Summary
| Action | SQL Condition | Field Updates |
|---|---|---|
| Initialize | DELETE WHERE 1 | ID=1, LOCKED=0 |
| Lock | WHERE ID=1 AND LOCKED=0 | LOCKED=1, LOCKEDBY='host', LOCKGRANTED=now |
| Unlock | WHERE ID=1 AND LOCKED=1 | LOCKED=0, LOCKEDBY=null, LOCKGRANTED=null |
Sources: InitializeDatabaseChangeLogLockClickHouse.java:53-57, LockDatabaseChangeLogClickHouse.java:58-62, UnlockDatabaseChangelogClickHouse.java:52-56
Summary
The locking system for Liquibase ClickHouse utilizes a dedicated DATABASECHANGELOGLOCK table managed by ClickHouseLockService. By leveraging ClickHouse-specific mutations (ALTER ... UPDATE) with synchronous settings and optional cluster replication, it provides a robust mechanism to coordinate database updates across distributed environments. This ensures that schema changes are applied sequentially and safely, even when multiple deployment pipelines are triggered simultaneously.