Schedule icon
Webhook icon
Query icon
If icon
SlackIncomingWebhook icon
Loop icon
Request icon
Log icon

QuestDB WAL Suspended Table Sentinel

Query QuestDB wal_tables() for suspended tables, alert Slack with the real names, and optionally call ALTER TABLE RESUME WAL over the REST API.

Categories
DataInfrastructure

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.

How it works

  1. 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.
  2. 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.
  3. On suspension, alert_wal_suspension posts a Slack message naming the real suspended tables, extracted with a jq filter, never an invented list.
  4. 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.
  5. When auto_resume_wal is false, log_auto_resume_disabled names the tables that still need a manual RESUME WAL.
  6. 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.

What you get

  • A real list of exactly which tables are suspended, from wal_tables() itself, never a guess.
  • A Slack alert naming the real tables, with the real count that triggered it.
  • Optional, serialized remediation with a defensive character check before any name reaches SQL or a URL, and a logged skip rather than a silent failure for a name that does not pass that check.
  • A manual-resume fallback that still tells you exactly which tables need attention when automatic remediation is off.

Who it's for

  • Teams running QuestDB for time-series or event data who need to know about a suspended WAL table before downstream queries start silently missing rows.
  • Teams evaluating QuestDB's PGWire compatibility with Kestra's generic PostgreSQL JDBC plugin, since no dedicated QuestDB plugin exists yet.

Why orchestrate this with Kestra

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.

Prerequisites

  • A reachable QuestDB instance with its PGWire port open to the Kestra worker, and its HTTP REST port open as well if auto_resume_wal will be used.
  • At least one WAL-enabled table, created with the WAL keyword, since wal_tables() only reports WAL-enabled tables.

Secrets

  • 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.

Quick start

  1. Set the four secrets above.
  2. Set questdb_host, questdb_pgwire_port, and questdb_http_port to your instance.
  3. Keep suspended_count_threshold: 0 to alert on any suspension.
  4. Run the flow manually once to confirm connectivity. Confirm the PGWire connection and the probe query before enabling anything else.
  5. Remove disabled: true from the schedule trigger once confirmed.
  6. Set 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;

How to extend

  • Add 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.
  • Switch the REST call from the documented-deprecated /exec path to /execute, which QuestDB's own docs describe as taking the same parameters and returning the same response.
  • Add 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.

Outputs

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

Inputs

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

Pitfalls

  • 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.
  • A table can be suspended on purpose, for a maintenance operation such as 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.
  • The REST call targets the documented-deprecated /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.
  • The safe-character check (^[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.
  • The REST call in 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.
  • No QuestDB version number is claimed anywhere in this blueprint. The QuestDB documentation pages consulted for wal_tables(), RESUME WAL, and SUSPEND WAL did not state which version introduced each.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.