Query icon
OutputValues icon
Log icon
If icon
SlackIncomingWebhook icon
Pause icon
Loop icon
Fail icon
Schedule icon

Rebind invalid Db2 packages after a migration, with approval

Detect Db2 packages marked invalid or inoperative in SYSCAT.PACKAGES, rebind an allowlisted batch behind an approval, fail the run if it did not land.

Categories
DataInfrastructure

Findings

Finding What it means What the flow does
VALID = 'N' (invalid) DDL removed something the access plan used, an index or a table or a privilege. Db2 recompiles on first use, so the cost lands on whichever request gets there first Reports it. Rebinds it when the allowlist covers it, the ceiling allows it, the approval is given
VALID = 'X' (inoperative) A dependency is gone outright. Db2 refuses to run the package at all until somebody rebinds it Same path. A rebind only succeeds once the missing object is back, which is why the re-read has to fail the run
No routine behind the package It was bound from a bind file by an application. SYSPROC.ADMIN_CMD rejects the CLP REBIND, so there is no SQL route Reports it, counts it, never touches it. Names the CLP command to run
Held by the allowlist The package is rebindable but no pattern matched Reports it as held. Nothing happens until a pattern covers it
Past the ceiling More eligible packages than max_rebinds Reports them as deferred. The next run picks them up

Safety

A rebind is not free. It recompiles the statement against current statistics, so a package that was performing acceptably on an old plan can come back on a different plan that is slower. That is a performance risk, not a data risk, but it is real on a busy system. Three things keep it bounded. APREUSE YES asks Db2 to reuse the existing plan wherever it still can. RESOLVE CONSERVATIVE resolves names only against objects that existed at the original bind. max_rebinds keeps one run to a batch you can watch.

Nothing is eligible by default. routine_allowlist starts empty, so a first run always reports.

One state this guard cannot fix. If the migration dropped an object outright rather than replacing it, the routine itself goes invalid as well as its package, so SYSPROC.REBIND_ROUTINE_PACKAGE answers SQL0443N with reason -2453 instead of rebinding. Recreate the missing object, then run this flow. The run fails loudly in that case rather than reporting a fix it did not make.

How it works

  1. detect (io.kestra.plugin.jdbc.db2.Query) reads every package in the schema whose SYSCAT.PACKAGES.VALID is not Y. It joins SYSCAT.ROUTINEDEP on BTYPE = 'K' to SYSCAT.ROUTINES to find the routine that owns each package, because the package name of an SQL routine is generated and tells you nothing. It lists the objects still recorded in SYSCAT.PACKAGEDEP, applies the allowlist with SQL LIKE, numbers the eligible rows so the ceiling can cut the batch. Read-only.
  2. plan (io.kestra.plugin.core.output.OutputValues) turns the rows into the batch the loop will walk, the name list the re-read will query, one counts line. Gate flags are strings.
  3. summary (io.kestra.plugin.core.log.Log) prints the counts with every package named.
  4. route (io.kestra.plugin.core.flow.If) stops here when the catalog is clean.
  5. alert posts the counts to Slack. no_route_note warns about application packages that need the CLP.
  6. remediate_gate requires auto_remediate true, dry_run false, a non-empty batch.
  7. approval (io.kestra.plugin.core.flow.Pause) holds the run. The resume form names the exact packages with the options they will get.
  8. rebind (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) calls SYSPROC.REBIND_ROUTINE_PACKAGE once per package. One at a time, because concurrent rebinds race on the same catalog rows.
  9. verify reads SYSCAT.PACKAGES again for exactly the targeted names. verify_gate fails the run through io.kestra.plugin.core.execution.Fail if any of them is still not Y.
  10. errors posts to Slack with the execution id.

What you get

A per-schema answer to the question a migration leaves behind: which compiled access plans did my DDL break. The counts line splits the findings into rebindable, held by the allowlist, deferred by the ceiling, no SQL route at all. The rebind runs only for packages you named, in a batch you capped, after you approved it. The catalog read afterwards is what decides success, so a rebind that reported fine but changed nothing fails the run instead of passing quietly.

Who it's for

Db2 LUW DBAs, platform teams that run migrations against Db2, anyone who has watched an application throw a runtime error the morning after a schema change. Also useful as the last step of a migration pipeline: run it with the allowlist set to the schema you just changed.

Why orchestrate this with Kestra

The work is a catalog read, a conditional write, a second read that has to be able to fail the run. Kestra gives the approval gate, the per-run ceiling, the loop that rebinds one package at a time, the Slack path on failure, without a script holding state. The read-only default plus the Pause means the same flow is safe to schedule for reporting and ready to remediate when somebody is watching.

Tested end to end

Run against Db2 12.1.5.0 on Kestra 2.0.5. The degraded state was built from a 5000 row table with a unique index plus current statistics, so the compiled plan records that index every time. Four SQL procedures, one SQL function, one static package bound with db2 prep plus db2 bind sat on top of it. Dropping the index flipped VALID from Y to N in SYSCAT.PACKAGES for every dependent package while a control package reading another table stayed Y, checked with db2 "SELECT PKGSCHEMA, PKGNAME, VALID, LAST_BIND_TIME FROM SYSCAT.PACKAGES" before and after. Detection returned zero findings against the clean catalog, then four against the broken one on the same inputs. A dry_run=true run named the batch with LAST_BIND_TIME unmoved. An approved run rebound the allowlisted batch, the re-read showed VALID=Y with LAST_BIND_TIME moved, the control untouched. A ceiling of 1 against 2 eligible packages rebound exactly one. A declined approval changed nothing. A detect-only account made the rebind raise SQL0443N reason -551, failing the run. A DROP INDEX landing while a 15 package batch was running made the loop report success for all 15 while 11 read back N, which failed the run on the re-read.

Two Db2 behaviours worth knowing before you schedule this. With AUTO_REVAL set to DEFERRED, the default for a database created on 9.7 or later, Db2 revalidates invalid objects itself when a new connection comes in, so the N window can close before you look. This guard earns its keep on AUTO_REVAL = DISABLED databases, on inoperative packages whose dependency is still missing, on application packages, which Db2 never revalidates at connect. Check with db2 get db cfg for <db> | grep AUTO_REVAL.

Prerequisites

Secrets

Secret Used for
DB2_PASSWORD Default for the jdbc_password input
SLACK_WEBHOOK_URL The alert plus the errors block

Inputs

Input Type Default Notes
jdbc_url STRING jdbc:db2://localhost:50000/testdb Db2 JDBC URL
jdbc_username STRING db2inst1 SELECT on the catalog views to detect, ALTERIN or BINDADD to rebind
jdbc_password STRING {{ secret('DB2_PASSWORD') }} Never a literal
package_schema STRING DB2INST1 PKGSCHEMA to scan, folded to upper case
routine_allowlist STRING empty Comma separated SQL LIKE patterns against the owning routine. Empty means nothing is eligible
max_rebinds INT 5 Per-run ceiling. Eligible packages past it are reported as deferred
rebind_options SELECT APREUSE YES RESOLVE ANY Options string for SYSPROC.REBIND_ROUTINE_PACKAGE
auto_remediate BOOL false Must be true before a rebind is proposed
dry_run BOOL true True reports the batch, changes nothing

The detecting account needs SELECT on SYSCAT.PACKAGES, SYSCAT.PACKAGEDEP, SYSCAT.ROUTINEDEP, SYSCAT.ROUTINES, which Db2 grants to PUBLIC by default. Rebinding needs ALTERIN on the package schema or BINDADD, plus EXECUTE on SYSPROC.REBIND_ROUTINE_PACKAGE. Point the flow at a detect-only account to keep the guard a reporter.

Quick start

  1. Store DB2_PASSWORD plus SLACK_WEBHOOK_URL as Kestra secrets.
  2. Run it with defaults against your database. It reports, nothing is eligible, nothing changes. Read the counts line.
  3. Set routine_allowlist to the schema or prefix your migration touched, for example PAYMENTS.%. Run again with dry_run=true to see the batch.
  4. To remediate, set auto_remediate=true with dry_run=false, run it, read the batch on the Pause form, resume with approved=true. The re-read fails the run if a package is still not valid.
  5. Enable daily_scan once you trust the reporting. Its inputs force dry_run=true with auto_remediate=false.

How to extend

  • Call it from the end of a migration flow with routine_allowlist set to the schema you changed, so the rebind happens while somebody is still watching the deploy.
  • Chain a RUNSTATS step before the rebind when the migration changed data volume, so the new plan is built on current statistics rather than stale ones.
  • Widen the detection to SYSCAT.PACKAGEDEP with BTYPE = 'F' to catch packages that depend on a user defined function, the usual way a package goes inoperative instead of invalid.
  • Add a second allowlist input for the package schema when one database holds several applications.
  • Write the counts line to a KV key per run to get a trend of how much plan churn each release causes.
  • Swap the Pause for a core.flow.Sleep plus a stricter ceiling on a database where nobody is on call at migration time.

Links

This blueprint was created by zkasuran.

See How

New to Kestra?

Use blueprints to kickstart your first workflows.