×Закрыть

Немного про A/B-тестирование на проекте Jiji.ng

В этой статье я хотел немного написать про наш опыт A/B-тестирования на нашем проекте и немного о том, какое отношение математическая статистика имеет к A/B-тестированию, а также послушать опыт других коллег из продуктовых компаний.

Сперва небольшой пролог про нашу компанию и чем мы занимаемся: я — CTO Jiji, Николай Зорин. До основания компании я работал в ряде других проектов и потратил на электронную коммерцию более 10 лет жизни. Проект Jiji мы запустили с нуля 2,5 года назад. Первая наша команда состояла из 4-х человек. С тех пор мы заметно выросли, в нашем киевском офисе работает свыше 30 человек — в основном, разработчики, аналитики и маркетологи, а в нашем офисе в Лагосе — уже более 100 сотрудников.

Самый частый вопрос, который мне задают на собеседовании: почему Нигерия? Мы проверяли: у людей есть ассоциации с джунглями, каннибализмом и жарким климатом. Но страна очень большая — 200 млн человек населения, ВВП очень быстро растет, количество интернет-пользователей больше, чем население Украины, а развитие всех интернет-ресурсов находится на уровне Украины 2000-2002 годов.

Это практически единственный крупный рынок в мире, где можно точно предугадать, каким будет интернет через 5, 10 и 15 лет. Для Генезиса рынок Нигерии стратегически важен — это один из немногих рынков в мире, где все еще только начинается и можно поучаствовать в развитии сверхбыстроменяющейся индустрии для десятков миллионов человек.

На текущий момент мы построили уже довольно интересную историю, хотя работы впереди еще очень много. У нас 4-5 млн уникальных пользователей в месяц и крупнейшее приложение в нашей нише в Нигерии. У нас несколько тысяч платных клиентов, несколько десятков тысяч бесплатных — и это все люди, которые кормят свои семьи, продавая товары через наш сайт. Я считаю социальную миссию очень важной в бизнесе, для всей нашей команды очень важно делать то, что приносит людям пользу.

Как в любом продукте, у нас есть огромное количество самых разнообразных идей, которые можно тестировать и пробовать. Эти идеи следуют и из нашего (в основном, украинского) опыта электронной коммерции, и из анализа конкурентов — больше всего, мы смотрим на Индию. В голову постоянно приходят идеи невероятных улучшений, многие из которых не проходят первой критики или откладываются до лучших времен из-за недостатка ресурсов.

Но некоторые идеи попадают в спринт и воплощаются в жизнь.

Как мы пришли к идее A/B-тестов

Первые улучшения всегда давали нам очень значимые приросты (+20%), и мы видели и радовались, что изменения приносят пользу сайту и радость пользователям. Но со временем масштаб приростов сильно снижается, и одним из самых важных вопросов становится: как же оценить новое «улучшение»? Действительно ли это улучшение или лишь флуктуация, которая может в действительности огорчить пользователей и ухудшить показатели бизнеса?

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

1. «Стартап мода» — первые полгода

В самом начале развития проекта новые фичи принимались в том случае, если они нравились команде и работали без видимых ошибок. Мы не очень смотрели на конверсии — в целом они и так росли, все изменения на старте приводят к значимой обратной связи. Может быть, именно в этом вся прелесть стартапов — большинство вещей, которые ты делаешь, имеют сильную позитивную обратную связь.

Как только аудитория сайта начала расти, влияние каждой фичи упало, и пришло понимание, что новые фичи нужно оценивать с точки зрения бизнеса.

Тогда мы немного изменили наш подход:

2. «Сравнение с прошлой неделей»

После выкатки новой фичи, конверсии по ней за 3 дня сравнивались с конверсиями за те же дни на прошлой неделе. И фича одобрялась лишь в случае, если она не снижает (лучше, конечно, повышает) конверсии.

Немного спустя стало понятно, что показатели конверсий — штука нестабильная и может сильно колебаться в зависимости от национальных праздников, количества и источников привлекаемых пользователей и многих других факторов. На этом этапе мы внедрили:

3. Flag toggles, с чего и началось наше A/B-тестирование

Flag toggles — флаги которые позволяют включать функционал для определенной части аудитории. Это позволило нам избежать влияния колебаний трафика между разными днями и получить более ясное влияние фич: старая и новая версия тестируются одновременно, в одинаковых условиях.

Кроме проведения A/B-тестов, флаги можно использовать для временного включения функционала для определенных пользователей, например для админов или тестировщиков.

Также важно убедиться, что наши флаги делят аудиторию так, как мы ожидаем, то есть нет статистически значимых изменений конверсий в двух разных аудиториях, использующих один и тот же функционал (так называемый А/А-тест).

Первые шаги

Сперва наш процесс выглядел примерно так:
1. Делим аудиторию пополам.
2. Одна половина юзеров видит старую версию сайта, другая — новую.
3. Отправляем данные в GA.
4. Ждем, пока новая фича победит старую или безнадежно проиграет, и заканчиваем тест.

Такая версия A/B-теста применялась некоторое время и показывала хорошие результаты, до тех пор пока наша аудитория не была очень большой, а средний показатель улучшений конверсий новой фичей составлял 5-10%.

Но время шло, аудитория росла, и улучшения от новой фичи по конверсиям стали составлять 1-2%. Запуск 10 фич, каждая из которых — как нам казалось — улучшала конверсии на 1%, не приводило к улучшению общих показателей по сайту на 10%.

Мы осознали две проблемы:
— не ясно, как определить, реально ли создается улучшение на 1% или это лишь временные колебания;
— после того, как количество событий в GA перевалило за 500 тысяч, бесплатная версия GA стала делать семплирование, что со статистической точки зрения абсолютно скомпрометировало ее показатели для A/B-тестов.

Нужно было вспоминать статистику и решать что-то с хранилищем для результатов A/B-тестов. Проблема с хранилищем доставила нам меньше всего хлопот — мы использовали кластер elasticsearch, а хранение событий типа просмотров страниц и конверсий мало чем отличается от хранения логов. Поэтому мы естественным образом пришли к использованию elasticsearch.

Учитывая особенности elasticsearch, а именно его искривленное понимание операций удаления (link), сразу стоит создавать отдельный индекс под каждый месяц (pageview-YYYY-MM): только в этом случае получится безболезненно удалять старые данные.

Из курса статистики вспоминается доверительный интервал (link), который получает на вход конверсию и размер выборки, на которой проводился тест, и может сказать с заданной вероятностью, например 95%, что реальная конверсия лежит в некотором интервале.

Пример 1: мы провели тест, и в группу А попало 1,000 пользователей, 200 из них совершили конверсию. Итого фактическая конверсия — 20%. Если мы посчитаем доверительный интервал, то получим, что с вероятностью 95% реальная конверсия лежит в интервале от 17,52% до 22,48%.

Пример 2: в выборке Б у нас было 995 пользователей, и 230 из них совершили конверсию. Фактическая конверсия получается 23,1%, а реальная с вероятностью 95% лежит в пределах от 20,48% до 25,72%

Как мы видим, эти интервалы пересекаются, а значит, хотя фактически конверсия в группе Б выше на 15,5%, в действительности она может оказаться хуже, чем в выборке А.

Также стоит понимать, что хотя мы и берем точность доверительного интервала 95%, эксперимент может утверждать, что результаты одной выборки лучше, чем другой, с уверенностью .95 * .95, то есть 90,25%, так как погрешность может дать каждый из 2-х доверительных интервалов независимо друг от друга.

Погрешности метода

Вооружившись новым хранилищем данных и чудо-инструментом подсчета доверительного интервала, мы вступили в новый поединок с A/B-тестами.

Приняв новым методом N-ое количество фич (и отклонив еще большее!), мы вдруг осознали, что этот подход довольно уязвим перед продакт-менеджерами, у которых есть KPI и которым нужно, чтобы новая фича «улучшила» показатели сайта.

Загвоздка крылась в самом определении доверительного интервала, которое грубо звучит так: с вероятностью X реальное значение конверсии лежит в пределах [a;b], из этого следует, что с вероятностью до (1-Х) значение конверсии лежит вне интервала [a;b].

Если брать пример выше, то имея доверительный интервал 95% (а вероятность ошибки данного метода до (100 — 90,25) = 9,75%), мы получаем, что при каждом измерении результатов A/B-теста мы с вероятностью до 9,75% можем получить false positive вердикт, то есть принять фичу, которая не принесла улучшений или, наоборот, все ухудшила.

Допустим, мы можем увеличить точность доверительного интервала до 97,5%, чтобы получить погрешность метода не более 5%, и смириться с такой погрешностью.
Но это только на первый взгляд...

Рассмотрим следующий случай. Допустим, мы проводим A/B-тест в течении 2-х дней и записываем результаты каждый день:

Таким образом, только 2 фичи принесли реальные улучшения.

Но будь вы продакт-менеджером, у которого есть вагон тасок и которому нужно быстро принимать решении по таскам, будете ли вы ждать еще сутки, если уже на первый день доверительный интервал говорит, что изменения уже существенны с вероятностью 95%?

Рассмотрим действия нетерпеливого продакта:

Третий сценарий в первый день показался нам положительным. Хотя он и был замерен на малой выборке, но показал значительные улучшения в конверсиях — возможно, из-за каких-то отклонений

В результате мы ошибочно решили, что третий сценарий дает нам некое улучшение.
Таким образом, реальная вероятность ошибки уже выше заявленных 5%, и больше шансов ошибочно принять фичу, которая ухудшит конверсии.

Масштаб проблемы

Насколько же большая эта проблема? Например, чтобы получить реальную вероятность ошибки не выше 5%, если мы проверяем эксперимент 10 раз, нам нужно, чтобы вероятность ошибки каждого измерения была не выше 1%.

Таким образом, чем чаще продакты заглядывают в результаты тестирования, тем выше вероятность принять плохую фичу.

Объяснить им это — отдельная проблема, ведь довольно тяжело заставить людей понимать то, от незнания чего зависят их результаты.

Тут нужно было что-то решать. Понятно, что еще до начала эксперимента нужно как-то определить размер выборки, который будет репрезентативным, но как это сделать? На помощь пришел подход из статистики, который называется: two independent proportions power analysis.

Решение

Суть этого подхода заключается в том, что до начала эксперимента мы должны измерить текущую конверсию, сделать предположение о том, насколько наша фича может изменить конверсию, задать максимально приемлемую вероятность false positive и false negative. В результате мы получим размер выборки, на которой мы с заданной точностью можем принять решение.

Допустим, текущая конверсия составляет 20%. Мы предполагаем, что новая фича улучшит конверсию на 1%, тогда новая конверсия составит 20,2%. Мы можем принять вероятность ошибочного принятия фичи не более 5% и хотим быть на 80% уверены, что мы не приняли реальное улучшение за флуктуацию. Тогда размер выборки на каждой из групп будет составлять 630260.

Следовательно, нам нужно запустить A/B-тест, подождать, пока мы получим 630к сессий в каждой выборке, и если после этого конверсия в группе Б будет не менее чем на 1% выше конверсии в группе А, то мы можем считать эксперимент успешным.

Этот подход позволил нам решить проблему с повышением ошибки при повторных проверках при A/B-тесте. Также мы можем сделать более точные прогнозы по длительности A/B-тестов. Главное — мы избежали принятия фич, которые на коротком промежутке времени дают неоправданно высокий прирост в конверсиях.

Куда мы планируем развиваться

Через некоторое время данных о конверсиях у нас стало уже более чем достаточно, и мы могли использовать эти данные, чтобы провести байесово А/B-тестирование.

Исходя из имеющихся данных, можно построить функцию распределения вероятности (бета-распределение) для версии А. Получая новые данные и обновляя имеющееся распределение, мы получаем новое распределение.

Чем байесово тестирование лучше? Мы получаем распределение вероятностей, а не просто оценку достоверности. Наличие априорной функции распределения позволяет проводить повторные тесты и быстрее принимать решения при низком базовом уровне конверсий.

Elasticsearch как бэкэнд для событий — хорошо, но со временем потребности могут вырасти. Например, делать выборки с join в эластике — проблематично. В планах — попробовать Clickhouse — «столбцовая СУБД для OLAP», как называет её Яндекс. Из обещанного — линейная масштабируемость, очень быстрые запросы, статистические функции, бесплатность.

Выводы

A/B-тест — достаточно простой инструмент, но, в то же время, требующий понимания базовых статистических инструментов. Без аккуратного проведения он может привести к существенным затратам ресурсов на развитие бесполезного функционала или даже к снижению конверсий по сайту.

Буду рад ответить на вопросы в комментариях, а также приведу список интересной для меня литературы:
— Most winning A/B test results are illusory;
— Bayesian A/B Test Calculator;
— Elasticsearch for Analytics;
— A/B Testing Tech Note: determining sample size;
— Feature Toggles;
— How Not To Run An A/B Test;
— Bayesian A/B campaign testing (and design);
— ClickHouse;
— Inference for Proportions: Comparing Two Independent Samples.

20 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

Спасибо! Было познавательно!

Ви молодці, ви пройшли практично весь шлях еволюції А/Б тестування.
Але ще не зробили один крок.
Якщо я вірно зрозумів, це щось схоже на те, про що ви згадали вкінці статті.
Якщо вірити заявам, то Google Analytics має працювати базуючись саме на ідеях «багаторукого бандиту».
Суть в тому, щоб не лише визначити яка з альтернатив краща, а й зменшити кількість трафіку, який попаде на «погану» альтернативу (не кожен може дозволити собі 600к людей пустити на тест).

Винаходити тут практично нічого не потрібно... все досить гарно розжовано в гугловських статтях:
support.google.com/...tics/answer/2844870?hl=en
support.google.com/analytics/answer/2846882
www.economics.uci.edu/~ivan/asmb.874.pdf

Загвоздка крылась в самом определении доверительного интервала, которое грубо звучит так: с вероятностью X реальное значение конверсии лежит в пределах [a;b], из этого следует, что с вероятностью до (1-Х) значение конверсии лежит вне интервала [a;b].

Достаточно грубо, что бы быть неправдой:) Реальное значение конверсии не есть случайной величиной, т.о. вероятностная интерпретация в данном случае не применима. Более того, реальная конверсия либо лежит в этом интервале, либо нет. Вероятность относится к самому интервалу: при повторных сэмплированиях 95% всех доверительных интервалов, построенных таким образом, будут содержать реальное значение конверсии.

Через некоторое время данных о конверсиях у нас стало уже более чем достаточно, и мы могли использовать эти данные, чтобы провести байесово А/B-тестирование.

Скорее наоборот, байесовская статистика помогает избежать сбора «нужного» количества данных и позволяет принимать разумные решение на малых выборках. Так же, она дает возможность ответить на вопрос «с какой вероятностью А лучше, чем В?». Качественные ресурсы на эту тематику: cdn2.hubspot.net/..._technical_whitepaper.pdf + блог автора статьи.

Так же, есть ряд решений, которые позволяют принимать решения быстрее, используя традиционную частотную статистику:
elem.com/...t1-rigorous.html#use-this
www.evanmiller.org/...equential-ab-testing.html

Касательно доверительного интервала, можешь уточнить?
Из твоих слов выходит что при проведении 100 опытов, в 95 случаях реальное значение попадет в интервал.
От сюда можно сделать вывод, что если взять один конкретный интервал, то с вероятностью 95%, он покрывает реальную величину.
А от сюда выходит что до 5% вероятность, что значение будет вне интервала (то что сказанно в статье)

В каком месте несоответствие?

А от сюда выходит что до 5% вероятность, что значение будет вне интервала (то что сказанно в статье)

Выходит немного другое: вероятность того, что интервал не покрыл реальное значение равна 5%. А вероятность того, что значение вне интервала либо 0, либо 1.

ОК, пожалуй это лишь конфликт определений)
Сути это не меняет, т.к если интервал не покрыл значение -> значение вне интервала.

Смотрели ли вы в сторону BigQuery и если да — что думаете по этому поводу?

Нет, BigQuery не рассматривали, не могу рассказать ничего интересного

Классная статья! Я смотрю вы пошли чуть другим путем нежели мы. Молодцы, приходите делиться опытом к нам :)

Зайдем при случае! А тем временем ты бы мог вкратце описать как проходят тесты у вас :)

Мы собираем данные типа кликов, просмотров и переходов в Hadoop + есть данные о пользователях и транзакциях хранящиеся в PostgreSQL. Все это агрегируем Spark-ом. На фронтенде есть PromTracker, который собирает все данные и выдает пользователям уникальные id. Флагами распределяем трафик и собираем уникальные события для каждой группы пользователей. Дальше считаем так же как и вы, только чуть-чуть быстрее. Интересное решение заюзать ES, кстати.

Супер статья! Мы тоже начали использовать clickhouse на небольшом кластере серваков .
Первые результаты впечатляют — за сутки записываем в clickhouse таблицу 60 миллиардов событий из 30 колонок. Простые запросы выполняются со скорость в миллиарды строк в секунду :) Запросы посложнее — сотни миллионов строк в секунду. Шардирование, репликация, бэкапы и удаление старых данных в clickhouse cделаны просто и понятно.

Спасибо!
Можно подробнее про размеры небольшого кластера?

Сейчас тестируем на шести серваках в google compute engine. В каждом сервере по 4 ядра CPU, 26 гиг оперативки и persistent disk storage (не SSD) в несколько терабайт.

Сейчас загрузка по CPU обычно не превышает 20%, в пиках достигает 50% (это когда clickhouse мержит большие куски данных).
За сутки clickhouse сжирает в сумме терабайт места на persistent disk storage.

Почему google compute engine, а не более дешевый хостер?
— В gce проще тестировать. Можно легко менять конфигурацию кластера за пару минут вместо того, чтобы ждать, пока хостер предоставит и настроит необходимое оборудование.
— Persistent disk storage можно при необходимости динамически расширять до 65Тб на каждом сервере.
— Гугл гарантирует, что persistent disk storage не выйдет из строя. Т.е. не нужно делать репликацию против hardware error . Репликация может только понадобиться для увеличения пропускной способности select’ов. Нам это пока не нужно, поэтому у нас только шардирование без репликации.

Вот скорость сканирования строчек при выполнении аналитических запросов:

Суммарное количество всех событий за сегодня. 9 миллиардов строк в секунду:

:) select count(*) from events_all where EventDate = today();

SELECT count(*)
FROM events_all 
WHERE EventDate = today()

┌─────count()─┐
│ 24691276705 │
└─────────────┘

1 rows in set. Elapsed: 2.762 sec. Processed 24.69 billion rows, 49.38 GB (8.94 billion rows/s., 17.88 GB/s.) 

Количество событий определенного типа за сегодня. 1.5 миллиарда строк в секунду:

:) select count(*) from events_all where EventDate = today() and Event in ('campaignRequest');

SELECT count(*)
FROM events_all 
WHERE (EventDate = today()) AND (Event IN 'campaignRequest')

┌────count()─┐
│ 5564513146 │
└────────────┘

1 rows in set. Elapsed: 3.814 sec. Processed 5.57 billion rows, 16.70 GB (1.46 billion rows/s., 4.38 GB/s.) 

Топ10 стран за сегодня для определенного типа события. 620 миллионов строк в секунду:

:) select CountryCode, adreqs from (select Country, count(*) adreqs from events_all where Event in ('requestRow') and EventDate = today() group by Country) any left join countries using Country order by adreqs desc limit 10;

SELECT 
    CountryCode, 
    adreqs
FROM 
(
    SELECT 
        Country, 
        count(*) AS adreqs
    FROM events_all 
    WHERE (Event IN 'requestRow') AND (EventDate = today())
    GROUP BY Country
) 
ANY LEFT JOIN countries USING (Country)
ORDER BY adreqs DESC
LIMIT 10

┌─CountryCode─┬─────adreqs─┐
│ US          │ 1650935235 │
│ CA          │   46110991 │
│ AU          │   27769089 │
│ GB          │   26752529 │
│ IT          │   18845510 │
│ FR          │   16362192 │
│ ES          │   14527652 │
│ BR          │   14338710 │
│ DE          │   12998304 │
│ MX          │   12581293 │
└─────────────┴────────────┘

10 rows in set. Elapsed: 3.079 sec. Processed 1.91 billion rows, 9.57 GB (621.64 million rows/s., 3.11 GB/s.) 

Топ10 наиболее популярных операционок для браузера Safari для определенного типа события за последний час. 300 миллионов строк в секунду:

:) select OSName, adreqs from (select OS, adreqs from (select Browser, OS, count(*) adreqs from events_all where EventDate = today() and Event in ('requestRow') and EventTime > now() - 3600 group by Browser, OS) any left join browsers using Browser where BrowserName = 'Safari') any left join oses using OS order by adreqs desc limit 10;

SELECT 
    OSName, 
    adreqs
FROM 
(
    SELECT 
        OS, 
        adreqs
    FROM 
    (
        SELECT 
            Browser, 
            OS, 
            count(*) AS adreqs
        FROM events_all 
        WHERE (EventDate = today()) AND (Event IN 'requestRow') AND (EventTime > (now() - 3600))
        GROUP BY 
            Browser, 
            OS
    ) 
    ANY LEFT JOIN browsers USING (Browser)
    WHERE BrowserName = 'Safari'
) 
ANY LEFT JOIN oses USING (OS)
ORDER BY adreqs DESC
LIMIT 10

┌─OSName────┬─adreqs─┐
│ MacOS X   │ 331676 │
│ IOS Other │   3054 │
│ IOS 9     │   1990 │
│ IOS 8     │    971 │
│ Linux     │    716 │
│ Windows 7 │    333 │
│ IOS 5     │    282 │
│ IOS 7     │    264 │
│ Windows 8 │    146 │
│ IOS 6     │     91 │
└───────────┴────────┘

10 rows in set. Elapsed: 1.168 sec. Processed 351.26 million rows, 3.86 GB (300.72 million rows/s., 3.31 GB/s.) 

Как же не хватает на Доу таких крутых прикладных статей! Когда я пытался разобраться, как технически правильно делать А/Б тестирование, то из русскоязычного интернета нашел лишь жалкие ошметки информации, где авторы или повторяли друг друга, или очень скупо и бегло все описывали. Большое спасибо за статью :)

Готовьтесь к тому, что вам еще и придется через месяцев несколько объяснять вашим продакт-менеджерам, что average success rate по ab-тестам существенно ниже, чем то, к чему они уже привыкли.

Спасибо за статью!

Подписаться на комментарии