Schedule icon
Download icon
WorkingDirectory icon
PythonSubmit icon
Docker icon
If icon
UploadFiles icon
Set icon
SlackIncomingWebhook icon
Fail icon

Write-Audit-Publish a dataset with Spark and Kestra

Write-Audit-Publish with Spark. Kestra builds a candidate Parquet file, audits row rules, publishes clean data, quarantines bad batches. Runs with no setup.

Categories
Data

Write-Audit-Publish (WAP) keeps bad data away from readers. The pipeline writes a candidate dataset to a private location, audits that exact file, then publishes it only when the audit passes. In this blueprint a PySpark job builds the candidate Parquet file and audits it with row-level rules. Kestra publishes the file to a stable path when it passes. A batch that fails is quarantined with the failing checks named, and the published dataset is left as it was.

It runs with no setup. Spark runs in local[*] mode in the task container, the input is a public CSV of 100 orders, the output lands in namespace files and Slack is off. Run it with inject_bad_rows set to true to watch a batch get quarantined.

This blueprint was created by zkasuran.

How it works

  1. download_source (io.kestra.plugin.core.http.Download) fetches the raw CSV.
  2. write (io.kestra.plugin.core.flow.WorkingDirectory) runs build_and_audit (io.kestra.plugin.spark.PythonSubmit) and keeps its candidate.parquet as an output file.
    • Write: the job types and cleans the rows (casts, trimmed names, lower-case emails) and writes one candidate Parquet file.
    • Audit: the job reads the candidate back and flags each row against five rules: null order_id, duplicated order_id, non-positive price or quantity, total not equal to price * quantity, and an invalid email. A row that breaks several rules counts once. It outputs rows, bad_rows, bad_row_rate, failed_checks and the count per rule.
  3. audit_gate (io.kestra.plugin.core.flow.If) passes when rows >= min_rows and bad_row_rate <= max_bad_row_rate.
    • Publish: publish (io.kestra.plugin.core.namespace.UploadFiles) overwrites data/published/orders.parquet and record_publication (io.kestra.plugin.core.kv.Set) stores the path, row count, execution id and time under the orders_published key.
    • Quarantine: quarantine writes the candidate to data/quarantine/orders-<execution id>.parquet, and block_publish (io.kestra.plugin.core.execution.Fail) ends the run as FAILED with the per-rule counts.
  4. The nightly Schedule trigger runs it every night. Slack messages are optional on both paths.

Inputs

  • source_url (URI, default the public orders CSV): the raw batch.
  • min_rows (INT, default 50): fewer rows fail the audit.
  • max_bad_row_rate (FLOAT, default 0.0): the highest share of bad rows allowed.
  • inject_bad_rows (BOOL, default false): add 12 bad rows (duplicates, null keys, wrong totals) for a demo of the quarantine path.
  • notify_slack (BOOL, default false): post the result to Slack.

Prerequisites

  • A Kestra worker that can run Docker containers. The job uses the apache/spark:4.0.1-java21-r image, the plugin default. The Docker task runner runs it as root because the image user cannot read the task files otherwise.

Secrets

  • SLACK_WEBHOOK_URL: Slack incoming webhook. Only needed when notify_slack is true.

Quick start

  1. Save the flow and run it. The audit passes and data/published/orders.parquet appears under the namespace Files tab.
  2. Run it with inject_bad_rows: true. The run fails with 17 bad rows of 112, the candidate is in data/quarantine/ and the published file is unchanged.
  3. To use your own data, point source_url at your file and edit the rules in the audit section of the script.

Expected outputs

  • outputs.build_and_audit.vars.rows, bad_rows, bad_row_rate, failed_checks, checks.
  • Namespace file data/published/orders.parquet: the last audited dataset.
  • KV orders_published: {path, rows, execution_id, published_at} for downstream flows.

Using it with a cluster and a lake

  • Set master to your cluster, for example spark://spark-master:7077, and replace the namespace file steps with your object store tasks (S3, GCS, Azure Blob) to publish to a lake path.
  • To read or write s3a:// paths, add the hadoop-aws package through configurations (spark.jars.packages). The default image does not include it.
  • With a table format such as Iceberg, write the candidate to a branch and publish by fast-forwarding the branch. The audit and gate stay the same.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.