New to Kestra?
Use blueprints to kickstart your first workflows.
Catch rising DB2 lock escalation while LOCKLIST and MAXLOCKS are fixed, alert Slack, then set them through ADMIN_CMD and verify the change landed.
Lock escalation is DB2 trading many row locks for one table lock when the lock memory runs
low. It happens when LOCKLIST (the memory set aside for locks) fills up or when a single
application holds more than MAXLOCKS percent of it. DB2 frees memory by replacing an
application's row locks on a table with a single table lock. That table lock blocks every
other transaction that touches the table, so throughput collapses and other statements start
failing with lock timeouts. The ALTER, the load or the batch that caused it all succeeded,
so nothing points at the cause. The symptom is timeouts somewhere else.
This blueprint reads LOCK_ESCALS from MON_GET_DATABASE and MON_GET_WORKLOAD, compares it
against the previous run held in Kestra KV so a rising count is told apart from an old one,
and reads whether LOCKLIST and MAXLOCKS are a fixed number or AUTOMATIC. When escalations
are rising while the memory is fixed it hands both parameters to the self tuning memory manager,
or applies a bounded bump, through SYSPROC.ADMIN_CMD, then reads the configuration again and
fails the run if the change did not land.
This blueprint was created by zkasuran.
| Verdict | Rule | Fix |
|---|---|---|
ACTION |
Escalations rose by at least escalation_threshold since the baseline while LOCKLIST or MAXLOCKS is a fixed value |
Hand them to the self tuning memory manager or bump the fixed value |
TUNED_ALREADY |
Escalations are rising but LOCKLIST and MAXLOCKS are already AUTOMATIC |
A config change will not help. Look at the escalating workload or DATABASE_MEMORY |
OK |
Fewer than escalation_threshold new escalations since the baseline |
None |
BASELINE |
First run against this database, so there is nothing to compare against yet | Rerun to measure new escalations |
AUTOMATIC means the self tuning memory manager sizes the parameter from the overall lock
memory. A fixed LOCKLIST that was set years ago for a smaller workload is the usual cause of
escalation. The guard only arms remediation on ACTION, because flipping an already automatic
parameter changes nothing.
auto_remediate is true, dry_run
is false and the verdict is ACTION. Three conditions, all of them.LOCKLIST and MAXLOCKS are online configurable, so the change takes effect without bouncing
the database. The guard still reads the live value back and fails the run if it did not move,
which catches a change that only landed in DEFERRED_VALUE.read_escals (io.kestra.plugin.jdbc.db2.Query, read-only) sums LOCK_ESCALS,
LOCK_TIMEOUTS, LOCK_WAITS and DEADLOCKS from MON_GET_DATABASE. read_workload reads
LOCK_ESCALS per workload from MON_GET_WORKLOAD. read_config reads the LOCKLIST and
MAXLOCKS rows from SYSIBMADM.DBCFG, including the VALUE_FLAGS that say whether each is
AUTOMATIC.assess (io.kestra.plugin.core.output.OutputValues) reads the baseline from Kestra KV,
works out the escalations added since the last run, decides the verdict, builds the
ADMIN_CMD command for the chosen mode and writes the Slack text.summary logs the report. record_baseline (io.kestra.plugin.core.kv.Set) stores this
run's LOCK_ESCALS for the next run. No ttl is set, because an expired KV key throws on read.route (io.kestra.plugin.core.flow.If) stops with a log when there is nothing rising. On a
finding it posts alert to Slack and planned prints what a remediation run would change.remediate_gate opens only on ACTION with auto_remediate true and dry_run false.
apply_config runs CALL SYSPROC.ADMIN_CMD('UPDATE DB CFG USING LOCKLIST ... MAXLOCKS ...').reverify reads DBCFG again and verify_result fails the run if the live value did not
reach the target, otherwise remediated logs the before and after.errors posts a Slack alert when the flow fails, for example when the database is unreachable.LOCKLIST and MAXLOCKS state, so you can see at a glance whether the memory is fixed or
tuned.ADMIN_CMD.Rising is a difference between two points in time, so it needs somewhere to keep the last count.
Kestra KV holds the baseline between runs, the schedule sets how far apart the two points are and
the destructive half sits behind auto_remediate and dry_run. The configuration change is
applied and then checked in the same run, with the whole history kept on the execution.
On Kestra 2.0.5 OSS against DB2 Community Edition 12.1.5 (icr.io/db2_community/db2), database
testdb, with a local HTTP sink standing in for Slack. Escalation was driven for real by setting
LOCKLIST 50 MAXLOCKS 10 with the database activated, then updating every row of a 20000 row
table in one statement, which forces DB2 to escalate the row locks to a single table lock. Every
run was checked against the DB2 CLI (SYSIBMADM.DBCFG, MON_GET_DATABASE) as well as the Kestra
log.
| Run | Inputs | Result |
|---|---|---|
| 1 | defaults, first run | Verdict BASELINE. LOCK_ESCALS 0. Baseline recorded, no change |
| 2 | defaults | Verdict OK. +0 escalations, LOCKLIST AUTOMATIC, clean branch |
| 3 | defaults, after driving escalations | Verdict ACTION. +3 escalations, LOCKLIST 50 fixed, alert sent, no change (dry run) |
| 4 | auto_remediate: true, dry_run: false, remediation_mode: automatic |
ADMIN_CMD set both AUTOMATIC, reverify read AUTOMATIC, CLI confirmed locklist AUTOMATIC and maxlocks AUTOMATIC |
| 5 | auto_remediate: true, dry_run: false, remediation_mode: bump |
ADMIN_CMD set LOCKLIST 4096 MAXLOCKS 20, reverify read the new values, CLI confirmed locklist 4096 with maxlocks 20 |
| 6 | unreachable port | read_escals failed, the errors block posted to Slack, the execution is FAILED |
The detector was shown to fire only on a real problem by run 2 against run 3. Nothing changed
between them except the escalations driven under a fixed LOCKLIST. The verdict moved from
OK to ACTION. The DB2 CLI read locklist 50 NONE while LOCK_ESCALS climbed, so the fixed
memory with the rising count were both confirmed outside Kestra.
MON_GET_DATABASE, MON_GET_WORKLOAD and SYSIBMADM.DBCFG, plus
call SYSPROC.ADMIN_CMD to UPDATE DB CFG for remediation. The instance owner has all of these.docker run -d --name db2 --privileged -p 50000:50000 -e LICENSE=accept -e DB2INST1_PASSWORD=... -e DBNAME=testdb icr.io/db2_community/db2:latest;LOCKLIST 4 MAXLOCKS 10, create a table with many rows, then update them all in one
transaction to drive escalation.DB2_PASSWORD: the database password, used as the default for the jdbc_password input.SLACK_WEBHOOK_URL: used by alert and the errors block.SECRET_.| Input | Default | Purpose |
|---|---|---|
jdbc_url |
jdbc:db2://localhost:50000/testdb |
Database to read |
jdbc_username |
db2inst1 |
Database user |
jdbc_password |
{{ secret('DB2_PASSWORD') }} |
Database password |
escalation_threshold |
1 |
New escalations since the baseline that make a run a finding |
remediation_mode |
automatic |
automatic sets AUTOMATIC. bump sets the fixed targets below |
bump_locklist_pages |
4096 |
LOCKLIST size in 4K pages applied in bump mode |
bump_maxlocks_percent |
20 |
MAXLOCKS percent applied in bump mode |
auto_remediate |
false |
Allow the configuration change |
dry_run |
true |
Must be false for a change to apply |
DB2_PASSWORD and SLACK_WEBHOOK_URL secrets.jdbc_url and jdbc_username, then run it. The first run records a
baseline.remediation_mode, then run with auto_remediate: true and
dry_run: false.hourly trigger, which always runs in safe mode.Pause before apply_config so a human approves the memory change in a busy window.DATABASE_MEMORY or turn on SELF_TUNING_MEM in bump mode, so the self tuning
manager has room to grow the lock memory.