Всем привет! Меня зовут Данила Леньков, я руковожу платформой A/B-экспериментов Trisigma в Авито. За 7 лет мы прошли путь от небольшой внутренней команды из трёх человек до полноценного технологического продукта, который теперь доступен на B2B-рынке.
Сегодня наша платформа обрабатывает 10 тысяч метрик и проводит 500 экспериментов ежедневно. Цифры впечатляющие, но за ними стоят множество технических вызовов и нетривиальных решений.
В этой статье я рассказываю о четырёх ключевых проблемах, с которыми мы столкнулись на пути от десятка экспериментов в год до четырёх тысяч. Это реальный опыт масштабирования, который может быть полезен любой компании, серьезно относящейся к принятию решений на основе данных.

Содержание:
Немного про Авито
Авито — одна из крупнейших classified-платформа в мире. Для тех, кто не знаком с термином: classified — это маркетплейсы объявлений, где пользователи могут продавать и покупать товары напрямую друг у друга. По объему трафика и количеству сделок мы занимаем первое место в этой категории глобально.

Масштаб компании отражается в её структуре: более 250 независимых продуктовых команд, каждая со своими микросервисами и зоной ответственности. Команды работают автономно, выкатывая обновления каждый спринт по принципам матричной организации.
Важная особенность Авито — это культура работы с данными. У нас более 500 аналитиков, которые не просто строят дашборды, а умеют писать сложные SQL-запросы и самостоятельно проверять гипотезы. Это возможно благодаря развитой инфраструктуре логирования: ежедневно мы собираем около 50 миллиардов событий, каждое из которых содержит сотни атрибутов для детального анализа.
Именно в такой среде — с огромными объемами данных и сотнями независимых команд — особенно остро встает вопрос эффективного A/B-тестирования.
Роль A/B-экспериментов
В компании нашего масштаба A/B-тестирование решает три критические задачи:
Контроль рисков. Когда у тебя десятки миллионов пользователей, любое непроверенное изменение может обернуться катастрофой. Мы усвоили этот урок на собственном опыте: в ранние годы выкатывали в продакшн всё, что казалось хорошей идеей, и несколько раз серьезно обжигались. Теперь A/B-платформа — это обязательный фильтр качества перед релизом любой функции.
Измерение реального эффекта от изменений. Каждая команда имеет свои KPI и OKR, привязанные к конкретным метрикам. A/B-тесты показывают точный вклад каждого релиза в достижение этих целей — без догадок и предположений.
Фундаментальная для нашей культуры — это принцип data-driven решений. В Авито не принято полагаться на интуицию или мнение самого важного участника встречи. Решения принимаются на основе данных, а A/B-тестирование — самый надежный способ получить данные для продуктовых решений.
Зачем нужна платформа

Почему недостаточно просто считать A/B-тесты
Многие компании начинают с простого подхода: аналитик пишет Python-скрипт в Jupyter notebook, считает p-value, строит красивые графики. Код может быть безупречным, с правильной статистикой и даже возможностью переиспользования.
Но попробуйте масштабировать это решение на 250 команд и тысячи сотрудников. Что произойдет?
каждый аналитик будет считать метрики по-своему;
результаты одного и того же теста будут различаться;
никто не сможет быстро найти результаты прошлых экспериментов;
пересчет старых тестов с новой методологией превратится в кошмар.
Именно поэтому нужна полноценная платформа — централизованная система, которая обеспечивает единообразие расчетов, хранение истории и доступность результатов для всей компании.

На самом популярном экране платформы аналитик видит полный отчет по эксперименту. Здесь можно фильтровать данные по разным срезам, искать неожиданные инсайты, анализировать метрики в различных сегментах аудитории.
Для каждого эксперимента мы рассчитываем 10–30 тысяч статистических показателей (p-value). Аналитик может найти значимые эффекты даже там, где не ожидал их увидеть. Углубляться в математическую статистику (и, тем более, в «проблему ложных прокрасов») не буду — это тема для отдельного разговора.

Итак, Trisigma обеспечивает две ключевые ценности: производительные вычисления и доверие к данным. Теперь разберемся с тем, как мы шли к масштабированию на примере нескольких проблем.
Проблема 1. Децентрализация

Напомню, в Авито более 250 независимых команд. С одной стороны, это хорошо: меньше накладных расходов на коммуникации и выкатки. Но при этом все команды являются частью общего бизнеса. Авито почти не разделено на бизнес-юниты — все продукты построены вокруг classified, поэтому метрики у бизнеса обладают единой бизнес-логикой.
Задача: все 250+ команд должны жить по единым правилам. На практике это часто приводит к расхождениям в расчётах: два аналитика могут посчитать одно и то же число по-разному — вплоть до противоположных выводов.
Почему так происходит?
Расчет метрики — это большой путь:
найти правильные таблицы;
правильно их соединить («поджойнить»);
отфильтровать нужные строки;
применить агрегатную функцию;
сгруппировать по разрезам;
оценить дисперсию (ошибку измерения).
Во всём этом много бизнес-логики, которая «разъезжается» в разных командах.
Решение: бизнес-логику выделить в семантический слой.

Изобразим организацию дата-хранилища: мы сливаем с помощью загрузок данные в core-слой, в котором расположены витрины. Эти витрины отвечают за опыт покупателей, монетизацию и тд – набирается порядка 100 шт. Все шаги для расчета метрики, что мы описали ранее, должны быть организованы в едином месте, которое называется семантическим слоем.
Семантический слой — это централизованное хранилище бизнес-логики для расчета метрик. По сути, это набор конфигурационных файлов, которые содержат:
SQL-запросы к таблицам с исходными данными;
правила соединения таблиц (джойнов);
формулы и логику расчета метрик;
определения разрезов для анализа.
Но просто создать семантический слой недостаточно — нужна система, которая будет его использовать. Здесь в игру вступает наша A/B-платформа с механизмом кодогенерации.
Процесс выглядит так:
Берем конфигурации экспериментов (какие эксперименты запущены на конкретный день расчета, какие метрики и разрезы в них используются).
Объединяем с правилами расчета метрик из семантического слоя.
Автоматически генерируем SQL-скрипт.
Отправляем этот скрипт в вычислительный движок (в нашем случае — Trino).
Получаем готовые результаты по всем экспериментам.
Такой подход позволяет избежать ручного написания SQL-запросов для каждого эксперимента и гарантирует, что все команды используют единую, проверенную бизнес-логику.

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

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

Чтобы не считать всё подряд и не тратить вычислительные ресурсы впустую, мы объединили метрики и разрезы в домены.
Домен — это, по сути, некоторая область, которой занимается отдельная команда, юнит, кластер или вся компания. Каждая команда имеет свой домен, и в отчётах по A/B-экспериментам анализируются только его метрики и разрезы.
Завершая разговор о проблеме децентрализации, хочется сказать, что на текущий день концепция семантического слоя — не нова и получила широкое распространение около пяти лет назад.
Если хотите углубиться — рекомендую посмотреть еще два доклада на эту тему: например, доклад от Анастасии Рыхлик или один из моих докладов. Кстати, также можно обратиться к опыту других компаний, перечисленных на картинке.

Проблема 2. Ресурсы Compute

Когда-то в Авито было единое хранилище данных на Vertica. Это мощная С++-база и мы долгое время использовали единый кластер на всю компанию. Но со временем ресурсов стало не хватать.

Мы подробнее рассказывали про процесс миграции в докладах ранее: например, Дмитрий Рейман рассказывал про технические особенности и детали в своем докладе. Для тех, кто больше любит читать, а не смотреть – совсем скоро выйдет статья по этому материалу.
Причины:
A/B-платформа очень требовательна к ресурсам: много джойнов, агрегаций, разрезов;
в какой-то момент расчеты А/B занимали 25% всего CPU-хранилища;
Vertica совмещает хранение и compute, что ограничивает масштабирование.
Решение: переход на концепцию Lakehouse с разделением Compute и Storage:
Trino для вычислений;
S3-совместимое хранилище;
Iceberg-таблицы.

Центр управления — это админ-панель, где хранится вся метаинформация об экспериментах. Отсюда сгенерированный SQL-код передается в оркестратор задач (мы используем как Airflow, так и собственные решения).
Ключевое преимущество такой архитектуры Lakehouse — эластичность. Когда нам нужно больше вычислительных ресурсов, мы просто добавляем новые ноды в Trino-кластер в Kubernetes. Никакого решардирования данных, никаких миграций — система масштабируется горизонтально за считанные минуты.
За годы работы платформы накопился огромный массив исторических данных — десятки терабайт в таблицах с более чем 20 миллионами партиций. Мы сохраняем полную историю всех экспериментов, чтобы можно было вернуться к любому тесту и проанализировать его заново с учетом новых знаний.
После расчета каждого эксперимента генерируется около 500 миллионов строк с результатами статистических тестов. Эти данные мы загружаем в ClickHouse — колоночную БД, оптимизированную для быстрой аналитики. Именно ClickHouse обеспечивает мгновенную отрисовку графиков и таблиц в интерфейсе платформы.
Проблема 3. Батчевый расчет vs длинные A/B

Во всех аналитических пайплайнах в крупных компаниях есть точка развития, когда процессы организованы по принципу «t-1» — это такой сленг у дата-инженеров для слова «вчера» («t» означает today).
Рассчитываем всю главную отчетность ежедневным запуском за вчерашний день единым батчом — это довольно эффективно. Но кейс A/B-тестов — не совсем подходящий для «t-1». Средняя длительность эксперимента — неделя, но бывают и 90 дней и более.
Почему длинный A/B тест — это проблема?

Для дальнейшего погружения важно понять ключевой момент. В основе любого A/B-теста лежит расчет статистической достоверности — можем ли мы доверять полученным результатам или это просто случайные колебания?
Чтобы ответить на этот вопрос, нужно измерить дисперсию — насколько сильно различаются показатели между отдельными пользователями (в статистике их называют «экспериментальными единицами»). Чем больше разброс в поведении пользователей, тем больше данных нужно для надежных выводов.
Дисперсия — это фундаментальный показатель, без которого результаты A/B-теста превращаются в гадание на кофейной гуще.
Допустим, для расчета метрик и ее дисперсии нужно выгрузить из хранилища данные по всем действиям всех пользователей за месяц. В масштабах Авито это означает:
десятки миллионов уникальных пользователей;
миллиарды событий (просмотры, клики, покупки);
терабайты данных, которые нужно прочитать с дисков;
нагрузка на сеть при передаче этих данных.
Данные в хранилище лежат в «сыром» виде — каждое действие каждого пользователя записано отдельной строкой. Расчет хотя бы за месяц может привратиться в вычислительную задачу, которая будет длиться часами.
Именно поэтому простой подход «давайте просто выгрузим все данные и посчитаем» не работает для длинных экспериментов в больших компаниях.

Возможное решение: предагрегаты по UserID.
Популярный подход — создавать промежуточные агрегированные таблицы. Идея простая: берем сырые данные и частично их обрабатываем, сохраняя UserID, но схлопывая остальные атрибуты. Например, вместо тысячи событий пользователя за день храним одну строку с суммарными показателями.
Это решение широко используется, но имеет серьезные недостатки:
предагрегаты — это дополнительные ETL-процессы, которые могут падать, требуют мониторинга и поддержки. Каждый новый слой обработки — потенциальная точка отказа всей системы;
хотите добавить новый разрез для анализа? Придется пересчитывать терабайты исторических данных. Это недели работы и огромная нагрузка на инфраструктуру;
даже после агрегации данные остаются слишком детальными — миллионы UserID все равно создают огромные таблицы;
в итоге, несмотря на все оптимизации, для длинных экспериментов все равно не хватает вычислительных ресурсов.
Наше решение: бакетирование.

Я уже много лет рассказываю очень простой лайфхак. В случае A/B-тестов можно сразу делать агрегаты по бакетам. Бакет — это корзинка, в которую мы закидываем пачку UserID.
В Авито мы берем очень ограниченное количество бакетов – 256. И по детерминированной хэш-функции, раскидываем всех юзеров по этим бакетам:
Берём хэш от UserID.
Считаем остаток от деления на 256.
На основе этого значения определяем номер бакета.
Ключевое преимущество — даже с небольшим количеством бакетов мы получаем статистически корректные результаты. Ошибка в оценке дисперсии остается минимальной и не влияет на выводы эксперимента.
Рассмотрим конкретный пример: в эксперименте участвует 1 миллион пользователей. Если мы используем 200 бакетов, то в каждом бакете окажется примерно 5000 пользователей. Это означает, что вместо обработки 1 миллиона отдельных записей по UserID, мы работаем всего с 200 агрегированными значениями — сокращение объема данных в 5000 раз!
Теперь, чтобы проанализировать длинный эксперимент, не нужно выгружать терабайты сырых данных. Достаточно взять компактные агрегаты по бакетам, которые уже содержат всю необходимую информацию расчета дисперсии. Это радикально снижает нагрузку на инфраструктуру и ускоряет получение результатов.
Такая вот интересная техника, всем рекомендую ее использовать, если вам нужно что-то очень быстро посчитать и сделать статистические выводы по большому количеству метрик.
Проблема 4. Ограничение SQL-движка

Переходим к последней и, пожалуй, самой сложной с технической точки зрения проблеме — ограничениям SQL-движков при работе с экстремальными объемами данных.
Для вычислений мы используем Trino — распределенный SQL-движок для аналитики больших данных.
Возможно, вы больше знакомы со Spark, который решает похожие задачи. Выбор между Trino и Spark — это классическая дилемма при построении современного дата-стека. Мы остановились на Trino: наши 500+ аналитиков привыкли работать с SQL, а Trino предоставляет знакомый SQL-интерфейс с понятным синтаксисом. Это позволило нам быстро масштабировать платформу без массового переобучения команд. Плюс, Trino хорошо работает «из коробки».
Но здесь мы столкнулись с фундаментальной проблемой. Даже самые изощренные SQL-оптимизации имеют свой предел. И в нашем случае, при работе с тысячами метрик и сотнями экспериментов одновременно, мы этот предел достигли.
Давайте разберемся, почему традиционный SQL-подход перестает работать на наших масштабах.

Вспомним семантический слой. С одной стороны, мы можем свободно генерировать любой SQL-код.


Но давайте посмотрим, как выглядит типичный SQL-запрос для расчета A/B-теста и почему он становится неэффективным.
Начинаем с таблицы фактов — например, clickstream с миллиардами событий пользователей. К ней нужно присоединить:
информацию об участии каждого пользователя в экспериментах (один пользователь может участвовать в десятках тестов одновременно);
определения метрик и их формулы расчета;
разрезы для анализа (по городам, устройствам, категориям).
После всех соединений нужно отфильтровать только те комбинации метрик и разрезов, которые действительно нужны конкретной команде в конкретном эксперименте. На первый взгляд, это стандартная задача для SQL — можно оптимизировать предикаты в JOIN и WHERE, настроить GROUP BY. И до определенного момента это работает.
Но давайте посчитаем, что происходит на наших масштабах:
берем 2 миллиарда событий из основной таблицы;
после JOIN с данными об экспериментах объем увеличивается в 20 раз (каждое событие может относиться к множеству экспериментов);
добавляем метрики и разрезы — еще умножение объема данных;
в итоге получаем такой объем промежуточных данных, который физически не помещается в память ни одного кластера.
Казалось бы, можно применить фильтрацию на ранних этапах. Но здесь кроется ловушка: мы не знаем заранее, какие именно строки понадобятся для расчета конкретной метрики в конкретном разрезе. Фильтр WHERE здесь не поможет — нужно сначала соединить данные, чтобы понять, что фильтровать.

Главная проблема — колоссальная избыточность вычислений. Реально используемых комбинаций «метрика × разрез × эксперимент» всего 0,15% от всех возможных. Это означает, что стандартный SQL-подход заставляет нас делать в 600 раз больше работы, чем действительно необходимо.
Завалить железом — не вариант. Нужен принципиально другой подход.

Наше решение: мы отказались от классического SQL в пользу кастомной UDF.
Вместо сложного SQL-запроса с множественными JOIN мы написали кастомную функцию на Java, которая подключается к Trino как плагин. Теперь расчет выглядит как простой вызов функции, в которую передаются источник данных и все необходимые конфигурации — метрики, эксперименты, разрезы.

Понимаю, что такое описание выглядит абстрактно. Давайте объясню, какие конкретно оптимизации есть под капотом.
данные предварительно сортируются по UserID перед передачей в функцию;
обработка идет микробатчами — берем данные одного пользователя, рассчитываем все нужные метрики, освобождаем память и переходим к следующему;
используем свойство аддитивности: метрики можно накапливать инкрементально, не храня промежуточные результаты;
джойним эксперименты, в которых участвует пользователь, массивом. Сам массив разворачиваем уже внутри функции, вместо того, чтобы размножить весь data source;
самое важное — фильтрация происходит на самом раннем этапе. Мы сразу отсекаем те комбинации метрик и разрезов, которые не нужны, не занимая лишнюю память.
Благодаря этим оптимизациям алгоритм не тратит много памяти и позволяет эффективно управлять нагрузкой на вычислительный кластер.
Выводы

Это всё, что я хотел рассказать. Что важно вынести из статьи:
без семантического слоя метрики превращаются в хаос — единые правила спасают от бесконечных споров;
Lakehouse — не тренд, а реальный способ масштабировать данные, когда старые хранилища захлёбываются;
SQL хорош до определённого масштаба, дальше нужны кастомные алгоритмы;
любая платформа уровня Trisigma стоит несколько лет разработки и десятки инженеров — но окупается за счёт доверия к данным и скорости принятия решений.
Если вам было бы интересно попробовать нашу платформу, связаться с нами можно по ссылке.
А как у вас решают проблему масштабирования A/B-тестов? Используете ли семантический слой или свои решения для ускорения экспериментов? Делитесь опытом в комментариях!
А если хотите вместе с нами помогать людям и бизнесу через технологии — присоединяйтесь к командам. Свежие вакансии есть на нашем карьерном сайте.
Wicron
Если всё так круто, то почему Avito стал таким тормозным ? Пользоваться с настольного браузера - полная боль, полно ошибок, не прогрузок. А качество поиска - это же смешно. Нет ИИ-агентов для продажи своего и закупки составных единиц из чужого.