A/B-тесты и канареечный деплой

Новая модель лучше на валидации — но прав ли валидационный набор? Проверяем на живом трафике осторожно.

Канареечный деплой направляет малую долю трафика на новую модель; A/B-тест сравнивает две версии статистически на реальной бизнес-метрике.

Почему мало офлайн-метрики

Модель может выигрывать на валидации, но проигрывать в проде: валидационный набор не отражает живой трафик, бизнес-метрика отличается от технической (выше accuracy не всегда = выше выручка). Поэтому финальное решение принимают на реальном трафике, но без риска обрушить всё сразу.

Канареечный деплой

Новую версию выкатывают на маленькую долю (1%, 5%, 10%) трафика — это «канарейка». Если метрики и качество в норме — долю постепенно повышают до 100%. Если что-то не так — мгновенно возвращают весь трафик на старую версию. Имя — от шахтёрских канареек, предупреждавших об опасности.

                   95% --> [модель v6 (текущая)]
клиенты --> роутер
                    5% --> [модель v7 (канарейка)]   <- следим за метриками
        нормально? -> 5% -> 25% -> 50% -> 100%
        проблема?  -> вернуть на 0%, разбираться

A/B-тест

Здесь цель — не плавный выкат, а статистически достоверное сравнение. Трафик делят (часто 50/50) между версией A (контроль) и B (новая), и измеряют бизнес-метрику: конверсию, выручку, удержание. Решение принимают только когда разница статистически значима, иначе можно принять шум за улучшение.

Значимость своими руками

Покажем, почему нельзя верить «B чуть лучше» без проверки. Сравним конверсии и грубо оценим, отличаются ли они с учётом размера выборки.

import math

def conv_rate(success, n):
    return success / n

def z_two_prop(s_a, n_a, s_b, n_b):
    p_a, p_b = s_a / n_a, s_b / n_b
    p = (s_a + s_b) / (n_a + n_b)
    se = math.sqrt(p * (1 - p) * (1 / n_a + 1 / n_b))
    return (p_b - p_a) / se if se > 0 else 0.0

# мало данных: B "лучше", но это может быть шум
print("Маленькая выборка:")
z1 = z_two_prop(20, 200, 26, 200)
print(f"  A=10.0%  B=13.0%  z={z1:.2f}  значимо(|z|>1.96)? {abs(z1)>1.96}")

# много данных: та же разница уже значима
print("Большая выборка:")
z2 = z_two_prop(2000, 20000, 2600, 20000)
print(f"  A=10.0%  B=13.0%  z={z2:.2f}  значимо(|z|>1.96)? {abs(z2)>1.96}")

Вывод:

Маленькая выборка:
  A=10.0%  B=13.0%  z=0.94  значимо(|z|>1.96)? False
Большая выборка:
  A=10.0%  B=13.0%  z=9.40  значимо(|z|>1.96)? True

Одна и та же разница 10% против 13% на 200 наблюдениях — статистический шум (|z|<1.96), а на 20000 — уверенное улучшение. Вот почему A/B-тесту нужен достаточный размер выборки, а решение принимают по значимости, а не по «на глаз лучше».

Канарейка против A/B

КанарейкаA/B-тест
Цельбезопасно выкатитьстатистически сравнить
Долярастёт 1%→100%фиксирована (напр. 50/50)
Решение«не сломалось ли»«значимо ли лучше»

Как работает под капотом

Роутер (балансировщик, фича-флаг, service mesh) разделяет трафик по версиям и проставляет в лог версию модели. Метрики считаются раздельно по версиям (вот зачем версия в логе из прошлого раздела). Канарейка управляется автоматикой: правила «p99 в норме И качество не упало → повысить долю; иначе → откатить». A/B-тест требует случайного и устойчивого назначения (один пользователь всегда в одной группе) и достаточной длительности, чтобы набрать значимость.

Частые ошибки

  • Сразу 100% трафика на новую модель. Любая скрытая проблема ударит по всем пользователям.
  • Читать A/B-тест слишком рано. Подглядывание до набора выборки выдаёт шум за победу.
  • Нестабильное назначение групп. Если пользователь скачет между A и B, результат загрязнён.
  • Сравнивать по технической метрике вместо бизнес-метрики. Выше f1 ≠ выше выручка.

Итог

  • Канареечный деплой безопасно выкатывает модель, повышая долю трафика и откатываясь при проблеме.
  • A/B-тест статистически сравнивает версии на бизнес-метрике; решение — по значимости, а не «на глаз».
  • Обоим нужен роутинг с версией в логах; A/B дополнительно требует устойчивого назначения групп и достаточной выборки.
Проверьте себя
1. В чём основная цель канареечного деплоя?
AСтатистически доказать превосходство
BБезопасно выкатить новую версию на малой доле трафика с возможностью быстрого отката
CУскорить инференс
DСократить размер модели
2. Почему одну и ту же разницу конверсий нельзя считать улучшением на маленькой выборке?
AМаленькие числа считать нельзя
BРазница может быть статистическим шумом; нужна достаточная выборка для значимости
CB всегда хуже A
DТак работает только на GPU
3. Чем A/B-тест отличается от канареечного деплоя?
AНичем
BA/B статистически сравнивает версии на бизнес-метрике, канарейка безопасно выкатывает с ростом доли
CКанарейка точнее статистически
DA/B не использует трафик