Highloadcup 2018

Введение

В данной статье я хочу рассказать о своем опыте участие в конкурсе Highloadcup 2018 от Mail.ru. О конкурсе я узнал из поста на хабре, а изначальной мотивацией принять участие было желание более опытных коллег посоревноваться.

В рамках конкурса нужно было спроектировать и упаковать в Docker приложение, которое реализовывает несложное, но объемное по бизнес-логике REST API. Требуется реализовать 4 GET-запроса и 3 POST-запроса. Затем решение нужно отправить в docker-registry, после оно будет обстрелено с помощью yandex-tank разными профилями обстрела. В ЛК участника будет виден результат обстрела.

Примерный объем данных: 1 300 000 пользователей. Ограничение сервера, на котором обстреливается решение: 2 Гб ОЗУ, 10 ГБ HDD.

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

Бета-раунд

Бета-раунд нужен для того, чтобы команда mail.ru могла с помощью сообщества найти недочеты в задании, а также в системе обстрела. Я решил попробовать следующий стек: golang + reindexer

Reindexer - это inmemory БД, написанная на C++, также имеет расширение для golang. Поддерживает несколько режимов работы. Builtin - режим, когда БД хранится в самом приложении. Также есть возможность работы в серверном режиме. Статья на хабре.

Данная база показала себя как очень быстрая и удобная, но со временем я уперся в ряд ограничений:

  • builtin-движок потреблял намного больше памяти, чем серверный вариант;
  • документация неполная и местами противоречивая;
  • группировка с помощью фасет не работает по составным индексам (или работает неочевидным образом);
  • иногда демон начинал потреблять CPU и отказывался принимать запросы;
  • “собственный” механизм получения данных (не sql)
  • отсутствие возможности ограничивать использования оперативной памяти (из-за этого пришлось отказаться, так как в следующих раундах данные не влезли).

Отборочный раунд

В отборочный раунде было увеличено количество данных и RPS. Пришлось искать другие варианты для хранения данных. Решил попробовать golang + sphinx

Положительные стороны:

  • наличие функциональных realtime-индексов;
  • возможность указывать количество памяти, которое будет выделено;
  • sphinxql - расширение sql-языка запросов;
  • поддержка json;
  • встроенная система фасет;
  • наличие множественных атрибутов.

Отрицательные стороны:

  • невозможность группировки по множественным атрибутам, если в группировке участвует обычный атрибут;
  • Sphinx выдержал максимум 40 rps (скорее всего я не смог его правильно приготовить, но времени изучать глубже больше не было).

Далее выбор пал на golang + tarantool.

Положительные стороны:

  • развернутая документация;
  • поддержка sql;
  • поддержка lua-скриптов для реализации логики на стороне БД;
  • возможность указать количество памяти для vanyl-движка;
  • компактность хранения данных в memrx-движке;
  • очень быстрый для фильтрации.

Отрицательные стороны:

  • поддержка sql только в alpha-версии;
  • не всегда очевидная работа планировщика запросов;
  • ответ в yaml не всегда удобно парсить;
  • не очень удобный клиент на golang
  • медленно работают запросы с группировкой.

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

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

Особенностью механизм “собственной” фильтрации было то, что мне пришлось придумать, как пересекать упорядоченные множества “слева”. Позднее я увидел в чате, что подобные алгоритмы относятся к sorted stream intersection.

Финал

Финальный раунд проходил автоматически, но для обстрела применялись “особые” волны с засекреченным rps.

Я занял 53 место. Результат мог быть лучше. если бы я использовал ОЗУ более эффективно и уместил бы все данные в память. Позднее пришла идея хранить данные в битсетах, попробую в следующий раз.

Заключение

Мне очень понравился конкурс. Это очень мощный стимул изучать что-то новое в совершенно разных областях. По итогам я узнал/изучил:

  • представление о golang;
  • опыт работы с goroutines;
  • особенности работы GC;
  • особенность алгоритма Нейгла и флаг tcp no delay при работе с сокетами;
  • способы упаковки и хранения данных: delta-compression, bitset;
  • особенности парсеров json;
  • какие есть утечки памяти в php;
  • особенности системных вызовов epoll и низкоуровневой работы;
  • навык написания кода: от пилотного прототипа до дальнейшего развития в условиях проверки разных гипотез;
  • Красно-черные деревья, LSM-деревья, битсеты.

Исходники