У нас в hh.ru есть три кластера PostgreSQL, два кластера Cassandra, кластер Hadoop и пять кластеров ClickHouse. Не то чтобы всё это было жизненно необходимо в разработке, но если уж начал собирать коллекцию, к делу надо подходить серьезно.
В этой статье расскажу, как нам удалось запилить конкретную бизнес-фичу с применением ClickHouse и на какие подводные камни при этом наткнулись. Щас будет мясо!
С чем едят ClickHouse
ClickHouse — это колоночная база данных, которая позволяет быстро выполнять аналитические запросы. Она использует собственные диалекты SQL, который близок к стандартному.
Про ClickHouse важно понимать, что это не OLTP-решение, поэтому здесь нет транзакций, к которым мы привыкли в PostgreSQL и других классических базах данных. Помимо этого, здесь нет точечных операций update и delete, которые позволяют нам удалять или изменять конкретные записи по первичному ключу. Все эти ограничения делают ClickHouse не очень подходящим для построения на нем бизнес-логики. Зато он отлично подходит для быстрого построения аналитических отчетов.
Как я уже писал в начале — у нас пять кластеров ClickHouse. Один из них выделен непосредственно под наши рекламные продукты. Изначально мы хранили все данные в PostgreSQL в агрегированном по дням виде, но с развитием наших рекламных продуктов, решили, что хотим хранить сами сырые данные — это даст нам кучу новых возможностей. Мы сможем строить на их основе ML-модели, разрабатывать алгоритмы показа баннеров и получать детальные аналитические отчеты.
Сырые данные представляют из себя лог действий пользователя с баннерами или вакансиями. Например:
Пользователь видит баннер — отправляем событие в лог.
Пользователь кликнул на баннер — отправляем событие в лог.
Пользователь открыл вакансию с определенными utm-метками — событие в лог.
Пользователь откликнулся после этого на вакансию — еще одно событие улетает в лог.
И так далее.
Каждое событие содержит в себе его тип, timestamp (временную метку) и дополнительные данные, вроде ID баннера, ID вакансий, User-Agent, UTM-метки etc. Для хранения таких данных мы и выбрали ClickHouse, потому что он отлично для этого подходит. В итоге у нас получилось несколько таблиц: одна с баннерами, другая — с действиями по вакансиям. Каждая такая таблица может содержать в себе десятки и сотни миллиардов событий. Все это выливается в несколько терабайт данных на каждую таблицу.
Мощь ClickHouse
Сейчас я вам покажу, как мы с помощью ClickHouse реализовали конкретную бизнес-фичу. Мы хотели, чтобы наши пользователи могли наглядно наблюдать, как подключенные рекламные продукты влияют на успех их вакансии. Поэтому решили показывать график просмотров и откликов вакансии во времени, а под графиком дополнительно отображать какая рекламная услуга была активна в какой промежуток времени.
Эти интервалы внизу мы называем колбасками, потому что считаем, что они похожи на колбаски. Добро пожаловать в нашу мясную лавку.
Для большинства услуг есть вполне четкое время начала и окончания действия. Но еще мы хотели добавить на наш график данные по рассылкам. С рассылками дело обстоит немного сложнее: когда пользователь получает смс или e-mail, он не сразу переходит по ссылке в письме, а может это сделать через несколько часов, на следующий день или даже позже.
Мы хотели отразить этот факт на нашем графике, поэтому условились считать, что временем действия услуги будет интервал, когда на вакансию с определенными utm-метками приходит больше некоторого порогового числа пользователей в день. Например, если в день пришло больше пяти пользователей с этими utm-метками, мы считаем, что услуга была активна. Лог просмотров вакансий у нас хранится в отдельной таблице в ClickHouse.
В итоге для построения колбасок по рассылкам у нас получился примерно следующий незамысловатый SQL-запрос:
Из таблицы действий пользователей достаем события просмотра вакансии с определенными utm-метками за выбранный интервал времени, считаем количество таких просмотров с разбиением по дням и определенным рекламным кампаниям.
Такие запросы можно выполнять прямо из кода приложения. Приложение мы пишем в основном на Java, а для работы с ClickHouse и Java существует опенсорсный JDBC-драйвер. Поэтому можно использовать все стандартные инструменты работы с базами данных. У нас запрос несложный, поэтому мы решили использовать для работы ClickHouse простой “советский” JDBC-темплейт из пакета Spring JDBC.
Внезапный кластерфак
Казалось бы, все легко и просто: пишешь запрос, выполняешь как обычно через JDBC и все — задача решена. О чем тут вообще рассказывать? В целом, так оно и есть, но когда мы начали выполнять запросы к ClickHouse на продакшене, то заметили, что они иногда тормозят и могут выполняться аж до нескольких секунд. Но почему? Придется подробнее остановиться на том, как устроен кластер ClickHouse.
Кластер ClickHouse состоит из шардов и реплик:
Шардирование — это разделение данных на отдельные части, каждая из которых хранится на отдельном сервере. Это позволяет распараллелить чтение данных и ускорить его.
Реплицирование — это дублирование данных, оно необходимо для обеспечения отказоустойчивости.
В совокупности шарды и реплики образуют кластер ClickHouse.
Мы стали разбираться с проблемой и детальнее изучать время отклика. Заметили, что большинство запросов выполнялись достаточно быстро — до 150ms. Однако примерно треть запросов выполнялась несколько секунд и более. Те запросы, которые выполнялись медленно, обрабатывались на серверах в одном дата-центре. В этом дата-центре установлены устаревшие и медленные HDD, а в других были быстрые SSD. Вот здесь и зарыта собака.
Мы пообщались с ребятами из эксплуатации и узнали, что эти железки в любом случае скоро собираются менять. Но нам не хотелось ждать обновления, нужно было как можно быстрее довести эту бизнес-фичу до пользователей. Поэтому мы решили временно отключить одну реплику ClickHouse, находящуюся в медленном дата-центре. Для этого нам было достаточно просто убрать из конфигов ClickHouse упоминания шардов этой реплики.
Приключение на двадцать минут — зашли и вышли, да? Ха! Мы снова наткнулись на подводные камни. После такого изменения конфигурации кластера некоторые сервера ClickHouse отказывались стартовать. В логах при этом было невнятное описание ошибки. Мы долго не могли понять, в чем проблема, потому что воспроизвести ее в тестовом окружении тоже не получалось. В итоге мы нашли issue на Гитхабе, из которого стало понятно, что это баг самого ClickHouse.
Суть бага заключалась в том, что после подобного изменения конфигурации кластера ClickHouse не мог стартануть, если у него в рабочих директориях были какие-то лишние поддиректории. Это наш случай. Оказалось, что ClickHouse сам создал временные поддиректории, а потом не мог с ними стартануть.
Просто удаляем проблемы
Сначала мы не были уверены, можно ли просто удалить эти директории, или мы сломаем весь кластер ClickHouse. Поизучав репозиторий, мы поняли, что в новых версиях ClickHouse этот баг уже пофикшен. В идеале, можно было бы сразу обновить версию ClickHouse, но нам не хотелось тратить на это время в рамках бизнес-задачи.
По коду фикса мы поняли, что это действительно временные директории и их можно просто безболезненно удалить.
Мы решили удалить директории вручную, а обновление до новой версии ClickHouse сделать позже в рамках техналога. После удаления этих директорий мы снова применили изменения конфигурации кластера ClickHouse с удалением одной из реплик, и на этот раз всё отлично заработало.
Вот так благодаря ClickHouse мы предоставили пользователям возможность отслеживать наглядную аналитику по вакансии в режиме онлайн, видеть преимущества рекламных продуктов и оценивать их эффективность.