Спільна функція в кореневому макеті (root layout) сайту перетворила лічильник експозицій A/B-тесту на лічильник переглядів сторінок, що роздуло розмір вибірки та зробило показники конверсії безглуздими. Тест із розподілом 50/50 для головної сторінки показав 178 переходів для одного варіанта та 57 для іншого — що зовсім не відповідало очікуваному рівномірному розподілу.

Як помилка пройшла повз рандомайзер

Спочатку розробник перевірив рандомайзер, який призначає відвідувачів до варіанта. Він вивчив middleware, перевірив логіку роботи з cookie та запустив скрипт, який викликав функцію призначення 10 000 разів; результат показав ідеальний розподіл 50/50. Сам рандомайзер працював правильно; проблема полягала в тому, як саме реєструвалася експозиція.

Одна функція виконувала два завдання:

  1. Встановлення варіанта — виконується під час кожного завантаження сторінки, щоб забезпечити сталість досвіду відвідувача.
  2. Реєстрація події експозиції — має спрацьовувати лише один раз для кожного відвідувача, у момент першої появи варіанта.

Функція знаходилася в кореневому макеті, компоненті, що рендериться під час кожної навігації. Оскільки код реєстрації експозиції виконувався щоразу під час рендерингу макета, кожен перегляд сторінки вважався новою експозицією. Два варіанти головної сторінки використовували дещо різні дерева макетів, тому рівень переглядів сторінок у них розійшовся, створюючи ілюзію несправного рандомайзера.

Чому помилка в підрахунках була важливою

Показники конверсії — кліки, реєстрації, покупки — реєструвалися правильно. Але знаменник (кількість експозицій) був хибним. Розраховані показники конверсії виявилися набагато нижчими за реальні, а будь-які рішення, прийняті на основі цих даних, були ненадійними.

Тест тривав два тижні, перш ніж виявилася розбіжність, що змусило команду відкинути весь набір даних.

Виправлення

Рішення було простим: розділити обов'язки на окремі функції. Тепер код реєстрації експозиції перевіряє, чи був відвідувач уже врахований, і спрацьовує лише один раз для кожного користувача. Код встановлення варіанта залишається на місці й продовжує працювати під час кожної навігації.

Три висновки для тих, хто проводить експерименти

  • Відокремлюйте встановлення стану від разових подій. Функція, яка одночасно призначає варіант і реєструє експозицію, спричинить конфлікт, оскільки перша повторюється, а друга — ні.
  • Уникайте логіки «одного разу» в кореневому макеті. Будь-що, розміщене в компоненті, що рендериться під час кожного завантаження сторінки, виконуватиметься повторно, перетворюючи «один раз на відвідувача» на «один раз на перегляд сторінки».
  • Якщо спостережуваний розподіл суперечить роботі рандомайзера, спочатку перевірте лічильник. Розробники часто тестують справедливість рандомайзера, але рідко перевіряють точність механізму підрахунку.

На що звернути увагу надалі

Будь-який експеримент, що покладається на єдиний лічильник експозицій, слід перевірити на предмет того, де цей лічильник знаходиться в дереві компонентів. Якщо лічильник розміщений у глобальному макеті, додайте перевірку, яка прив'язує подію до постійного ідентифікатора — наприклад, cookie або прапорця в local-storage. Командам також варто впровадити перевірку коректності (sanity check) у свої дашборди: якщо спостережуваний розподіл варіантів виходить за межі невеликої статистичної похибки, позначте тест для аудиту лічильника, перш ніж припускати, що рандомайзер зламаний.

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