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

DB2 Tablespace High Water Mark Reduce Guard

Find DB2 tablespaces holding free pages below the high water mark, reclaim them with ALTER TABLESPACE REDUCE, then prove the mark came down.

Categories
DataInfrastructure

A DB2 DMS tablespace grows to its high water mark and then stays that big. Drop a large table or delete most of its rows. DB2 hands the extents back to the tablespace as pending-free. The container file on disk does not shrink. TBSP_USED_PAGES falls, so a used-pages check looks healthy, while TBSP_TOTAL_PAGES and TBSP_PAGE_TOP stay where the data left them. The volume stays full. Nothing in the database raises an alert, because from the database's point of view there is no problem at all.

Findings

On the rig behind this blueprint, a tablespace with 120000 rows in it read 8192 total pages, 8160 used pages, 0 free pages, high water mark 8160. After DROP TABLE the same tablespace read 96 used pages and a high water mark still at 8160, with 8064 pages parked in TBSP_PENDING_FREE_PAGES. So 32 MB of a 33 MB tablespace was dead space that only an ALTER TABLESPACE ... REDUCE would return.

TBSP_FREE_PAGES is the wrong number to watch here. It counts pages above the high water mark, which is why it read 0 for the tablespace holding 8064 recoverable pages. The number that matters is TBSP_PAGE_TOP - TBSP_USED_PAGES.

Safety

Detection is a single read of MON_GET_TABLESPACE. The write needs auto_remediate true, dry_run false, then a typed approval at a Pause. Killing the execution at the pause leaves every tablespace as it was. Each ALTER TABLESPACE runs on its own, never two at once, because extent movement competes with live queries for I/O.

How it works

  1. detect reads MON_GET_TABLESPACE('', -2) and keeps the rows where TBSP_PAGE_TOP - TBSP_USED_PAGES clears both a page threshold and a percentage threshold. The filter also drops anything REDUCE cannot move: SMS tablespaces, temporary content, tablespaces without reclaimable storage, tablespaces without automatic resize, anything not in NORMAL state.
  2. plan records the names plus the page counts as they were before the run.
  3. summary logs the scan. route sends a Slack message naming each tablespace, its high water mark, its used pages, its pending-free pages, the megabytes on offer.
  4. In reporting mode the flow stops there and logs the statement it would have run.
  5. With auto_remediate true and dry_run false, approve pauses for a typed true.
  6. remediate loops over the tablespaces at concurrencyLimit: 1 and runs ALTER TABLESPACE "<name>" REDUCE <clause> on each.
  7. settle waits, then reverify runs the same query again. If any tablespace is still over both thresholds, remediation_failed fails the run with the before and after page counts.

What you get

  • A read of real page counts, not a guess from table sizes. MON_GET_TABLESPACE reports what the storage layer actually holds.
  • A detector that cannot fire on a tablespace it could not fix, because the same predicate that selects a target also checks that REDUCE is supported on it.
  • Two thresholds instead of one, so a 2 TB warehouse tablespace with 1 GB of slack stays quiet.
  • A reduce_clause that controls how much of the container file goes back, for the case where you want the mark lowered without handing the whole file to the filesystem.
  • A run that fails loudly when the space did not come back. A refused ALTER TABLESPACE reports success to the client, so the re-read is the only honest check.

Who it's for

DBAs and platform teams running DB2 LUW where storage is billed or capped. The classic trigger is an archive job that drops last quarter's partitions every month, after which the database reports plenty of room while the mount point stays at 95 percent. Also useful after a one off purge, a table rebuild, a migration that left a staging tablespace behind.

Why orchestrate this with Kestra

Reclaiming space is a read, a decision, a write, then a second read that has to be able to fail the job. A cron script can do the first three. It cannot pause for a human, keep the approval next to the evidence that prompted it, then prove the change landed. Kestra gives the Pause, the typed approval, the per task logs that show the page counts before and after, the errors: block that reaches Slack when the connection itself breaks. The flow ships with the schedule disabled, so it is a reporting job until someone turns it on.

Tested end to end

Run against DB2 12.1.5.0 in a container, with DMS tablespaces under automatic storage.

  • Compact control tablespace, 160 used pages at high water mark 160: zero findings, clean ran.
  • Degraded tablespace, 96 used pages at high water mark 8160: detection fired with 8064 free pages below the mark, 98 percent, 31 MB recoverable. Both tablespaces were inside the scan pattern on that run, so the compact one was in scope. Only the degraded one was reported.
  • dry_run=true: logged the statement it would run, changed nothing. The page counts read the same from the DB2 CLI afterwards.
  • auto_remediate=true, dry_run=false, approval true, clause MAX: high water mark 8160 to 96, total pages 8288 to 192, read back from the DB2 CLI.
  • Same run with clause 1 PERCENT: high water mark 8160 to 96, total pages 8384 to 8288. The clause sized the container shrink. The mark came down either way.
  • Approval declined: no ALTER TABLESPACE ran, page counts unchanged at 8160 and 8384.
  • Account without SYSCTRL: DB2 refused the ALTER TABLESPACE with SQL0552N while the task itself reported success. The re-read found the mark still at 8160 with 8384 total pages, so the run failed with the before and after counts in the message. The errors: block posted to Slack.
  • Unreachable JDBC URL: detect failed, the errors: block posted to Slack.

Prerequisites

DB2 LUW 10.1 or later, since ALTER TABLESPACE ... REDUCE needs reclaimable storage, which is a property of tablespaces created at DB2 9.7 or later. A tablespace carried forward from an older release reports RECLAIMABLE_SPACE_ENABLED = 0 and is skipped by the detector.

Secrets

  • DB2_PASSWORD: password for the database user.
  • SLACK_WEBHOOK_URL: incoming webhook for the alert and the failure notification.

Inputs

  • jdbc_url, jdbc_username, jdbc_password: the connection. The user needs EXECUTE on MON_GET_TABLESPACE to detect, SYSCTRL or SYSADM to reduce.
  • name_pattern, exclude_pattern: LIKE patterns scoping the scan. Defaults are every tablespace except SYS%.
  • min_free_pages_below_hwm (default 1000), min_free_pct_below_hwm (default 20): both have to be met before a tablespace is reported.
  • reduce_clause (default MAX): what follows REDUCE. MAX, a size such as 200 M, a percentage such as 25 PERCENT.
  • settle_duration (default PT5S): wait between the last statement and the re-read.
  • auto_remediate (default false), dry_run (default true): both have to be flipped before any write happens.

Quick start

  1. Add the DB2_PASSWORD secret plus SLACK_WEBHOOK_URL.
  2. Run it as it is. You get a Slack message listing every tablespace holding reclaimable pages with the megabytes on offer, no change to the database.
  3. Tune min_free_pages_below_hwm and min_free_pct_below_hwm until the list is the one you would act on.
  4. To reclaim, run with auto_remediate=true and dry_run=false, then answer the pause with approved=true inside a maintenance window.
  5. Enable the Schedule trigger once the reporting output looks right. It forces safe mode, so enabling it only turns on the weekly report.

How to extend

  • Set reduce_clause to a size or a percentage if you want the mark lowered while the container file keeps most of its space, so a tablespace that refills every month is not re-extending.
  • Add an io.kestra.plugin.jdbc.db2.Query step before the loop that runs CALL SYSPROC.ADMIN_CMD('REORG TABLE ...') on the tables in the target tablespace, so rows pack down before the mark is lowered. REDUCE moves extents, it does not compact rows.
  • Replace the Slack task with io.kestra.plugin.opsgenie.notifications.OpsgenieAlert to route the finding at an on call rota.
  • Write the before and after page counts to a KV pair with io.kestra.plugin.core.kv.Set to trend how fast each tablespace refills.
  • Point name_pattern at one storage group per execution if you want separate maintenance windows per group.

Links

This blueprint was created by zkasuran.

See How

New to Kestra?

Use blueprints to kickstart your first workflows.