Avito Test Task

Запуск

Через docker compose:

  • docker compose up -d --build

Сервис будет доступен по адресу localhost:8080

Swagger расположен по адресу http://localhost:8080/swagger/index.html

Полезные команды

  • make swagger перегенерировать сваггер файлы
  • make migrate запустить миграцию БД (требует установленного migrate)

Функциональные характеристики

  • Реализован минимум.
  • Реализовано получение истории добавления/удаления сегментов пользователю:
    • История выдается по пользователю (user_id), выдается сразу CSV файл.
  • Реализовано время жизни сегмента, при добавлении может быть указан параметр TTL (время жизни в секундах).
  • Реализовано автоматическое добавление сегмента пользователю:
    • Сегмент добавляется пользователям равномерно в зависимости от указанного процента, решение о добавлении происходит при запросе сегментов пользователя у которого данного сегмента никогда не было.
    • Для определения того, будет ли пользователь состоять в сегменте берутся 4 байта md5 хэша строки slugName+userId, если это число меньше порога - пользователь добавляется в сегмент.
    • При удалении сегмента с пользователя он больше не может автоматически добавиться снова.
    • Для тестирования правильности распределения можно создать сегмент с нужным auto_add_percent (например 50), а затем сделать 1000 запросов юзеров с id от 0 до 1000. Затем подключиться к БД и получить группировку по slug_name: SELECT count(*) FROM slugs_users GROUP BY slug_name, если будет примерно 500 (или 1000 * ваш процент / 100), значит распределение верное; у меня получилось ~503 пользователя.

Недоработки

В ходе разработки возникли были замечены некритичные недоработки:

Недоработка Возможное решение
При удалении сегмента с TTL в историю будет добавлено 2 строки действия удаления 1. Удалять из истории запись об удалении по истечению срока жизни при досрочном удалении
2. Добавить причину удаления в историю
При удалении сегмента и последующим добавлением нового сегмента с таким же именем и auto_add_percent пользовали имевшие старый сегмент не смогут иметь новый 1. Использовать уникальные имена сегментов
2. Использовать id сегмента вместо имени
Возможны повторные удаления уже удаленного для пользователя сегмента Добавить проверку

Вопросы возникшие при разработке и ответы на них

    1. Нужна ли регистрация/авторизация пользователей?
    2. Нет, так как сервер будет использоваться только внутри сети компании.
    1. Должен ли сервис заниматься пользователями? Например уточнять что данный пользователь не существует?
    2. Нет, т.к. это не является зоной ответственности данного сервиса.
    1. Нужно ли удалять все отношения пользователь-сегмент при удалении сегмента?
    2. Да, нужно, так как информация о сегменте больше не требуется.

Оригинальный README задания

Тестовое задание для стажёра Backend

Сервис динамического сегментирования пользователей

Проблема:

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

Задача:

Требуется реализовать сервис, хранящий пользователя и сегменты, в которых он состоит (создание, изменение, удаление сегментов, а также добавление и удаление пользователей в сегмент)

Сценарии использования:

Хотим провести несколько экспериментов и протестировать новый функционал Авито:

  • Голосовые сообщения в чатах
  • Новые услуги продвижения
  • Скидка 30% на услуги продвижения
  • Скидка 50% на услуги продвижения

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

Допустим аналитики создали сегменты:

  • AVITO_VOICE_MESSAGES
  • AVITO_PERFORMANCE_VAS
  • AVITO_DISCOUNT_30
  • AVITO_DISCOUNT_50

и добавили созданные сегменты нескольким пользователям:

Пользователь Сегменты которым он принадлежит
1000 [AVITO_VOICE_MESSAGES, AVITO_PERFORMANCE_VAS, AVITO_DISCOUNT_30, …]
1002 [AVITO_VOICE_MESSAGES, AVITO_DISCOUNT_50, …]
1004 нет сегментов

Формат хранения данных в базе данных не ограничен - можно выбрать любой удобный

Получили следующие данные:

  • Пользователь 1000 состоит в 3 сегментах: AVITO_VOICE_MESSAGES, AVITO_PERFORMANCE_VAS, AVITO_DISCOUNT_30

  • Пользователь 1002 состоит в 2 сегментах: AVITO_VOICE_MESSAGES, AVITO_DISCOUNT_50

  • Пользователь 1004 не состоит ни в одном из сегментов

Теперь мы хотим через API сервиса по user_id получать список сегментов в которых он состоит.

Требования и детали по заданию

Технические требования:

  1. Сервис должен предоставлять HTTP API с форматом JSON как при отправке запроса, так и при получении результата.
  2. Язык разработки: Golang.
  3. Фреймворки и библиотеки можно использовать любые.
  4. Реляционная СУБД: MySQL или PostgreSQL.
  5. Использование docker и docker-compose для поднятия и развертывания dev-среды.
  6. Весь код должен быть выложен на Github/Gitlab с Readme файлом с инструкцией по запуску и примерами запросов/ответов (можно просто описать в Readme методы, можно через Postman, можно в Readme curl запросы скопировать, и так далее).
  7. Если есть потребность в асинхронных сценариях, то использование любых систем очередей - допускается.
  8. При возникновении вопросов по ТЗ оставляем принятие решения за кандидатом (в таком случае в Readme файле к проекту должен быть указан список вопросов, с которыми кандидат столкнулся и каким образом он их решил).
  9. Разработка интерфейса в браузере НЕ ТРЕБУЕТСЯ. Взаимодействие с API предполагается посредством запросов из кода другого сервиса. Для тестирования можно использовать любой удобный инструмент. Например: в терминале через curl или Postman.

Будет плюсом:

  1. Покрытие кода тестами.
  2. Swagger файл для вашего API.

Основное задание (минимум):

  1. Метод создания сегмента. Принимает slug (название) сегмента.
  2. Метод удаления сегмента. Принимает slug (название) сегмента.
  3. Метод добавления пользователя в сегмент. Принимает список slug (названий) сегментов которые нужно добавить пользователю, список slug (названий) сегментов которые нужно удалить у пользователя, id пользователя.
  4. Метод получения активных сегментов пользователя. Принимает на вход id пользователя.

Детали по заданию:

  1. По умолчанию сервис не содержит в себе никаких данных о сегментах и пользователях (пустая табличка в БД). Данные появляются при создании сегментов и добавлении их пользователям.
  2. Валидацию данных и обработку ошибок оставляем на ваше усмотрение.
  3. Список полей к методам не фиксированный. Перечислен лишь необходимый минимум. В рамках выполнения доп. заданий возможны дополнительные поля.
  4. Механизм миграции не нужен. Достаточно предоставить конечный SQL файл с созданием всех необходимых таблиц в БД.
  5. Сегменты пользователя очень важны - на этих данных в будущем строится аналитика о том насколько продукт востребован. Поэтому нужно следить за тем чтобы сегменты не терялись (не перетирались) и не добавлялись лишним пользователям.
  6. Мы можем добавлять сегменты пользователю динамически (он уже состоит в нескольких, мы можем добавить еще несколько, не перетирая существующие).
  7. В методе получения сегментов пользователя мы должны получить АКТУАЛЬНУЮ информацию о сегментах пользователя с задержкой не более 1 минуты после добавления сегмента.

Опциональные задания

Далее перечислены дополнительные задания.

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

Можно выбрать несколько из представленных

Доп. задание 1:

Иногда пользователи приходят в поддержку и спрашивают почему у них пропал/появился какой-то новый функционал. Нужно иметь возможность посмотреть когда точно пользователь попал в конкретный сегмент.

Задача: реализовать сохранение истории попадания/выбывания пользователя из сегмента с возможностью получения отчета по пользователю за определенный период. На вход: год-месяц. На выходе ссылка на CSV файл.

Пример отчета:

идентификатор пользователя 1;сегмент1;операция (добавление/удаление);дата и время

идентификатор пользователя 1;сегмент2;операция (добавление/удаление);дата и время

идентификатор пользователя 2;сегмент3;операция (добавление/удаление);дата и время

Доп. задание 2:

Бывают ситуации когда нам нужно добавить пользователя в эксперимент на ограниченный срок. Например выдать скидку всего на 2 дня.

Задача: реализовать возможность задавать TTL (время автоматического удаления пользователя из сегмента)

Пример: Хотим чтобы пользователь попал в сегмент на 2 дня - для этого в метод добавления сегментов пользователю передаём время удаления пользователя из сегмента отдельным полем

Доп. задание 3:

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

Задача: в методе создания сегмента, добавить опцию указания процента пользователей, которые будут попадать в сегмент автоматически. В методе получения сегментов пользователя, добавленный сегмент должен отдаваться у заданного процента пользователей.

Пример: создали сегмент AVITO_VOICE_MESSAGES и указали что 10% пользователей будут попадать в него автоматически. Пользователь 1000 попал в этот сегмент автоматически. При запросе сегментов пользователя 1000, сегмент AVITO_VOICE_MESSAGES должен отдаваться всегда.