Schedule icon
Webhook icon
Script icon
If icon
SlackIncomingWebhook icon
OutputValues icon

High-Performance Polars Parquet Streaming ETL and Validation

Stream and transform large Parquet files using Polars LazyFrames in Kestra. Validate schemas, sink partitioned datasets, and report performance to Slack.

Categories
Data

Processing multi-gigabyte or streaming data batches with traditional Python libraries like Pandas frequently causes memory exhaustion (Out-Of-Memory crashes) and sluggish single-threaded query bottlenecks. Modern data engineering teams are increasingly standardizing on Polars, a blazing-fast DataFrame engine written in Rust that leverages Apache Arrow column layouts, multi-core vectorization, and streaming query optimization.

This blueprint implements a high-throughput, memory-efficient ETL and data validation pipeline orchestrated by Kestra. It scans incoming raw datasets using Polars LazyFrames, applies vectorized arithmetic and derived columns, validates business constraints and null thresholds, sinks compressed partitioned Parquet files (zstd/snappy), and reports processing speed metrics to Slack.

How it works

  1. Streaming Ingestion: The flow initializes an isolated Python 3.11 container with polars, pyarrow, and s3fs. It constructs a Polars LazyFrame using pl.scan_csv() or pl.scan_parquet(), deferring execution until computation graph optimization is complete.
  2. Vectorized Transformations: Derives computed fields (net_total_usd = quantity * transaction_amount * (1 - discount)), casts types into compact Apache Arrow representations, and extracts temporal partition keys.
  3. Vectorized Data Quality Gate: Validates nullability thresholds on primary identifiers and tests numeric bounds across the dataset in parallel, calculating anomaly counts and null ratios.
  4. Streaming Parquet Sink: Calls lazy_plan.sink_parquet() with configured row-group sizes and compression codecs (zstd, snappy), streaming results directly to storage with minimal peak memory overhead.
  5. Conditional Telemetry & Alerting: An io.kestra.plugin.core.flow.If task assesses data quality:
    • If anomalies were found, it dispatches an urgent Slack warning with anomaly counts.
    • If the batch is clean, it posts an execution health card detailing throughput (rows/sec) and output file size.
  6. Failure Triage: Root-level errors tasks catch cloud connectivity drops, invalid file formatting, or permission errors, alerting #data-ops with direct execution links.

What you get

  • Rust-Powered Throughput: Processes hundreds of thousands of records per second on minimal CPU and RAM allocations.
  • OOM Resilience: Polars streaming query execution processes datasets larger than available RAM by streaming data in chunks through memory.
  • Storage Optimization: Sinks columnar Parquet files compressed with modern codecs like Zstandard (zstd) for faster query scans in DuckDB, ClickHouse, Snowflake, and BigQuery.
  • Automated Quality Defense: Catches malformed fields, negative totals, and null drift before data lands in downstream lakehouses.

Expected outputs

Downstream orchestrations can reference the typed ETL metrics generated by output_etl_metrics:

Output Variable Description Expression Example
rows_processed Total records transformed and ingested {{ outputs.output_etl_metrics.values.rows_processed }}
valid_rows Clean records passing validation {{ outputs.output_etl_metrics.values.valid_rows }}
invalid_rows Count of records failing null or boundary checks {{ outputs.output_etl_metrics.values.invalid_rows }}
speed_rows_per_sec Processing throughput in rows per second {{ outputs.output_etl_metrics.values.speed_rows_per_sec }}
parquet_destination Destination storage URI for curated Parquet {{ outputs.output_etl_metrics.values.parquet_destination }}
duration_seconds Total end-to-end pipeline execution time {{ outputs.output_etl_metrics.values.duration_seconds }}

Who it's for

  • Data Engineers & Analytics Engineers migrating legacy Pandas pipelines to modern, high-throughput Arrow-based engines.
  • Lakehouse Architects seeking lightweight, serverless Parquet compaction without provisioning costly Spark or EMR clusters.
  • Platform Teams enforcing strict schema quality gates prior to warehouse loading.

Why orchestrate this with Kestra

Running standalone Python ETL scripts on cron jobs creates operational blind spots: failed runs go unnoticed, logs are scattered, and retries are manual. Kestra provides:

  • Isolated Container Runtime: Packages Polars and PyArrow dependencies in isolated Docker containers with zero dependency conflicts.
  • Flexible Event & Schedule Triggers: Supports scheduled daily runs alongside event-driven webhooks triggered whenever new files land in S3 or Google Cloud Storage.
  • First-Class Error Interception: Dedicated errors blocks ensure your team is instantly notified in Slack if upstream file layouts change unexpectedly.

Prerequisites

  • An Amazon S3 bucket, Google Cloud Storage bucket, or local directory accessible by the Kestra worker.
  • Cloud storage credentials with read access on raw files and write access on curated paths.
  • An incoming Slack webhook URL for performance digests and quality alerts.

Secrets

Configure the following secrets in your Kestra namespace:

Secret Name Description Example
AWS_ACCESS_KEY_ID Cloud storage access key ID AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY Cloud storage secret access key ••••••••••••
AWS_REGION Storage region us-east-1
SLACK_WEBHOOK_URL Destination Slack webhook URL https://hooks.slack.com/services/T00/B00/X00
WEBHOOK_KEY Secret authentication key for on-demand webhook execution whk_live_c4d29e710b

Quick start

  1. Import this blueprint into your Kestra instance under the company.data namespace.
  2. Add your cloud credentials and Slack webhook under Settings > Secrets.
  3. Run the flow manually to process the default synthetic batch and observe Polars' sub-second throughput metrics.
  4. Enable the daily_batch_etl_schedule trigger or configure the webhook URL in your cloud storage bucket event notification.

Common pitfalls & notes

  • Schedule State: The schedule trigger is shipped disabled: true by default to prevent unexpected runs before storage secrets are configured.
  • Row Group Sizing: For optimal query performance in Snowflake, DuckDB, or Trino, set row_group_size between 50,000 and 100,000 rows.
  • Eager vs. Lazy Execution: Always use pl.scan_csv() or pl.scan_parquet() instead of pl.read_csv() to enable query graph optimization and streaming execution.

How to extend

  • DuckDB Querying: Add a downstream io.kestra.plugin.jdbc.duckdb.Query task to run SQL analytics directly on the generated Parquet files.
  • Multi-File Glob Ingestion: Pass wildcard patterns (e.g., s3://bucket/events/*.parquet) into pl.scan_parquet() to process hundreds of partition files in a single pass.
  • Delta Lake / Iceberg Sinks: Extend the Python script with deltalake or pyiceberg to append the curated LazyFrame directly into an open lakehouse table.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.