Schedule icon
Queries icon
Scan icon
If icon
SlackIncomingWebhook icon
Fail icon
Log icon

Data Quality Gate with Soda Checks

Block downstream pipelines on bad data with Soda and Kestra. SodaCL checks feed an If branch that alerts Slack and fails the execution explicitly.

Categories
Data

A quality check that only writes a log line is a suggestion; a quality gate stops the pipeline. This blueprint turns io.kestra.plugin.soda.Scan into a gate: the scan runs with allowFailure: true so a failing check does not short-circuit the flow, then an If task branches on {{ outputs.gate_scan.result.hasFailures }}. Bad data alerts Slack and fails the execution through an explicit Fail task, so anything gated on this flow, dbt runs, exports, publishing jobs, never sees it. Good data logs a clean verdict and lets downstream work proceed.

How it works

  1. load_orders (io.kestra.plugin.jdbc.duckdb.Queries) creates and refreshes a small orders table in the DuckDB file at warehouse_path. It exists so the blueprint runs anywhere; in production you replace it with your dlt, Airbyte, or JDBC load task.
  2. gate_scan (io.kestra.plugin.soda.Scan) receives the warehouse connection through the configuration map, which must define a data source named kestra, and the SodaCL checks through the checks map. The requirements list adds soda-core-duckdb for the DuckDB connection. allowFailure: true converts a failed check into a WARNING state instead of a hard failure, handing the decision to the next task.
  3. route_gate (io.kestra.plugin.core.flow.If) branches on {{ outputs.gate_scan.result.hasFailures }}. The failure branch posts a Slack alert naming the flow and execution, then fail_execution (io.kestra.plugin.core.execution.Fail) fails the execution with a message pointing at the scan logs.
  4. The pass branch logs the green verdict; in a real pipeline this is where the downstream tasks live, so they structurally cannot run when the gate is closed.
  5. The errors block alerts on any failure path, including a scan that could not run at all, and a disabled-by-default Schedule trigger runs the gate every morning.

What you get

  • A gate, not a report: bad data fails the execution instead of decorating a dashboard.
  • Explicit failure semantics through the Fail task, with an error message that names the next action.
  • Declarative SodaCL checks versioned with the flow, no custom validation code.
  • A pass branch that doubles as the anchor point for downstream tasks, making the gate structural rather than conventional.

Who it's for

  • Data engineers who need transformation and publishing jobs to stop when upstream data is bad.
  • Platform teams replacing ad hoc SQL assertions with declarative, versioned checks.
  • Anyone whose incident reviews keep ending with the sentence that the pipeline kept running anyway.

Why orchestrate this with Kestra

Soda decides whether the data passes; it cannot stop a downstream job it knows nothing about. Kestra supplies the gate mechanics: allowFailure defers the verdict, the If task turns a structured output into control flow, and the Fail task makes the block explicit in the execution history rather than implicit in a skipped schedule. Every gate decision is preserved as structured outputs, so you can audit exactly which check closed the gate and when.

Prerequisites

  • A persistent path for the DuckDB file (the warehouse_path input) visible to the task containers. For production warehouses, swap the configuration map to Snowflake, BigQuery, or Postgres and add the matching soda-core-* package to requirements.
  • A Slack incoming webhook for gate alerts.

Secrets

  • SLACK_WEBHOOK_URL: Slack incoming webhook URL.

Quick start

  1. Add the SLACK_WEBHOOK_URL secret to your Kestra namespace.
  2. Execute the flow and confirm the log line reports the gate passing with 3 green checks.
  3. Change a check to missing_count(customer_id) = -1 and rerun to watch the gate close: Slack alert, explicit Fail, errors block.
  4. Replace load_orders with your real load task, put your downstream tasks in the else branch, and enable the after_load_window trigger.

How to extend

  • Point the configuration map at Snowflake, BigQuery, or Postgres with credentials from {{ secret('...') }}, and add soda-core-snowflake, soda-core-bigquery, or soda-core-postgres to requirements.
  • Add warn thresholds to checks so marginal data passes the gate with a WARNING state instead of blocking.
  • Escalate gate closures to an incident, as shown in the Soda incident escalation blueprint.
  • Replace the else Log with a Subflow task so the gate protects an entire downstream flow.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.