New to Kestra?
Use blueprints to kickstart your first workflows.
Measure Apache Pinot ingestion lag with now(), classify table freshness, and alert Slack when a stream consumer stalls.
A stalled Kafka (or other stream) consumer feeding an Apache Pinot
real-time table degrades silently - queries still return results, just
increasingly stale ones, with no obvious client-side error. This
blueprint reads the table's own newest row against Pinot's own now()
scalar function to measure that lag directly, classifies the result into
a three-state risk level, and alerts on anything but a clean bill of
health.
periodic_freshness_check (io.kestra.plugin.core.trigger.Schedule) runs every 10 minutes, explicitly passing pinot_broker_url, pinot_table, timestamp_column, and max_lag_minutes through its own inputs: block so the flow is self-contained when the trigger fires. Shipped disabled so a manual run can be verified first.probe_table_freshness (io.kestra.plugin.jdbc.pinot.Query, fetchType: FETCH_ONE) runs a single SQL statement against the Pinot broker, returning row.row_count (total rows in the table), row.latest_event_time (max of timestamp_column), and row.lag_ms (Pinot's own now() minus that max, in milliseconds).evaluate_row_presence (io.kestra.plugin.core.flow.If) branches on whether row_count > 0.else (EMPTY_TABLE): alert_empty_table reports that the table has never received a row.then (rows exist): nested evaluate_freshness (io.kestra.plugin.core.flow.If) branches on lag_ms > max_lag_minutes * 60000.then (STALE): alert_stale_ingestion reports the lag, threshold, row count, and latest event time to Slack.else (HEALTHY): log_freshness_healthy records a clean check.log_audit_complete always runs last, printing row_count and lag_ms regardless of which branch fired.errors block alerts Slack separately if the flow itself fails outright - an unreachable broker or bad credentials must not read as EMPTY_TABLE or HEALTHY.now(), with no external monitoring agent or sidecar.io.kestra.plugin.jdbc.pinot.Query task driving the aggregate probe, reusable against any table via the pinot_table and timestamp_column inputs.Query driving a single-statement aggregate probe, not just a row extract.A one-off Pinot SQL query answers one moment in time; it does not decide a cadence, classify the result into a three-state risk level, or notify anyone. Kestra supplies the schedule, the HEALTHY/STALE/EMPTY_TABLE classification as a first-class branch, and an execution history that shows exactly when ingestion degraded and for how long.
pinot_broker_url).pinot_table and timestamp_column).Local testing:
docker pull apachepinot/pinot:latest
docker run -d --name pinot-quickstart -p 9000:9000 apachepinot/pinot:latest QuickStart -type batch
docker network connect YOUR_KESTRA_NETWORK pinot-quickstart
Run docker network ls first to find the active Kestra network name. QuickStart -type batch loads the sample airlineStats table used as this blueprint's default, on host port 9000, which does not collide with Kestra's own UI on port 8080.
PINOT_USERNAME / PINOT_PASSWORD: credentials used by io.kestra.plugin.jdbc.pinot.Query. The open-source QuickStart image enforces no authentication, so these can be left as empty secrets for local testing; set them for any secured broker.SLACK_WEBHOOK_URL: Slack incoming webhook used by alert_stale_ingestion, alert_empty_table, and the errors block.SECRET_, base64-encoded - for example SECRET_SLACK_WEBHOOK_URL=$(echo -n '<your-webhook-url>' | base64) - and read back in flows with {{ secret('NAME') }}.pinot_broker_url (STRING, default jdbc:pinot://localhost:9000): JDBC URL of the Pinot broker.pinot_table (STRING, default airlineStats): table audited for freshness.timestamp_column (STRING, default __time): epoch-millisecond event time column used for the lag calculation.max_lag_minutes (SELECT: "5", "15", "30", "60", "120"; default "30"): lag ceiling before a non-empty table counts as STALE.outputs.probe_table_freshness.row.row_count: total rows in pinot_table at probe time.outputs.probe_table_freshness.row.latest_event_time: max value of timestamp_column.outputs.probe_table_freshness.row.lag_ms: Pinot's now() minus latest_event_time, in milliseconds.airlineStats table.PINOT_USERNAME, PINOT_PASSWORD (can be empty for QuickStart), and SLACK_WEBHOOK_URL secrets.airlineStats is a static batch QuickStart dataset rather than a live stream, lag_ms will be very large and alert_stale_ingestion is expected to fire on the very first run - see Pitfalls.pinot_table and timestamp_column at a real-time or hybrid table with an active stream consumer to see log_freshness_healthy fire instead.periodic_freshness_check once you trust the check against your own table.pinot_table values with io.kestra.plugin.core.flow.Loop to audit every real-time table in a cluster in one run.row_count across consecutive runs (via a Kestra KV store) to detect a consumer that is connected but no longer advancing, which lag_ms alone would not catch if timestamp_column is not monotonic.airlineStats is a static batch QuickStart dataset, not a live stream. Running this blueprint against the defaults reports STALE on every run because its __time values are fixed at load time - point pinot_table at a genuinely real-time table to see HEALTHY.__time is taken from Kestra's own io.kestra.plugin.jdbc.pinot.Query plugin documentation example, not confirmed as a universal reserved alias for every Pinot deployment. If it does not resolve against your schema, set timestamp_column to your table's actual timeColumnName.io.kestra.plugin.jdbc.pinot.Query task used for any other Pinot read.io.kestra.plugin.jdbc.pinot.Query supports only a single SQL statement. Use io.kestra.plugin.jdbc.pinot.Queries instead for flows that need more than one statement per task.now() returns epoch milliseconds, per Apache Pinot's own datetime functions documentation. If timestamp_column is stored in a different unit (seconds, days), lag_ms and max_lag_minutes will be wrong until the SQL is adjusted.now() function and its epoch-millisecond return type are taken verbatim from Apache Pinot's own datetime functions documentation, and the io.kestra.plugin.jdbc.pinot.Query properties and outputs are taken from the plugin's own source on main - but no Docker container or Kestra engine was run to execute this flow end to end.