Сумма всех ежемесячных подписочных платежей, нормализованных к месяцу.
$$\text{MRR} = \sum_{i} \text{monthly_amount}i$$ Для годовых подписок: $\text{MRR}{annual} = \text{annual_amount} / 12$
Что измеряет: ключевая метрика SaaS-бизнеса. Предсказуемый, повторяющийся доход.
MRR Breakdown (всегда смотрят 4 компонента):
NEW MRR — от новых клиентов в этом месяце
EXPANSION — апгрейды существующих (Tier 1 → Tier 2)
CONTRACTION — даунгрейды
CHURN — отток (отрицательный)
NET NEW MRR = NEW + EXPANSION - CONTRACTION - CHURN
Healthy SaaS: Net New MRR > 0 каждый месяц.
Связанные метрики:
Каспи Pay контекст: для подписочного Premium-тарифа NAQTY DATA:
Pitfalls:
One-time payments не в MRR. Если клиент заплатил 50K единовременно — это не MRR, это booking. MRR — только recurring.
Discounts: если у клиента 50% скидка на год — MRR = list_price × 0.5, не list price.
MRR vs Revenue: MRR — теоретическая. Cash collected revenue может отличаться (delays, refunds).
-- MRR breakdown по типам изменений месяц-к-месяцу
WITH monthly_subs AS (
SELECT user_id,
date_trunc('month', billing_date) AS month,
SUM(amount_kzt) AS mrr_amount
FROM subscriptions
GROUP BY 1, 2
),
changes AS (
SELECT user_id, month,
mrr_amount AS curr_mrr,
LAG(mrr_amount) OVER (PARTITION BY user_id ORDER BY month) AS prev_mrr
FROM monthly_subs
)
SELECT month,
SUM(curr_mrr) AS total_mrr,
SUM(CASE WHEN prev_mrr IS NULL THEN curr_mrr ELSE 0 END) AS new_mrr,
SUM(CASE WHEN prev_mrr < curr_mrr THEN curr_mrr - prev_mrr ELSE 0 END) AS expansion,
SUM(CASE WHEN prev_mrr > curr_mrr THEN prev_mrr - curr_mrr ELSE 0 END) AS contraction
FROM changes
GROUP BY month
ORDER BY month;
subs = subscriptions.assign(
month=subscriptions["billing_date"].dt.to_period("M")
)
mrr = subs.groupby(["user_id", "month"])["amount_kzt"].sum().reset_index()
mrr["prev"] = mrr.groupby("user_id")["amount_kzt"].shift(1)
summary = mrr.groupby("month").apply(lambda x: pd.Series({
"mrr": x["amount_kzt"].sum(),
"new": x.loc[x["prev"].isna(), "amount_kzt"].sum(),
"expansion": (x.loc[x["amount_kzt"] > x["prev"], "amount_kzt"] - x.loc[x["amount_kzt"] > x["prev"], "prev"]).sum(),
}))