Query icon
SlackIncomingWebhook icon
Schedule icon

Cross-Catalog ELT from Postgres to Iceberg with Trino

Run cross-catalog ELT with Trino and Kestra. One INSERT SELECT moves daily Postgres orders into Iceberg, verifies the count, and notifies Slack.

Categories
Data

Move data between systems with one SQL statement. Because Trino federates catalogs, an INSERT INTO iceberg... SELECT ... FROM postgres... is a complete extract-and-load pipeline, with no export files, no staging bucket, and no custom code. This blueprint runs that statement daily through io.kestra.plugin.jdbc.trino.Query, scoped to a single date taken from the schedule, then verifies the landed row count and posts it to Slack.

How it works

  1. load_daily_orders (io.kestra.plugin.jdbc.trino.Query, fetchType: NONE) executes INSERT INTO iceberg.analytics.daily_orders SELECT ... FROM postgres.public.orders for exactly one order_date. The date literal is rendered from {{ (trigger.date ?? execution.startDate) | date('yyyy-MM-dd') }}, so scheduled runs load their own day and manual or backfill executions pick the right date automatically.
  2. verify_load (fetchType: FETCH_ONE) counts rows in the target table for the same date, returning a single loaded_rows scalar.
  3. notify posts the verified count and the loaded date to Slack; a run that inserted nothing reports 0 instead of silence.
  4. The errors block posts a distinct Slack alert naming the possibly missing date.
  5. A disabled-by-default Schedule trigger runs the load daily at 05:00. Trino has no multi-statement transactions, so the load and the verification are deliberately separate single-statement tasks.

What you get

  • A full ELT pipeline in one reviewable SQL statement, moving data across catalogs with no intermediate storage.
  • Date-scoped loads driven by the schedule, which makes Kestra's backfill feature replay any historical day correctly.
  • Independent verification, so the Slack message reports what actually landed.
  • Failure alerts that name the exact date at risk.

Who it's for

  • Data engineers landing operational Postgres data in an Iceberg lakehouse without building a connector pipeline.
  • Analytics engineers who prefer their ELT logic as SQL in version control.
  • Teams migrating from nightly dump-and-load scripts to governed, replayable executions.

Why orchestrate this with Kestra

The single statement is only a pipeline if something runs it every day, at the right date boundary, retries transient cluster failures, replays missed days, and tells someone when a day is missing. Kestra supplies the schedule, the trigger.date context that makes backfills exact rather than approximate, per-task retries, and the success and failure notifications, while the data movement itself stays inside Trino at full engine speed.

Prerequisites

  • A Trino cluster with a postgres catalog and an iceberg catalog, plus a target table matching iceberg.analytics.daily_orders with the same column layout as the SELECT.
  • Rerun awareness: replaying a date inserts the day again. Delete the date's rows first when reloading, or extend the flow with a preceding DELETE task for that date.
  • A Slack incoming webhook for summaries and alerts.

Secrets

  • TRINO_URL: JDBC URL, e.g. jdbc:trino://host:443/iceberg/analytics (use https on 443 in production; password authentication requires TLS).
  • TRINO_USERNAME: Trino username.
  • TRINO_PASSWORD: Trino password.
  • SLACK_WEBHOOK_URL: Slack incoming webhook URL.

Quick start

  1. Add the four secrets to your Kestra namespace.
  2. Create the target Iceberg table and adjust both statements to your schemas and columns.
  3. Execute the flow manually and check the Slack summary reports a plausible row count.
  4. Set disabled: false on the daily trigger; use Kestra backfills to load historical dates.

How to extend

  • Add a DELETE FROM iceberg.analytics.daily_orders WHERE order_date = DATE '...' task before the insert to make reloads idempotent.
  • Transform in flight: add joins, filters, or aggregations to the SELECT, since the engine executing it is a full query engine.
  • Chain the data quality gate blueprint after the load to validate the new partition before consumers read it.
  • Load several source tables by duplicating the task pair or iterating with io.kestra.plugin.core.flow.ForEach.

Links

Orchestrate with Kestra
Orchestrate Slack with Kestra
Share this Blueprint
See How

New to Kestra?

Use blueprints to kickstart your first workflows.