Schedule icon
SparkCLI icon
Docker icon
If icon
Pause icon
Fail icon
Log icon
SlackIncomingWebhook icon

Clean orphaned Spark output staging safely

Safely clean abandoned Spark output staging directories with age, liveness, approval and post-delete checks.

Categories
Data

Safety

A killed Spark write can leave _temporary or _SPARK_STAGING trees beside committed partitions. They hold a second copy of the output and a reader can mistake them for data.

Guard What it stops
Output-root allowlist A delete can only target a staging child below output_path.
Age plus Spark-master liveness A recent write or a path containing a live application id is spared.
Approval and immediate re-read A changed tree during the pause fails before any delete.
Post-delete tree and committed digest A survivor or changed committed path fails the execution.

How it works

  1. inspect_before (io.kestra.plugin.spark.SparkCLI) runs hadoop fs -ls -R against the output URI, reads the Spark master JSON endpoint (the run fails if it is unreachable) and emits candidate paths, ages, bytes and a digest of every non-staging path.
  2. remediation_gate (io.kestra.plugin.core.flow.If) keeps the default dry run read-only. When armed and candidates exist, hold (io.kestra.plugin.core.flow.Pause) records operator approval.
  3. prewrite_recheck (io.kestra.plugin.spark.SparkCLI) repeats the detector after the pause. stale_plan_guard (io.kestra.plugin.core.flow.If) fails if either the candidate set or committed digest changed.
  4. remediate (io.kestra.plugin.spark.SparkCLI) invokes hadoop fs -rm -r -f only for the rechecked staging roots, serially within this flow's queue.
  5. verify (io.kestra.plugin.spark.SparkCLI) lists the tree again and fails if a staging root from the plan survived or the committed digest changed.

What you get

  • Staging byte counts before and after cleanup from the storage CLI.
  • A dry-run report of old staging roots that spares recent or live ones.
  • An approval record and a failed execution when the plan goes stale.

Who it's for

Data platform teams running Spark output on S3-compatible storage.

Why orchestrate this with Kestra

Kestra adds the approval record, the stale-plan check and the post-delete assertion that raw listing and delete commands lack, with an execution history and a disabled safe-mode schedule.

Tested end to end

The test fixture uses Apache Spark 3.5.1 and an S3-compatible MinIO service on a private Docker network. The flow runs the SparkCLI plugin 1.2.9 on Kestra 2.0.5; the evidence packet records the exact execution ids and before/after byte totals.

Case Execution Result
Healthy control 11Tk2kQj7IH4wsPBpoddVc SUCCESS, no candidates
Degraded dry run XyHP2xSa5rmiorrnYkQq7 SUCCESS, 8,388,625 staging bytes identified
Declined approval 1zCY8fUgDpm353OiegJsgR SUCCESS, staging preserved
Remediation 1BwQ81CVhLUzlyfyqooQzp SUCCESS, 8,388,625 bytes removed and 13 committed bytes preserved
Repeat QfQWujeCzzMjzoMjpg665 SUCCESS, no candidates
Ineffective write 4OSFafdykAQrG03iDFc91J FAILED at stale-plan guard
Race or partial 6tjwDb2gifaBPDe7Bv4X1L FAILED at stale-plan guard

Prerequisites

  • Kestra 2.0.5 with the Spark plugin and a Docker task runner on the same network as the Spark master and S3 endpoint.
  • A Spark standalone master JSON endpoint and an S3 identity that can list and delete only the chosen output prefix.

Secrets

  • SPARK_S3_ACCESS_KEY: access key for the output prefix.
  • SPARK_S3_SECRET_KEY: secret key for the output prefix.
  • SPARK_S3_ENDPOINT: S3A endpoint of the output store.
  • SPARK_MASTER_WEB_URL: Spark standalone master web URL.
  • SLACK_WEBHOOK_URL: incoming webhook for failed-run alerts.

Inputs

Input Default Purpose
output_path s3a://sstage/output Bounded output root.
s3_endpoint secret('SPARK_S3_ENDPOINT') S3A endpoint for the output store.
s3_access_key, s3_secret_key the two S3 key secrets S3 credentials.
spark_master_web_url secret('SPARK_MASTER_WEB_URL') Spark standalone master JSON endpoint.
task_network bridge Docker network for task runners.
max_job_runtime_seconds 3600 Age boundary for staging candidates.
auto_remediate false Arms the approval and delete path.
dry_run true Forces read-only behavior by default.

Quick start

  1. Configure the five secrets.
  2. Execute with dry_run=true and auto_remediate=false; inspect the candidate byte total.
  3. Set auto_remediate=true, dry_run=false, execute and approve the listed roots from the Pause.
  4. Confirm verify finds no planned root left and the committed digest unchanged.

How to extend

Add a tenant-specific path policy to the detector below the same output root. For another cluster manager replace only the liveness read and keep the post-approval recheck.

Limits: an application id counts as live only when the Spark standalone master JSON contains it. A history server or another scheduler is not consulted.

Links

This blueprint was created by zkasuran.

See How

New to Kestra?

Use blueprints to kickstart your first workflows.