New to Kestra?
Use blueprints to kickstart your first workflows.
Query QuestDB wal_tables() for suspended tables, alert Slack with the real names, and optionally call ALTER TABLE RESUME WAL over the REST API.
QuestDB's write-ahead log tables can become suspended either on their own, when applying a transaction fails, or deliberately, when an operator runs ALTER TABLE SUSPEND WAL ahead of a maintenance operation. Either way, while a table is suspended, writes still land in the sequencer but nothing is applied to the table, so queries do not see the new rows until RESUME WAL runs. Nothing in QuestDB itself pages anyone when this happens. This blueprint queries wal_tables() on a schedule, alerts Slack with the real names of every suspended table, and can optionally resume them over the REST API, one at a time, with a safe-character check before any name is used in SQL or a URL.
probe_suspended_tables (io.kestra.plugin.jdbc.postgresql.Query) runs SELECT name, writerTxn, sequencerTxn FROM wal_tables() WHERE suspended = true over QuestDB's
PGWire port. No dedicated QuestDB plugin or JDBC subplugin exists in Kestra's plugin
catalog, so this uses the generic PostgreSQL JDBC task.gate_on_suspension (io.kestra.plugin.core.flow.If) compares
outputs.probe_suspended_tables.size, the real row count the query task reports,
against suspended_count_threshold.alert_wal_suspension posts a Slack message naming the real suspended
tables, extracted with a jq filter, never an invented list.check_auto_resume (io.kestra.plugin.core.flow.If) gates remediation behind
auto_resume_wal. When enabled, resume_loop
(io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) iterates over the suspended
table names one at a time. For each one, guard_safe_table_name checks it against a
conservative character allowlist before resume_wal_via_rest
(io.kestra.plugin.core.http.Request) sends ALTER TABLE <name> RESUME WAL to
QuestDB's REST /exec endpoint as a URL-encoded GET query. A name that fails the
check is logged and skipped instead.auto_resume_wal is false, log_auto_resume_disabled names the tables that still
need a manual RESUME WAL.log_audit_summary always runs last. The flow-level errors block pages Slack if the
flow itself fails, for example on a probe connection or authentication error.wal_tables() itself, never a
guess.QuestDB has no built-in alert for a suspended WAL table, and wal_tables() itself reads
from disk, which QuestDB's own documentation describes as less suitable for frequent
polling, so a naive tight polling loop is not the right approach either. Kestra adds the
schedule, the threshold check, the Slack alert naming the real tables, and an optional,
guarded remediation loop as one declarative, auditable flow, instead of a cron job and a
hand-rolled script.
auto_resume_wal will be used.WAL keyword, since wal_tables()
only reports WAL-enabled tables.QUESTDB_USERNAME: QuestDB PGWire login user.
QUESTDB_PASSWORD: QuestDB PGWire login password.
SLACK_WEBHOOK: Slack incoming webhook URL used by the alert task and the errors
block.
WEBHOOK_KEY: random string that authenticates the on_demand_webhook trigger.
In Kestra OSS, set these as base64-encoded environment variables, for example
SECRET_QUESTDB_USERNAME, SECRET_QUESTDB_PASSWORD, SECRET_SLACK_WEBHOOK,
SECRET_WEBHOOK_KEY.
questdb_host, questdb_pgwire_port, and questdb_http_port to your instance.suspended_count_threshold: 0 to alert on any suspension.disabled: true from the schedule trigger once confirmed.auto_resume_wal: true only after confirming the cause of any suspension has been
addressed.Local Docker test (OSS):
# 1. Find the Docker network your Kestra containers are on
docker network ls
# 2. Start QuestDB without publishing host port 8080, to avoid clashing with services
# commonly run on that port
docker run -d --name questdb \
-p 9000:9000 -p 8812:8812 -p 9009:9009 \
questdb/questdb:latest
# 3. Attach QuestDB to Kestra's own Docker network, found in step 1
docker network connect YOUR_KESTRA_NETWORK questdb
# 4. Export secrets as base64 for Kestra OSS
export SECRET_QUESTDB_USERNAME=$(echo -n "<questdb-username>" | base64)
export SECRET_QUESTDB_PASSWORD=$(echo -n "<questdb-password>" | base64)
export SECRET_SLACK_WEBHOOK=$(echo -n "<slack-webhook-url>" | base64)
export SECRET_WEBHOOK_KEY=$(echo -n "<webhook-key>" | base64)
# 5. To simulate a suspended table, create a WAL table and suspend it, in the QuestDB
# web console:
# CREATE TABLE readings (ts TIMESTAMP, val DOUBLE) TIMESTAMP(ts) PARTITION BY DAY WAL;
# ALTER TABLE readings SUSPEND WAL;
errorTag and errorMessage to the SELECT list, both real wal_tables() columns,
to include the reason a table suspended on its own in the Slack alert./exec path to /execute, which
QuestDB's own docs describe as taking the same parameters and returning the same
response.options.auth to resume_wal_via_rest if the target QuestDB instance has HTTP
basic authentication enabled, this blueprint's REST call is unauthenticated as given.| Output path | Description |
|---|---|
outputs.probe_suspended_tables.rows |
List of maps, one per suspended table, each with name, writerTxn, sequencerTxn |
outputs.probe_suspended_tables.size |
Real count of suspended tables returned by the query |
outputs.resume_loop.iterationCount |
Total number of tables the resume loop iterated over |
outputs.resume_loop.terminatedIterations |
Count of loop iterations per terminal state |
| Input | Type | Default | Description |
|---|---|---|---|
questdb_host |
STRING | questdb |
QuestDB hostname or IP |
questdb_pgwire_port |
INT | 8812 |
PGWire port for the wal_tables() probe |
questdb_http_port |
INT | 9000 |
HTTP REST port for RESUME WAL |
suspended_count_threshold |
INT | 0 |
Alert when the real suspended count exceeds this value |
auto_resume_wal |
SELECT | false |
true or false, auto-resume suspended tables via the REST API |
wal_tables() reads from disk. QuestDB's own documentation describes it as less
suitable for frequent polling. A 15-minute schedule is the default here; polling
meaningfully more often than that should be weighed against that documented caveat.REBASE WAL, not only because of a genuine failure. auto_resume_wal set to true
during a planned maintenance window could resume a table an operator deliberately
suspended./exec path, not the current /execute path. Both are documented by QuestDB as taking
the same parameters and returning the same response, but /exec is not the path
QuestDB recommends for new work.^[A-Za-z0-9_.-]{1,255}$) is this blueprint's own defensive
guard, not a verified QuestDB identifier grammar, no such grammar was found in the
QuestDB documentation consulted for this blueprint. A legitimate table name using a
character outside that set would be skipped and logged, not resumed.resume_wal_via_rest is unauthenticated as given. If the target
QuestDB instance has HTTP basic authentication enabled, this call needs
options.auth added, see How to extend.wal_tables(), RESUME WAL, and SUSPEND WAL did
not state which version introduced each.