New to Kestra?
Use blueprints to kickstart your first workflows.
Write-Audit-Publish with Spark. Kestra builds a candidate Parquet file, audits row rules, publishes clean data, quarantines bad batches. Runs with no setup.
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.
download_source (io.kestra.plugin.core.http.Download) fetches the raw CSV.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.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.audit_gate (io.kestra.plugin.core.flow.If) passes when rows >= min_rows and bad_row_rate <= max_bad_row_rate.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 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.nightly Schedule trigger runs it every night. Slack messages are optional on both paths.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.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.SLACK_WEBHOOK_URL: Slack incoming webhook. Only needed when notify_slack is true.data/published/orders.parquet appears under the namespace Files tab.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.source_url at your file and edit the rules in the audit section of the script.outputs.build_and_audit.vars.rows, bad_rows, bad_row_rate, failed_checks, checks.data/published/orders.parquet: the last audited dataset.orders_published: {path, rows, execution_id, published_at} for downstream flows.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.s3a:// paths, add the hadoop-aws package through configurations (spark.jars.packages). The default image does not include it.