Retention Curve и Product-Market Fit: как AI помогает увидеть сигнал в данных
Что такое retention curve?
Retention curve — кривая удержания, показывающая долю пользователей из когорты, которые возвращаются в продукт через N дней/недель/месяцев после первого визита. Плоская кривая, стабилизирующаяся выше нуля (plateau), является количественным сигналом product-market fit: часть аудитории регулярно возвращается, потому что продукт решает реальную проблему.
TL;DR
- -Три паттерна retention curve: плато (PMF-сигнал), медленное затухание (слабый PMF) и обрыв (отсутствие PMF) — визуально похожи, поэтому требуют статистического анализа.
- -Slope за последние 4 недели менее 0.5 п.п./неделю означает стабилизацию кривой; порог для вывода о PMF: плато > 15% для B2C и > 40% для SaaS.
- -Минимум 6 зрелых когорт (12+ недель) необходим для статистически значимого вывода о PMF; 200–500 пользователей в когорте обязательны.
- -AI-промпты структурируют диагностику кривой, сравнение когорт и сегментный анализ — превращая табличные данные SQL в формальный вердикт о PMF.
- -Четыре ложных сигнала PMF: survivorship bias в когортах, малые выборки, сезонность и искусственный retention от push-уведомлений.
В 2014 году Алекс Шульц, VP Growth в Facebook, сформулировал один из самых цитируемых критериев product-market fit в лекции Stanford CS183B: если retention curve выходит на плато параллельно оси X, у вас есть PMF. Не NPS, не revenue growth, не количество регистраций. Форма кривой удержания.
Проблема в том, что на практике retention curves зашумлены. Маленькие когорты дают статистические выбросы, сезонность искажает тренд, а граница между “кривая стабилизируется” и “кривая медленно падает к нулю” визуально неочевидна. Здесь AI-анализ даёт преимущество: модели находят паттерны flattening, которые человеческий глаз пропускает.
Статья о том, как построить retention curves от сырых событий до формального вывода о PMF. SQL-запросы для когортного анализа, статистические тесты на flattening, промпты для AI-интерпретации.
Retention curve как индикатор PMF
Retention curve показывает, какая доля пользователей из определённой когорты возвращается в продукт через N дней/недель/месяцев после первого визита. Day 0 всегда 100%. Day 1 обычно резко падает. Дальше форма кривой говорит о продукте больше, чем любой другой показатель.
Три характерных паттерна:
Плато (PMF-сигнал). Кривая стабилизируется на определённом уровне. Пример: Day 1 — 40%, Day 7 — 25%, Day 14 — 20%, Day 30 — 18%, Day 60 — 17%, Day 90 — 17%. Кривая нашла дно и перестала падать. Часть пользователей остаётся навсегда. Это и есть PMF.
Медленное затухание (слабый PMF или нет PMF). Кривая снижается без явной стабилизации: Day 30 — 15%, Day 60 — 12%, Day 90 — 9%, Day 120 — 7%. На первый взгляд похоже на плато, но каждый месяц теряется 2-3 процентных пункта. При таком темпе через год retention стремится к нулю.
Обрыв (отсутствие PMF). Day 1 — 15%, Day 7 — 3%, Day 30 — 0.5%. Пользователи пробуют продукт и уходят. Никакого намёка на стабилизацию.
Ключевой вопрос: как отличить первый паттерн от второго? Визуально разница между “плато на 17%” и “медленное снижение с 18% до 12% за полгода” может быть неразличима на графике с 90-дневным окном. Нужен формальный анализ.
SQL-запросы для построения retention curves
Базовая структура данных: таблица событий с user_id, event_timestamp и event_name. Минимальная схема:
CREATE TABLE events (
user_id TEXT NOT NULL,
event_name TEXT NOT NULL,
event_timestamp TIMESTAMP NOT NULL
);
Определение когорт
Когорта — группа пользователей, зарегистрированных (или впервые появившихся) в один и тот же период. Первый шаг — вычислить дату первого визита каждого пользователя:
CREATE MATERIALIZED VIEW user_first_seen AS
SELECT
user_id,
DATE_TRUNC('week', MIN(event_timestamp)) AS cohort_week
FROM events
GROUP BY user_id;
DATE_TRUNC('week', ...) группирует пользователей по неделям. Для продуктов с ежедневным использованием (мессенджеры, соцсети) лучше 'day'. Для SaaS с месячным циклом — 'month'.
Расчёт retention по когортам
WITH activity AS (
SELECT DISTINCT
e.user_id,
u.cohort_week,
DATE_TRUNC('week', e.event_timestamp) AS activity_week
FROM events e
JOIN user_first_seen u USING (user_id)
),
retention_raw AS (
SELECT
cohort_week,
EXTRACT(DAYS FROM activity_week - cohort_week)::INT / 7 AS week_number,
COUNT(DISTINCT user_id) AS active_users
FROM activity
GROUP BY cohort_week, week_number
),
cohort_sizes AS (
SELECT
cohort_week,
COUNT(DISTINCT user_id) AS cohort_size
FROM user_first_seen
GROUP BY cohort_week
)
SELECT
r.cohort_week,
r.week_number,
r.active_users,
c.cohort_size,
ROUND(100.0 * r.active_users / c.cohort_size, 2) AS retention_pct
FROM retention_raw r
JOIN cohort_sizes c USING (cohort_week)
WHERE r.week_number >= 0
ORDER BY r.cohort_week, r.week_number;
Результат — таблица, где каждая строка содержит когорту, номер недели и процент вернувшихся пользователей. Это сырые данные для retention curve.
Агрегированная retention curve
Для анализа PMF нужна не россыпь отдельных когорт, а средняя кривая по всем когортам (или по достаточно зрелым):
WITH mature_cohorts AS (
-- Берём когорты возрастом 12+ недель
SELECT cohort_week
FROM user_first_seen
GROUP BY cohort_week
HAVING MAX(CURRENT_DATE - cohort_week::DATE) >= 84
),
retention_data AS (
SELECT
r.week_number,
AVG(r.retention_pct) AS avg_retention,
STDDEV(r.retention_pct) AS std_retention,
COUNT(*) AS num_cohorts
FROM retention_raw r
JOIN mature_cohorts mc USING (cohort_week)
WHERE r.week_number BETWEEN 1 AND 12
GROUP BY r.week_number
)
SELECT
week_number,
ROUND(avg_retention, 2) AS avg_retention_pct,
ROUND(std_retention, 2) AS std_dev,
num_cohorts,
ROUND(std_retention / SQRT(num_cohorts), 2) AS standard_error
FROM retention_data
ORDER BY week_number;
standard_error показывает, насколько надёжно среднее. Если SE больше 3-4 процентных пунктов, данных недостаточно для выводов.
Обнаружение flattening: статистический подход
Визуальная оценка “кривая вышла на плато” субъективна. Формализация требует определения: flattening — это ситуация, когда скорость снижения retention статистически не отличается от нуля.
Метод скользящего наклона
Вычислить наклон (slope) retention curve в окне последних N точек. Если slope не отличается от нуля с заданной значимостью, кривая стабилизировалась.
WITH weekly_retention AS (
-- Используем данные из предыдущего запроса
SELECT week_number, avg_retention_pct
FROM retention_data
ORDER BY week_number
),
slope_calc AS (
SELECT
week_number,
avg_retention_pct,
-- Наклон за последние 4 недели (линейная регрессия)
REGR_SLOPE(avg_retention_pct, week_number)
OVER (ORDER BY week_number ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
AS rolling_slope,
-- R² для оценки линейности
REGR_R2(avg_retention_pct, week_number)
OVER (ORDER BY week_number ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
AS rolling_r2,
-- Количество точек в окне
COUNT(*) OVER (ORDER BY week_number ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
AS window_size
FROM weekly_retention
)
SELECT
week_number,
avg_retention_pct,
ROUND(rolling_slope, 3) AS slope_per_week,
ROUND(rolling_r2, 3) AS r_squared,
CASE
WHEN window_size >= 4 AND ABS(rolling_slope) < 0.5 THEN 'PLATEAU'
WHEN window_size >= 4 AND rolling_slope < -0.5 THEN 'DECLINING'
ELSE 'INSUFFICIENT_DATA'
END AS curve_status
FROM slope_calc
WHERE window_size >= 4
ORDER BY week_number;
Порог 0.5 означает: если retention падает менее чем на 0.5 п.п. в неделю, кривая считается стабилизировавшейся. Для месячных когорт аналогичный порог — 1-2 п.п. в месяц.
Метод сравнения периодов
Альтернативный подход: сравнить retention за две последовательные половины наблюдаемого периода с помощью t-теста.
WITH period_comparison AS (
SELECT
CASE
WHEN week_number BETWEEN 5 AND 8 THEN 'period_1'
WHEN week_number BETWEEN 9 AND 12 THEN 'period_2'
END AS period,
avg_retention_pct
FROM retention_data
WHERE week_number BETWEEN 5 AND 12
)
SELECT
period,
ROUND(AVG(avg_retention_pct), 2) AS mean_retention,
ROUND(STDDEV(avg_retention_pct), 2) AS std_retention,
COUNT(*) AS n_weeks
FROM period_comparison
GROUP BY period
ORDER BY period;
Если разница средних между period_1 и period_2 статистически незначима (p > 0.05), кривая стабилизировалась. Расчёт p-value удобнее вынести в Python или передать AI для интерпретации.
AI-анализ retention curves
SQL даёт числа. AI превращает числа в выводы. Три промпта для последовательного анализа.
Промпт 1: Диагностика формы кривой
Ты — product data analyst. Получи данные retention curve
и определи паттерн.
Данные (формат: week_number | avg_retention_pct | std_dev):
[ВСТАВИТЬ РЕЗУЛЬТАТ SQL-ЗАПРОСА]
Задачи:
1. Классифицируй форму кривой: plateau / slow_decay / cliff
2. Если plateau — укажи, на какой неделе начинается
стабилизация и на каком уровне
3. Рассчитай slope кривой за последние 4 точки
4. Оцени статистическую значимость: достаточно ли данных
для вывода? Какой доверительный интервал?
5. Дай однозначный вердикт: есть PMF-сигнал или нет
Формат ответа: JSON с полями curve_type, plateau_start_week,
plateau_level, final_slope, confidence, pmf_signal (boolean),
reasoning.
Важно запрашивать JSON. Структурированный вывод позволяет автоматизировать пайплайн: запрос → SQL → AI-анализ → алерт в Slack.
Промпт 2: Сравнительный анализ когорт
Ты — product data analyst. Сравни retention curves
нескольких когорт и найди тренд.
Данные retention по когортам:
[ВСТАВИТЬ ТАБЛИЦУ С КОГОРТАМИ]
Задачи:
1. Сравни уровень стабилизации (plateau level)
между ранними и поздними когортами
2. Растёт ли plateau level от когорты к когорте?
(Это сигнал улучшения PMF)
3. Меняется ли скорость первоначального падения
(week 0 → week 4)?
4. Есть ли аномальные когорты? Если да, выдвини гипотезу
почему
5. Спрогнозируй plateau level для следующих 3 когорт
Формат: JSON с полями trend_direction (improving/stable/
degrading), plateau_levels_by_cohort, anomalies,
forecast, confidence.
Этот промпт ловит то, что статический анализ одной кривой пропускает: динамику PMF. Продукт может иметь плато на 15%, но если три месяца назад плато было на 10%, это сильный положительный сигнал.
Промпт 3: Сегментированный анализ
Ты — product data analyst. Проанализируй retention curves
для разных сегментов пользователей.
Сегмент A (органический трафик):
[ДАННЫЕ]
Сегмент B (платный трафик):
[ДАННЫЕ]
Сегмент C (реферальный трафик):
[ДАННЫЕ]
Задачи:
1. Для каждого сегмента: определи форму кривой
и уровень стабилизации
2. Какой сегмент показывает лучший PMF-сигнал?
3. Есть ли сегменты, где PMF отсутствует полностью?
4. Рассчитай разницу в LTV-потенциале между сегментами
(исходя из retention levels)
5. Рекомендации: на какие каналы увеличить расход,
какие сократить
Формат: JSON с полями segments (массив с curve_type,
plateau_level для каждого), best_segment, worst_segment,
ltv_multiplier, recommendations.
Сегментация вскрывает ситуации, когда общая кривая маскирует проблему. Агрегированный retention 18% может означать 30% для органики и 8% для платного трафика. PMF есть, но только для определённой аудитории.
Полный pipeline: от событий до PMF-вердикта
Практическая реализация объединяет SQL и AI в автоматизированный процесс.
Шаг 1: Извлечение данных
-- Полный запрос: retention по неделям, зрелые когорты,
-- с доверительными интервалами
WITH user_first_seen AS (
SELECT
user_id,
DATE_TRUNC('week', MIN(event_timestamp)) AS cohort_week
FROM events
WHERE event_name = 'session_start'
GROUP BY user_id
),
weekly_activity AS (
SELECT DISTINCT
e.user_id,
u.cohort_week,
DATE_TRUNC('week', e.event_timestamp) AS activity_week
FROM events e
JOIN user_first_seen u USING (user_id)
WHERE e.event_name = 'session_start'
),
retention_by_cohort AS (
SELECT
w.cohort_week,
EXTRACT(DAYS FROM w.activity_week - w.cohort_week)::INT / 7
AS week_number,
COUNT(DISTINCT w.user_id) AS active_users,
c.cohort_size,
ROUND(100.0 * COUNT(DISTINCT w.user_id) / c.cohort_size, 2)
AS retention_pct
FROM weekly_activity w
JOIN (
SELECT cohort_week, COUNT(*) AS cohort_size
FROM user_first_seen
GROUP BY cohort_week
) c USING (cohort_week)
GROUP BY w.cohort_week, week_number, c.cohort_size
),
aggregated AS (
SELECT
week_number,
ROUND(AVG(retention_pct), 2) AS avg_retention,
ROUND(STDDEV(retention_pct), 2) AS std_dev,
COUNT(DISTINCT cohort_week) AS n_cohorts,
ROUND(STDDEV(retention_pct) / SQRT(COUNT(DISTINCT cohort_week)), 2)
AS se
FROM retention_by_cohort
WHERE week_number BETWEEN 0 AND 12
AND cohort_week <= CURRENT_DATE - INTERVAL '84 days'
GROUP BY week_number
HAVING COUNT(DISTINCT cohort_week) >= 4
)
SELECT * FROM aggregated ORDER BY week_number;
Шаг 2: Вычисление slope и flattening-метрик
WITH slopes AS (
SELECT
week_number,
avg_retention,
avg_retention - LAG(avg_retention) OVER (ORDER BY week_number)
AS week_over_week_change,
REGR_SLOPE(avg_retention, week_number)
OVER (ORDER BY week_number ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
AS rolling_slope_4w
FROM aggregated
)
SELECT
week_number,
avg_retention,
ROUND(week_over_week_change, 2) AS wow_change,
ROUND(rolling_slope_4w, 3) AS slope_4w,
CASE
WHEN week_number >= 4 AND ABS(rolling_slope_4w) < 0.5
THEN 'FLATTENING'
WHEN week_number >= 4 AND rolling_slope_4w < -0.5
THEN 'DECLINING'
ELSE 'EARLY_STAGE'
END AS status
FROM slopes
ORDER BY week_number;
Шаг 3: AI-интерпретация
Передать результаты обоих запросов в промпт 1 (диагностика формы). Если вердикт — plateau, запустить промпт 2 (динамика по когортам) для подтверждения.
Шаг 4: Формальные критерии PMF
Набор условий, при выполнении которых PMF считается подтверждённым:
| Критерий | Порог | Вес |
|---|---|---|
| Slope за последние 4 недели | |slope| < 0.5 п.п./нед | Обязательный |
| Уровень плато (retention) | > 15% для B2C, > 40% для SaaS | Обязательный |
| Количество зрелых когорт | >= 6 | Обязательный |
| Стандартная ошибка среднего | < 3 п.п. | Рекомендуемый |
| Тренд plateau level по когортам | Не снижается | Рекомендуемый |
| AI-вердикт (промпт 1) | pmf_signal: true | Подтверждающий |
Все три обязательных критерия должны выполняться одновременно. PMF — не один показатель, а совокупность сигналов.
Бенчмарки retention по типам продуктов
Уровень плато сам по себе ничего не значит без контекста индустрии. Ориентиры:
B2C мобильные приложения. Day 1 retention: 25-40% (хорошо), Day 30: 10-15%. Плато выше 15% на Day 90 — сильный PMF-сигнал. Для социальных приложений и мессенджеров бенчмарк выше: 20-25% на Day 90.
SaaS (B2B). Month 1 retention: 80-90%, Month 12: 60-75%. Плато выше 70% на Month 12 — здоровый продукт. Ниже 50% — проблема с value delivery.
E-commerce. Repeat purchase rate Month 3: 20-30%. Плато выше 25% указывает на сформированную привычку покупать в конкретном магазине.
Контентные платформы. Week 4 retention: 15-25%. Плато выше 20% на Week 12 — контент удерживает аудиторию. Ниже 10% — пользователь получил ценность и ушёл (one-shot usage).
Сравнивать retention B2C-приложения с SaaS-бенчмарками бессмысленно. 20% Day 90 retention для мобильной игры означает top-10 перцентиль. Те же 20% Month 3 retention для SaaS с подпиской означают, что 80% платящих клиентов уходят за квартал.
Ложные сигналы PMF
Retention curve может показывать плато при отсутствии реального PMF. Четыре распространённых ловушки.
Survivorship bias в когортах. Если когорты включают только пользователей, совершивших определённое действие (оплата, завершение онбординга), retention будет искусственно завышен. Правильный подход: когорта включает всех, кто зарегистрировался, без фильтрации.
Малые когорты. 50 пользователей в когорте — это шум, не сигнал. Один активный пользователь из 50 даёт 2% retention. Два — уже 4%. Разница в 100% вызвана одним человеком. Минимальный размер когорты для статистически значимых выводов — 200-500 пользователей.
Сезонность. Когорты декабря (предновогодний период) и июля (каникулы) могут вести себя принципиально по-разному. Агрегация без учёта сезонности смешивает сигнал с шумом. Решение: анализировать когорты одного сезона отдельно или добавить сезонную поправку.
Push-notifications и реактивация. Агрессивные push-уведомления искусственно поднимают retention. Пользователь открывает приложение не потому, что нуждается в продукте, а потому что получил уведомление. Такой retention не конвертируется в монетизацию и рассыпается при отключении пушей.
SQL-запрос для проверки survivorship bias:
-- Сравнить retention "всех зарегистрированных" vs
-- "завершивших онбординг"
WITH all_users AS (
SELECT user_id, cohort_week
FROM user_first_seen
),
onboarded_users AS (
SELECT DISTINCT e.user_id, u.cohort_week
FROM events e
JOIN user_first_seen u USING (user_id)
WHERE e.event_name = 'onboarding_complete'
),
retention_all AS (
SELECT
'all_users' AS segment,
week_number,
ROUND(AVG(retention_pct), 2) AS avg_retention
FROM retention_by_cohort
GROUP BY week_number
),
retention_onboarded AS (
-- Аналогичный расчёт только для onboarded_users
SELECT
'onboarded' AS segment,
week_number,
ROUND(AVG(retention_pct), 2) AS avg_retention
FROM retention_by_cohort_onboarded
GROUP BY week_number
)
SELECT * FROM retention_all
UNION ALL
SELECT * FROM retention_onboarded
ORDER BY segment, week_number;
Если разница между двумя сегментами больше 10 п.п. на Week 8+, retention “завышен” за счёт фильтрации. Реальный PMF должен просматриваться на когорте всех зарегистрированных.
Автоматизация: еженедельный PMF-монитор
Вместо ручного анализа — автоматический пайплайн, который каждую неделю выдаёт PMF-статус.
Архитектура
Cron (понедельник, 09:00)
│
▼
SQL-запросы к базе событий
│
├─ Агрегированная retention curve
├─ Slopes и flattening-метрики
└─ Retention по когортам
│
▼
AI-анализ (промпт 1 + промпт 2)
│
▼
Структурированный JSON
│
├─ pmf_signal: true/false
├─ plateau_level: X%
├─ trend: improving/stable/degrading
└─ confidence: high/medium/low
│
▼
Алерт в Slack / Dashboard update
Python-скрипт для оркестрации
import json
from datetime import datetime
def run_pmf_monitor(db_conn, llm_client):
# 1. Выполнить SQL-запросы
retention_data = db_conn.execute(RETENTION_QUERY).fetchall()
slope_data = db_conn.execute(SLOPE_QUERY).fetchall()
# 2. Форматировать данные для промпта
data_str = format_retention_table(retention_data)
slope_str = format_slope_table(slope_data)
# 3. AI-анализ
prompt = DIAGNOSTIC_PROMPT.format(
retention_data=data_str,
slope_data=slope_str
)
response = llm_client.chat(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": prompt}],
response_format={"type": "json_object"}
)
result = json.loads(response.content)
# 4. Проверка формальных критериев
formal_check = {
"slope_ok": abs(result["final_slope"]) < 0.5,
"plateau_ok": result["plateau_level"] > 15,
"cohorts_ok": len(retention_data) >= 6,
"ai_signal": result["pmf_signal"]
}
pmf_confirmed = all([
formal_check["slope_ok"],
formal_check["plateau_ok"],
formal_check["cohorts_ok"]
])
return {
"date": datetime.now().isoformat(),
"pmf_confirmed": pmf_confirmed,
"details": result,
"formal_check": formal_check
}
Модель claude-sonnet-4-6 выбрана осознанно: для табличных данных и числового анализа Sonnet даёт результат, сопоставимый с Opus, при меньшей стоимости. Для задач с неструктурированным контекстом (анализ причин аномалий, стратегические рекомендации) стоит использовать Opus.
Что делать после обнаружения PMF
Три сценария в зависимости от результата анализа.
PMF подтверждён (plateau > порога, slope ~ 0, тренд стабильный/растущий). Фокус смещается с product-development на growth. Retention показывает, что продукт решает задачу. Следующие вопросы: как масштабировать привлечение, какие каналы дают пользователей с лучшим retention (промпт 3), как увеличить plateau level через улучшение онбординга.
PMF не подтверждён (кривая падает, slope значимо отрицательный). Масштабировать привлечение на этом этапе — тратить деньги впустую. Возврат к product-development: интервью с пользователями, анализ момента оттока (на какой неделе происходит максимальное падение), поиск magic number — действия, коррелирующего с retention.
Неопределённость (slope на границе, данных мало, когорты маленькие). Увеличить горизонт наблюдения. Подождать ещё 4-6 недель для формирования зрелых когорт. Увеличить размер когорт через таргетированное привлечение. Повторить анализ.
Ограничения метода
Retention curve — необходимый, но недостаточный критерий PMF.
Продукт может показывать плато retention при низкой готовности платить. Пользователи возвращаются, но переход на платный план не происходит. Формально retention стабилен, но бизнес-модель не работает.
Retention не учитывает глубину использования. 17% пользователей возвращаются, но если каждый визит длится 30 секунд вместо 10 минут, engagement снижается при стабильном retention.
Для полной картины retention curves дополняются метриками: revenue retention (NRR), depth of engagement (DAU/MAU ratio), willingness to pay, Net Promoter Score. Retention — фундамент, остальные метрики — этажи.
Итог
Retention curve, выходящая на плато, остаётся одним из самых надёжных количественных сигналов PMF. SQL-запросы для когортного анализа строят кривую. Статистические тесты на flattening формализуют субъективную оценку. AI-промпты автоматизируют интерпретацию и ловят паттерны, которые ускользают при ручном анализе.
Минимальный набор для старта: таблица событий с user_id и timestamp, запрос для расчёта retention по когортам, один промпт для диагностики формы кривой. Всё остальное — слои, добавляющие точность: сегментация, сравнение когорт, автоматический мониторинг. Каждый слой сокращает неопределённость, но базовый ответ на вопрос “есть ли PMF” доступен уже на первом.
Нужна помощь с анализом retention? Я помогаю стартапам внедрять AI-решения и строить продукты — belov.works.