Query icon
Script icon
If icon
SlackIncomingWebhook icon
Log icon
Schedule icon

Weekly Cohort Retention Matrix and Churn Watch with DuckDB

Build a weekly cohort retention matrix from an events parquet file with DuckDB SQL, then alert Slack when the newest cohort drops past your threshold.

Categories
BusinessData

Weekly retention questions usually end in a spreadsheet that nobody trusts by the next quarter. This flow computes a cohort retention matrix straight from an events parquet file with embedded DuckDB SQL, judges the newest cohort cell by cell against the trailing average of the cohorts before it, and posts a Slack summary the moment retention falls further than the threshold you set. Everything lives in one declarative workflow, a disabled weekly schedule, a SQL matrix build, a small analyzer, and a conditional alert branch.

How it works

  1. seed_sample_events (io.kestra.plugin.jdbc.duckdb.Query) writes a deterministic sample events.parquet with COPY ... TO and outputFiles, so the flow is runnable before you have a warehouse of your own.
  2. cohort_matrix (io.kestra.plugin.jdbc.duckdb.Query) mounts that parquet through inputFiles, assigns every user to the week of their first activity with date_trunc, and measures weeks since with date_diff.
  3. The same statement pivots the events into one row per cohort and weeks-since cell, then COPY ... TO writes cohort_matrix.csv through outputFiles so the matrix is a downloadable artifact as well as the next task's input.
  4. analyze_cohorts (io.kestra.plugin.scripts.python.Script) reads that CSV with the standard library, keeps the newest cohort_weeks cohorts, and compares every cell of the newest cohort with the average of the same weeks-since column across the earlier cohorts.
  5. Any cell that fell by more than churn_drop_threshold_pct points lands in flagged_cohorts, and the task emits its verdict with the ::json:: output protocol plus a one line Slack summary and a cohort_report.json artifact.
  6. route_churn (io.kestra.plugin.core.flow.If) branches on the flag, the true branch posts the drops and the report reference to Slack and the false branch logs a green result.
  7. The errors handler repeats the same webhook for failures, and the flow outputs the verdict, the flagged cohorts, the worst drop, and the report file.

What you get

  • A retention matrix with one row per weekly cohort and weeks-since offset, ready to chart or hand to a dashboard.
  • A churn verdict of churn_flagged, healthy, or insufficient_data with the exact cells that caused it.
  • The worst single drop in percentage points, so a quick scan of the execution outputs answers the question.
  • A Slack message carrying the drops, the overall trend, and a reference to the full JSON report.
  • Two artifacts per run, the CSV matrix and the JSON report, both preserved in internal storage.

Who it's for

  • Growth and product analysts who track weekly cohorts and need the numbers refreshed without manual spreadsheet work.
  • Data engineers who want retention logic living next to the ingestion pipeline instead of in a notebook.
  • Customer success and lifecycle teams who need an early warning when a recent cohort stops coming back.

Why orchestrate this with Kestra

DuckDB answers a retention question in milliseconds, but it does not know when to run, what to do with the answer, or who to tell. Wrapping the query in Kestra gives the analysis a weekly schedule, typed file handoffs between SQL and Python, a branch that only wakes the channel when the threshold is crossed, and an execution history that shows the exact matrix behind any past alert. Failures land in the same webhook instead of vanishing into a cron log, and the threshold and cohort window are inputs you can change without editing code. The result is a retention check that behaves like production monitoring rather than a one off query.

Prerequisites

  • A Kestra instance with the DuckDB JDBC plugin available, since both SQL tasks run in process and need no server.
  • An events parquet file with a user identifier column and an activity date column, for example user_id and event_date.
  • The sample seed task only exists so the blueprint runs out of the box, you can delete it once your own parquet is mounted through inputFiles.
  • A Slack incoming webhook for the churn branch and the failure handler.

Secrets

  • SLACK_WEBHOOK_URL: the incoming webhook URL used by the alert branch and by the errors handler. Store it as a Kestra secret in your namespace, the flow never hardcodes a URL or token.

Quick start

  1. Add the SLACK_WEBHOOK_URL secret to your namespace, either from the UI or in your version controlled namespace configuration.
  2. Execute the flow once with the defaults, the seeded sample includes a deliberate drop in the newest cohort so the alert branch shows itself.
  3. Open the report output to inspect cohort_report.json, then review the verdict and worst_drop_pct outputs on the execution overview.
  4. Enable the weekly schedule by pasting this snippet into the flow and removing disabled: true when you are ready:
triggers:
  - id: weekly_refresh
    type: io.kestra.plugin.core.trigger.Schedule
    cron: "0 6 * * 1"
    disabled: true
  1. Tune cohort_weeks and churn_drop_threshold_pct from the inputs panel on each run, no code change needed.

How to extend

  • Point cohort_matrix.inputFiles at your own events parquet and delete the seed task, the rest of the flow is unchanged.
  • Change the SQL to bucket by month instead of week, or to split retention by plan tier with an extra grouping column.
  • Add a second threshold input that pages on call for a catastrophic drop while a smaller drop only posts to the analytics channel.
  • Store the matrix in a warehouse table with a JDBC load task so BI tools can query the same numbers the alert quotes.
  • Render the matrix as a chart image with a Python task and attach it to the Slack message.
  • Branch further with io.kestra.plugin.core.flow.Switch to open a ticket automatically on the flagged path.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.