Доля пользователей или дохода, переставших использовать продукт за период.
$$\text{Churn} = \frac{\text{lost in period}}{\text{active at start of period}}$$
Два типа churn:
Часто MRR churn < customer churn (уходят дешёвые юзеры) или наоборот (уходят дорогие).
Бенчмарки для месячного churn:
| Категория | Healthy | Concerning |
|---|---|---|
| Consumer subscription | 5-7% | 10%+ |
| B2B SaaS small | 3-5% | 7%+ |
| B2B SaaS enterprise | 0.5-1% | 2%+ |
| Banking apps | 1-2% | 5%+ |
| Streaming (Netflix) | 4-6% | 10%+ |
Pitfalls:
Voluntary vs involuntary churn: часть «churn» — это failed payments (карта истекла) — это техническая проблема, не uxperience. Раздели и решай отдельно.
Survivorship bias: «у нас churn 3%» легко звучит хорошо, пока не разделишь на новые vs returning. У новых может быть 20%, у returning 1%. Среднее 3%.
Definition matters: «не залогинился 30 дней» vs «отменил подписку» — совсем разные churn. Выбери понятное и придерживайся.
Расчёт LTV через churn:
$$LTV = \frac{ARPU}{churn}$$
Если churn 5% месяц → клиент живёт 20 месяцев → LTV = 20 × ARPU.
-- Customer churn по cohort
WITH cohorts AS (
SELECT user_id, date_trunc('month', registered_at)::date AS cohort
FROM users
),
activity AS (
SELECT user_id, date_trunc('month', event_time)::date AS active_month
FROM events
GROUP BY 1, 2
)
SELECT
c.cohort,
COUNT(DISTINCT c.user_id) AS cohort_size,
COUNT(DISTINCT CASE WHEN a.active_month = c.cohort + INTERVAL '1 month' THEN c.user_id END) AS retained_m1,
1.0 - COUNT(DISTINCT CASE WHEN a.active_month = c.cohort + INTERVAL '1 month' THEN c.user_id END)::float
/ COUNT(DISTINCT c.user_id) AS month_1_churn
FROM cohorts c
LEFT JOIN activity a ON a.user_id = c.user_id
GROUP BY 1
ORDER BY 1;
# По месячным когортам
users["cohort"] = users["registered_at"].dt.to_period("M")
events["active_month"] = events["ts"].dt.to_period("M")
cohort_size = users.groupby("cohort")["id"].nunique()
m1_active = (events.merge(users[["id", "cohort"]], left_on="user_id", right_on="id")
.query("active_month == cohort + 1")
.groupby("cohort")["user_id"].nunique())
churn_m1 = 1 - (m1_active / cohort_size)