New to Kestra?
Use blueprints to kickstart your first workflows.
Audit Azure HorizonDB durable orchestration instances on a schedule, alert Slack on failures or backlog, and optionally cancel a reviewed instance.
A durable orchestration that gets stuck in Running, or a batch that starts failing after a
downstream dependency changes, looks identical to normal traffic from the outside, the
HTTP trigger that started it already returned 200. HorizonDB's own df.list_instances()
and df.status() know exactly what state every instance is actually in, but nothing reads
that report on a schedule. This blueprint turns a HorizonDB durable store into a Day 2
health gate: it counts instances by status, alerts Slack with the real numbers, and lets an
operator cancel one specific instance they have already identified, never a bulk cancel.
probe_failed_instances and probe_running_instances
(io.kestra.plugin.azure.horizondb.durable.ListInstances) each query
df.list_instances(status, limit) for one status, Failed and Running, with
allowFailure: true so a connection problem does not stop the flow before it can be
classified as PROBE_FAILED below.classify_orchestration_health (io.kestra.plugin.core.debug.Return) compares
probe_failed_instances.size and probe_running_instances.size against their own
thresholds, gated by primary_watch_status, and returns PROBE_FAILED, CRITICAL,
WARNING, or HEALTHY.route_by_orchestration_health (io.kestra.plugin.core.flow.Switch) branches on that
status: HEALTHY logs it, WARNING, CRITICAL, and PROBE_FAILED each alert Slack
with the real failed and running counts, never an invented number.remediate_gate (io.kestra.plugin.core.flow.If) runs only when cancel_instance_id
is set, auto_remediate is true, and dry_run is false:cancel_target_instance calls df.cancel(id, reason) on exactly that one instance.
df.cancel() does not document a return value confirming the instance actually
stopped, so this step alone proves nothing.verify_cancel calls df.status() on the same instance, and report_cancel_result
(io.kestra.plugin.core.flow.If) fails the flow with
io.kestra.plugin.core.execution.Fail if it is still Running, so a cancel that did
not take is never silently reported as done.log_audit_summary always runs last and logs the final counts and classification.errors block posts to Slack if the flow itself fails outside the gate logic above.df.list_instances(), not an assumption that everything that started also finished.df.cancel()'s own silence.durable.ListInstances, durable.Cancel, and durable.GetStatus together, including
the Entra ID and static-password authentication paths.HorizonDB exposes df.list_instances(), df.status(), and df.cancel() as plain SQL
functions, it has no concept of a schedule, a threshold, or an alert built from what they
return. Kestra adds the recurring audit, the classification by status count, the Slack
alert, the deliberately scoped single-instance cancel, and the verification that the
cancel worked, all as one declarative YAML file you can version and review like code.
az login on the worker) with useEntraId: true.org.postgresql JDBC driver, ssl: false is explicitly supported for local development
against a non-TLS instance. The df schema functions this blueprint depends on
(df.list_instances, df.status, df.cancel) are HorizonDB's own managed
implementation, a vanilla local PostgreSQL container will accept the connection but does
not have these functions, there is no open-source emulator for the df schema itself.
Local testing needs a real, non-production HorizonDB instance.HORIZONDB_HOST: HorizonDB server hostname, without protocol or port.
HORIZONDB_USERNAME, HORIZONDB_PASSWORD: static credentials, unused when
use_entra_id is true.
AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET: service principal
credentials for Entra ID authentication, unused when use_entra_id is false. Omit all
three to fall back to the default Azure credential chain on the worker.
SLACK_WEBHOOK: Slack incoming webhook URL used by the alert tasks and the errors
block.
In Kestra OSS, set all these as base64-encoded environment variables, for example
SECRET_HORIZONDB_HOST, SECRET_HORIZONDB_USERNAME, SECRET_SLACK_WEBHOOK.
| Input | Type | Default | Allowed values |
|---|---|---|---|
database |
STRING | orchestrations |
any HorizonDB database name |
primary_watch_status |
SELECT | Both |
Failed, Running, Both |
failed_count_critical_threshold |
INT | 5 |
any non-negative integer |
running_backlog_warning_threshold |
INT | 50 |
any non-negative integer |
sample_limit |
INT | 100 |
1 to 10000 |
use_entra_id |
BOOL | false |
true, false |
cancel_instance_id |
STRING | `` (empty) | empty, or a real instance ID |
auto_remediate |
BOOL | false |
true, false |
dry_run |
BOOL | true |
true, false |
{{ outputs.probe_failed_instances.size }}: count of durable function instances currently in Failed status.{{ outputs.probe_failed_instances.rows }}: list of raw instance records returned by the Failed probe (fetchType: FETCH).{{ outputs.probe_running_instances.size }}: count of durable function instances currently in Running status.{{ outputs.probe_running_instances.rows }}: list of raw instance records returned by the Running probe (fetchType: FETCH).{{ outputs.classify_orchestration_health.value }}: health classification evaluated by the gate (HEALTHY, WARNING, CRITICAL, or PROBE_FAILED).{{ outputs.cancel_target_instance.cancelled }}: boolean indicating whether the cancel statement executed cleanly.{{ outputs.verify_cancel.status }}: verified runtime status of the target instance after remediation.HORIZONDB_HOST, credentials, and SLACK_WEBHOOK secrets.log_audit_summary for the counts.CRITICAL or WARNING, query df.list_instances('Failed') or
df.list_instances('Running') directly to find the specific instance worth
investigating.cancel_instance_id,
auto_remediate: true, and dry_run: false and run this flow again.hourly trigger, which always ships in safe mode.df.list_instances() result columns include a start or last-updated timestamp
in your HorizonDB version, read it from outputs.probe_running_instances.instances to
classify by age instead of only count, that detail was not documented in the plugin
source this blueprint was verified against, confirm your own schema before relying on it.outputs.probe_failed_instances.instances to post each failed instance's own
identifying fields to Slack individually, instead of only the count.Signal task after a verified cancel to notify a compensating orchestration that
the original instance was terminated.df.cancel() does not document a return value confirming the instance actually stopped.
cancelled: true on cancel_target_instance's output means the call executed without
the driver throwing, nothing more, trust verify_cancel's real status, not that flag.limit on ListInstances is capped at 10000 by the task itself specifically so this
gate can never accidentally load an unbounded number of instances into memory, raise
sample_limit deliberately, do not assume it is unlimited.useEntraId: true with no tenantId/clientId/clientSecret set falls back to
whatever DefaultAzureCredential resolves on the worker (managed identity, environment
variables, az login), which can silently differ between a developer's laptop and a
production worker. Set the three explicitly for a service principal if that matters.ssl defaults to true (sslmode=require), only set it to false against a
non-production instance you control.