Задача: своя платформа A/B-тестов
Александр сразу оговаривает формат: он рассказывает задачу, Никита задаёт уточняющие вопросы и фиксирует ответы, потом проектирует решение, а оставшиеся полчаса уходят на разбор — что получилось, что не очень и что стоит подтянуть. Задача — система проведения A/B-тестов для веба и мобильных приложений: часть зала, возможно, знакома с такими системами и пользовалась Google Firebase с флагами и remote config, и вот такое зарубежное решение решили заменить своим. Функциональные требования: аналитик заводит эксперимент с параметрами и списком метрик, регулярно смотрит по нему статистику; клиентские приложения через SDK получают не общий список экспериментов, а только те, в которые попал конкретный пользователь, и отгружают аналитические события. Тесты не «орёл или решка», а с умным таргетингом по аудиторным сегментам: только мужчины, только клиенты с определёнными продуктами, только те, кто заходил на определённые страницы. Александр признаётся, что задача для него не случайная: A/B-платформа группы компаний развивается в его подразделении, и он за ней пристально следит.
Нефункциональные требования короткие, но жёсткие: ответ приложению за 200 миллисекунд, чтобы тесты не блокировали загрузку страницы или экрана; пользователь должен «прилипать» к эксперименту, чтобы кнопка не мигала ему зелёным, красным и жёлтым; результатов должно хватать на статистически значимый вывод. Никита выспрашивает цифры, и Александр их называет: тысячи аудиторных сегментов, причём один идентификатор входит, по прикидке, примерно в сотню из них; порядка 30 млн активных пользователей в месяц, около 10 млн DAU в мобильных приложениях и условно 3 млн в вебе; целевой потолок — тысяча одновременно активных экспериментов; аналитиков в большой организации пускай будет сотня. Эксперимент обычно живёт неделю-две, иногда месяц, иногда заканчивается за пару дней. Лаг расчёта статистики в первой версии допустим в 10–15 минут и даже час. Автоподведение итогов Александр вешает отдельным дополнительным вопросом, а очередь конфликтующих экспериментов и ролевую модель просто выносят за рамки задачи.
От сценариев к decision-сервису
Никита начинает не с квадратиков, а со сценариев. Аналитик заводит эксперимент, регулярно смотрит табличку со статистикой, завершает его, а если гипотеза победила — ставит задачу раскатить победивший вариант, приложив ссылку на эксперимент, чтобы не выглядело, будто он самолично решил перекрасить все кнопки. Пользователь в сценариях пассивен: он открывает приложение, а вся магия происходит без его ведома. Приложение на старте получает список экспериментов по идентификатору и дальше живёт с ним; само оно при этом «тупое» — просто применяет конфигурацию: меняет порядок элементов на экране, порядок шагов в форме или текст описания продукта. Отдельную аналитику не заводят: к уже существующей телеметрии добавляют метку, в каком эксперименте произошло событие. Веб и мобилу решают не разделять, но, заметив, что мобильное приложение может уйти в офлайн и копить события, оставляют два разных эндпоинта отгрузки.
Дальше появляются кубики: API-шлюз с публичной и приватной частью — ручкам аналитика торчать наружу незачем, и Александр засчитывает это проговорённым вслух; эксперимент-сервис с базой экспериментов; отдельные коллекторы статистики для веба и мобилы, потому что масштабировать их надо по-разному; очередь; аналитическая база и сервис, который решает, относится ли пользователь к эксперименту. Название рождается в диалоге: Никита признаётся, что не может его придумать, и в ответ получает готовый вариант — decision-сервис, «так намного лучше». Не хватает ещё одного кубика: хранилища сегментов, куда decision-сервис ходит по User ID за коллекцией сегментов, — сам список экспериментов маленький и меняется редко, его можно держать в памяти сервиса. Технологии Никита подбирает под нагрузку: PostgreSQL для экспериментов, Kafka с партиционированием по идентификатору пользователя, чтобы не восстанавливать порядок по таймстемпам, ClickHouse для аналитики (честно говорит, что из аналитических баз знаком по-настоящему только с ним) и Cassandra для сегментов — key-value с большой кардинальностью по юзеру.
Прилипание, разбор и вопросы зала
Последний вопрос, который Александр успевает задать, — прилипание пользователя к эксперименту. Никита предлагает хранить пару «пользователь — эксперимент» в быстром key-value: 30 млн пользователей по паре байт дают всего мегабайты. Александр показывает другой ход: назначение можно записать прямо в сегменты — завести сегмент «попал в тестовый вариант такого-то эксперимента», и отдельная сущность не понадобится. Это возвращает разговор к собственной реплике Никиты про «вычислимое»: раз есть эксперименты и сегменты по юзеру, назначение можно досчитать заранее. А если бы список экспериментов запрашивался не один раз на старте, а на каждой загрузке страницы, как бывает в вебе, понадобились бы онлайн-сегменты: обработчик рядом с потоком событий считал бы поведение в реальном времени и складывал результат в то же хранилище, чтобы учитывать его при решении и запускать цепочки экспериментов.
В разборе Александр хвалит сам ход: не бросаться в гущу, понять, что система делает и какие API для каких сценариев отдаёт, разложить на концептуальные кубики и только потом говорить про технологии, собранные, кстати, из стандартных платформенных кирпичей компании. Не успели дорисовать ручку, через которую аналитик смотрит свою табличку, посчитать нагрузку и дойти до автоматического подведения итогов с уведомлением аналитика, — «так часто бывает и на реальном собеседовании». Зал добивает деталями: для сегментов используется коммерческое key-value Aerospike, с которым в текущих условиях сложно, и команда недавно писала RFC, сравнивая Cassandra и FoundationDB и прикидывая собственный шардированный stateful-сервис. Отдельный «наброс» — кандидат назвал технологии, но не их конкурентов; Александр соглашается, что обычно спрашивает «почему именно это», и вспоминает, что в прошлом поколении платформы слой ускорения лямбда-архитектуры жил на Cassandra, работал не очень и был переписан на ClickHouse.
Что стоит унести с собой
- 01Вся сложность платформы концентрируется в decision-сервисе: за 200 миллисекунд он должен взять список экспериментов с правилами по сегментам и решить, в какие попадает конкретный идентификатор.
- 02Прилипание пользователя не обязательно хранить отдельной сущностью: назначение выражается сегментом «попал в тестовый вариант», а поведение в реальном времени даёт обработчик онлайн-сегментов.
- 03На вопрос про BFF Александр отвечает историей про server-driven UI: сервис с тем же интерфейсом берёт дефолтную выдачу, применяет к ней набор тестов и отдаёт готовый результат, делая эксперимент невидимым для клиента.
- 04System Design секцию проходят не только архитекторы: инженеры от определённого уровня, тимлиды и технические руководители; для роли архитектора есть отдельная секция про инженерные практики и архитектурные процессы.