ListInstances icon
Return icon
Switch icon
Log icon
SlackIncomingWebhook icon
If icon
Cancel icon
GetStatus icon
Fail icon
Schedule icon

HorizonDB Durable Instance Health Gate

Audit Azure HorizonDB durable orchestration instances on a schedule, alert Slack on failures or backlog, and optionally cancel a reviewed instance.

Categories
CloudInfrastructure

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.

How it works

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. log_audit_summary always runs last and logs the final counts and classification.
  6. The errors block posts to Slack if the flow itself fails outside the gate logic above.

What you get

  • A real count of failed and in-flight durable instances, read straight from HorizonDB's own df.list_instances(), not an assumption that everything that started also finished.
  • A Slack alert carrying the exact counts that triggered it, never an invented number.
  • A deliberately narrow remediation path: this blueprint cancels exactly one named instance an operator already identified, it does not expose a way to cancel instances in bulk from a schedule.
  • A verified cancel: the flow checks the instance's real status after cancelling and fails loudly if it is still running, instead of trusting df.cancel()'s own silence.

Who it's for

  • Platform teams running durable orchestrations on Azure HorizonDB who need to know about a growing Failed count or a stalled Running backlog before it is a customer-facing incident.
  • Teams evaluating the HorizonDB plugin who want a working example of durable.ListInstances, durable.Cancel, and durable.GetStatus together, including the Entra ID and static-password authentication paths.

Why orchestrate this with Kestra

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.

Prerequisites

  • An Azure HorizonDB server with a database containing durable function instances.
  • Either a HorizonDB username and password, or an Azure Entra ID identity (managed identity, service principal, or az login on the worker) with useEntraId: true.
  • A Slack incoming webhook for alerts.
  • HorizonDB speaks the standard PostgreSQL wire protocol and this plugin uses the standard 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.

Secrets

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

Inputs

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

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

Quick start

  1. Add the HORIZONDB_HOST, credentials, and SLACK_WEBHOOK secrets.
  2. Run this flow manually with the defaults and read log_audit_summary for the counts.
  3. If it reports CRITICAL or WARNING, query df.list_instances('Failed') or df.list_instances('Running') directly to find the specific instance worth investigating.
  4. Once you have a specific instance ID to cancel, set cancel_instance_id, auto_remediate: true, and dry_run: false and run this flow again.
  5. Enable the hourly trigger, which always ships in safe mode.

How to extend

  • If your 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.
  • Loop over outputs.probe_failed_instances.instances to post each failed instance's own identifying fields to Slack individually, instead of only the count.
  • Chain a Signal task after a verified cancel to notify a compensating orchestration that the original instance was terminated.

Pitfalls

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

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.