Schedule icon
WorkingDirectory icon
Queries icon
Fail icon
OutputValues icon
Log icon
Switch icon

Audit a Parquet lake for missing, empty, short and doubled daily partitions

Kestra flow that audits a daily-partitioned Parquet lake from file footers with DuckDB and reports missing, empty, short and doubled days and ranges to re-run.

Categories
Data

A daily load can finish green and still leave a hole: a day that never landed, a partition with zero rows, half a day of data, or a day loaded twice. Nothing in the orchestrator notices, because every task succeeded. This flow audits the lake itself. It reads the row count of every Parquet file from its footer, groups the counts by day, compares each day with the median of the previous four same-weekday days, and reports what does not fit. It reads no data and changes nothing. The demo builds its own lake in DuckDB, with no credentials.

How it works

  1. workspace (io.kestra.plugin.core.flow.WorkingDirectory) shares one working directory between its tasks.
  2. build_demo_lake (io.kestra.plugin.jdbc.duckdb.Queries) writes a demo lake with one folder per day, and add_empty_partition adds the one partition that has zero rows. Both are skipped when lake_glob is set.
  3. find_files lists the files that lake_glob matches. scan_guard (io.kestra.plugin.core.execution.Fail) stops the run when no file matches, when no path has the partition key, or when no partition value is a date. A wrong path, key or date format would otherwise look like a lake with every day missing.
  4. audit reads the footers with parquet_file_metadata, joins the days to a calendar, judges each day and writes two files: the findings and the date ranges to re-run.
  5. result and log_audit report the counts. route (io.kestra.plugin.core.flow.Switch) logs COMPLETE, or lists the ranges for FINDINGS and fails the run when on_findings is FAIL.
  6. The nightly Schedule is disabled.

What it reports

Status Meaning
MISSING no Parquet file for the day
EMPTY files exist and hold 0 rows
SHORT fewer rows than short_ratio times the median of the previous four same-weekday days
DOUBLE more rows than double_ratio times that median, which can mean the day was loaded twice
STRAY a partition folder that is not a day of the calendar: not a date (such as __HIVE_DEFAULT_PARTITION__), more than a day in the future, or before 1971 (usually a NULL timestamp cast to 0)
NO_BASELINE fewer than 3 same-weekday days of history, so the volume is not judged. A missing or empty day is still reported

The baseline is a median, so one bad day in the history does not move it, and it is taken per weekday, so a quiet Sunday is not reported as short. DOUBLE is only a volume signal, and a real spike looks the same.

Prerequisites

  • A worker with DuckDB available through the Kestra DuckDB plugin. No Docker image, no network.
  • Secrets: none for the demo. For a lake on object storage, see below.

Quick start

  1. Import the flow into the company.team namespace, or paste it into the editor.
  2. Run it with the defaults. Then run the other scenarios:
Run Inputs Result
1 defaults SUCCESS, decision FINDINGS. 2026-06-03 to 2026-08-31, 90 days: 3 missing, 1 empty, 1 short, 1 doubled, 3 stray partitions, 5 ranges to re-run. Today's partition, which holds 40 percent of a normal day, is not judged.
2 demo_scenario: CLEAN SUCCESS, decision COMPLETE.
3 on_findings: FAIL FAILED at fail_on_findings with the same findings.
4 demo_scenario: EMPTY_LAKE FAILED at scan_guard. BLOCKED_NO_FILES.
5 demo_scenario: WRONG_KEY FAILED at scan_guard. BLOCKED_NO_PARTITION_KEY.
6 demo_scenario: WRONG_KEY, partition_key: ds SUCCESS, decision COMPLETE.
7 demo_scenario: COMPACT_DATES FAILED at scan_guard. BLOCKED_NO_DATES.
8 settle_days: 0 SUCCESS, decision FINDINGS, now with 2 short days: the partition of the demo date that was still loading is judged.
9 short_ratio: 0.2 SUCCESS, decision FINDINGS, now with 0 short days: the short partition holds 30 percent of a normal day.

Failing runs 3, 4, 5 and 7 are the expected result. The errors block logs them.

Inputs

  • demo_scenario (SELECT, default GAPS, allowed GAPS, CLEAN, EMPTY_LAKE, WRONG_KEY, COMPACT_DATES): the demo lake. Ignored when lake_glob is set.
  • lake_glob (STRING, optional): glob for your Parquet files, such as /data/events/*/*.parquet. Empty audits the demo lake. No single quote allowed.
  • partition_key (STRING, default dt): the folder level that holds the date, as in dt=2026-08-19. Values must be YYYY-MM-DD.
  • lookback_days (INT, default 90, minimum 7): days to judge, ending settle_days ago. The demo seeds defects up to 29 days back, so use 30 or more to see all of them.
  • settle_days (INT, default 1, minimum 0): recent days that are not judged because they may still be loading.
  • short_ratio (FLOAT, default 0.5, from 0 to 1): below this share of the median a day is SHORT.
  • double_ratio (FLOAT, default 1.8, minimum 1): above this multiple of the median a day is DOUBLE.
  • on_findings (SELECT, default REPORT, allowed REPORT, FAIL): FAIL ends the run FAILED when there is any finding.

Outputs

  • outputs.result.values.row: files, days_audited, ok_days, no_baseline_days, missing_days, empty_days, short_days, double_days, stray_partitions, ranges, audited_from, audited_to, plan, decision, reason.
  • outputs.audit.outputFiles.findings: one line per finding with partition_value, status, row_count, baseline_rows, ratio, files and note.
  • outputs.audit.outputFiles.ranges: one line per range of consecutive flagged days with start_date, end_date (inclusive), days and statuses.

Expected result

With the defaults the execution ends in SUCCESS. log_audit reports a window from 2026-06-03 to 2026-08-31 of 90 days, 3 missing, 1 empty, 1 short, 1 doubled and 3 stray, with the ranges 2026-08-03, 2026-08-11, 2026-08-19, 2026-08-23 and 2026-08-27..2026-08-28. log_findings lists the same ranges as a warning.

Using it with your lake

  1. Set lake_glob to your files, one folder per day, for example /data/events/*/*.parquet. With a different partition key, set partition_key.
  2. For s3://, gs:// or Azure paths DuckDB needs its object storage extension and credentials, which this flow does not set up. Add the statements to the start of the find_files and audit SQL, and keep the values in secret('NAME'). Each file footer is one request, so a lake with very many files is slow. This is not tested here.
  3. Set settle_days to cover the time your daily load needs, and lookback_days to how far back you want to be told about gaps.
  4. To repair, pass the ranges to a backfill. safe-backfill-controller takes start_date and end_date (inclusive) with granularity DAY. It skips partitions its own KV checkpoints mark as done, so use force or a new backfill_id for days this audit found missing.
  5. Enable the nightly Schedule, and send the errors block to Slack or e-mail so a failed audit is seen.

Limits

  • One date key, one folder per day, YYYY-MM-DD values. Another layout is an edit to the regexp_extract and the calendar in audit. Two folders that spell the same day differently, such as 2026-08-05 and 2026-8-5, are added together as one day.
  • It counts every Parquet file that lake_glob matches. A Delta Lake or an Iceberg table keeps files that its log no longer references, so their counts are too high. Use plain Parquet folders. For Delta tables see delta-lake-table-health-gate.
  • It checks that data landed and how much of it, not that it is correct. A partition with the right row count and wrong values passes.
  • A volume baseline gives false SHORT flags on holidays and for the weeks after a step change in volume. Tune short_ratio.
  • An unreadable or zero-byte Parquet file stops the scan, and the error names the file.
  • A glob that covers only part of the lake reports the rest as missing days. With on_findings set to FAIL that is loud, not silent.
  • Dates are compared with the worker's date. A lake that is partitioned in a time zone ahead of the worker can show tomorrow's partition, which is accepted up to one day ahead.
  • Not tested: object storage, lakes with many thousands of files, and any DuckDB version other than the one the offline tests used.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.