×

Наш опыт внедрения ClickHouse — аналитической CУБД

Всем привет. Меня зовут Роман Базар. Я работаю в компании MGID на позиции Tech Lead, в основном занимаюсь web-разработкой. Не так давно моя команда занималась внедрением ClickHouse в наш стек технологий. В статье хочу поделиться этим опытом.

Начали мы знакомство с ClickHouse осенью 2016. Вскоре выпустили в продакшн первую фичу с его использованием. С тех пор прошло 2 года, и сейчас 4 ClickHouse ноды принимают на вставку порядка 10-ти миллиардов записей в день, хранят около 4 TB данных, обслуживают выборки с нескольких web-приложений, а также дают возможность отделу аналитики работать с данными еще из одного источника.

Disclaimer. Статья не претендует на тег best practices. Это история о том, как мы улучшили свои инфраструктурные возможности по обработке статистических данных и поработали с ClickHouse. Надеюсь, статья будет полезной.

Для тех, кого напугал объем статьи:

  • Old School Statistics Storage — описание того, как мы хранили статистику раньше и почему больше не можем так делать;
  • Road to ClickHouse — тесты альтернативных решений до ClickHouse;
  • ClickHouse Hello World — основы ClickHouse;
  • ClickHouse First Blood — первая фича, первый взлет и первые ошибки;
  • ClickHouse & Quantiles — самое интересное. Если вы уже знакомы с ClickHouse и не хотите читать все, вам сюда.

Несколько слов о компании. MGID уже более 10-ти лет занимается нативной рекламой. Сотрудничаем с сайтами-площадками, на которых размещаем виджеты показа рекламы. Виджеты отвечают за отображение рекламных объявлений наших клиентов (да-да, та самая реклама с громкими заголовками, на которую многие хотят в тайне кликнуть). Компания продуктовая, пишем для себя. В техническом отделе работает порядка 80-ти человек. Стек технологий: PHP, JavaScript, Go, C++, Lua, Kafka, MySQL, MongoDB, Aerospike и, понятное дело, ClickHouse.

Old School Statistics Storage

Начнем с того, зачем нам вообще понадобился ClickHouse. Как я уже упомянул выше, MGID занимается нативной рекламой. Чтобы четко оценить продуктивность компании и ее ценность для клиентов и партнеров, нужна статистика. Критически важно иметь возможность быстро и удобно ее анализировать.

Долгое время в качестве основного хранилища статистических данных мы использовали MySQL. Сущностей, которые могут получать статистику, в системе достаточно много. Например: сайт, виджет отображения, рекламный блок, рекламная кампания, Список можно продолжать до бесконечности. У всех этих сущностей есть определенные метрики, которые нужно анализировать: клики, показы, траты, заработок, конверсии и т. д. В MySQL хранился ряд таблиц, в которые агрегировались данные по определенным разрезам, достаточно небольшим, чтобы была возможность их компактно хранить и делать по ним быстрые выборки. Когда-то этого было достаточно.

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

Но скоро компания подросла в объемах. Увеличилось число клиентов, сильно выросли объемы трафика и, следовательно, — статистики. Увеличился объем данных в MySQL, и вот таблицы, которые раньше работали хорошо, начали доставлять хлопоты. Выборки уже не были такими быстрыми. Появились проблемы с изменением структуры таблиц, особенно когда нужно было изменить первичный ключ. В общем, наш DBA начал проводить гораздо больше времени наедине с MySQL.

Тем временем, объемы данных, которые нужно обрабатывать, только увеличивались. Мы поняли, что нужно что-то менять.

Road to ClickHouse

Итак, было ясно, что использовать MySQL как единственное хранилище статистических данных не очень перспективно. Нужно двигаться дальше. Но с чего начать?

Первой попыткой стало использование MongoDB. Ставка была на документоориентированный подход: мол, все, что нужно будет, допишем в коллекцию. Может, даже во вложенную коллекцию. В рамках теста реализовали фичу с использованием MongoDB. Все вроде заработало, но мы поняли, что в наших кейсах этого решение не намного лучше подхода с MySQL. Нужно искать что-то другое.

Следующим испытуемым был Druid. По описанию все идеально подходило для наших задач. Даже в статьях с примерами оперируют теми же метриками, что и мы, — кликами и показами. «Нужно пробовать», — подумали мы. Если я не ошибаюсь, дело было весной 2016.

С внедрением возникли затруднения. Учитывая тогдашние объемы данных, проблемы с производительностью возникли уже на предварительных тестах. Особенно запомнился момент, когда нужно было запускать множество демонов вручную. Процесс чем-то напоминал покупку мебели в IKEA: вроде купил шкаф, но его еще нужно собрать из досок, а здесь вроде установил хранилище данных druid, а демон индексации данных запусти сам.

Параллельно соседняя команда занималась тестированием Google BigQuery, но у них все тоже не было гладко.

В то время попадались на глаза статьи от Yandex о том, какие у них были проблемы с хранением статистики для метрики и как они решили их внутренним инструментом — ClickHouse. Вскоре он попал в публичный доступ. ClickHouse выглядел достаточно перспективно, но мы все-таки решили подождать пару месяцев, прежде чем попробовать его использовать.

ClickHouse Hello World

Осенью 2016 мы начали поглядывать в документацию ClickHouse и оценивать, как его можно применить в нашей системе.

ClickHouse — столбцовая СУБД. Отличие от строковых (например, MySQL, PostgreSQL) в том, что столбцовые БД хранят значение одного столбца вместе, а строковые хранят данные одной строки вместе, как ни странно.

Сразу стоит отметить некоторые ограничения, которые есть в ClickHouse:

  • Нет команды UPDATE: данные, записанные в ClickHouse, нельзя изменить. Или почти нельзя. Об этом расскажу дальше.
  • Нет команды DELETE, есть только DROP. Можно удалить таблицу целиком, или ее патрицию. Конкретную строчку удалить нельзя.
  • Нет транзакций.

Данные в ClickHouse хранятся в таблицах. Доступно немалое количество движков таблиц. Зачастую для хранения данных используют семейство движков MergeTree.

Немного об основных движках.

MergeTree

MergeTree — базовый движок семейства. Он обязывает задавать первичный ключ для таблицы. Первичный ключ используется в качестве ключа сортировки для данных. Данные хранятся отсортированными по первичному ключу. Ключ задается структурой ORDER BY при создании таблицы. Первичный ключ может быть неуникальным.

Также в таблице нужно указать поле для партиционирования. Партиция — это минимальная единица данных, которую можно удалить, задетачить, оптимизировать и т. д. Задается поле или выражение структурой PARTITION BY при создании таблицы.

Физически данные хранятся в виде файлов, называются они parts или «куски». Можно всегда заглянуть в таблицу system.parts и посмотреть, что и как. Данные в файле отсортированы по первичному ключу (ключу сортировки). Файл не может содержать данные из разных партиций. При вставке новых данных они записываются в файлы небольшого размера. Через некоторое время ClickHouse запускает процесс слияния (отсюда и название — MergeTree), который объединяет файлы и сортирует данные.

MergeTree — базовый движок семейства. Он не обладает никакими дополнительными возможностями, но хорошо подходит для хранения и накопления любых данных, которые нужно хранить в «сыром» виде. Партиционирование и первичный ключ — это основные инструменты оптимизации выборок.

SummingMergeTree

Основное отличие от MergeTree заключается в том, что движок позволяет задать цифровые столбцы для суммирования (накопления данных). Эти столбцы можно назвать счетчиками. На этапе слияния файлов для строк с одинаковым первичным ключом выполняется суммирование значений счетчиков. После этапа слияния файл имеет только одну строку по каждому первичному ключу. Строка хранит суммарные показатели счетчиков.

Так как таблица может хранить данные в нескольких патрициях, а следовательно, — в нескольких файлах, не стоит полагаться на полное суммирование всех строк одного первичного ключа в рамках таблицы. Для выборок, в которых нужно получить итоговые значение счетчиков, всегда стоит использовать GROUP BY.

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

CollapsingMergeTree

Основное отличие от MergeTree заключается в том, что движок позволяет задать столбец-метку, на основании которой он будет выполнять «схлопывание» данных с учетом первичного ключа на этапе слияния. Часто такой столбец называют sign. Столбец должен принимать значение 1 для новых данных, и −1 для старых. На этапе слияния ClickHouse оставит в таблице только самые актуальные данные по каждому первичному ключу, старые данные будут удалены.

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

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

ClickHouse First Blood

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

Требования предельно просты: нужно иметь возможность произвольно выбирать разрезы и метрики и быстро получать отчет. Интерфейс будет доступен клиентам, поэтому быстродействие и отказоустойчивость критичны. Выборка всегда осуществляется в рамках клиента.

Доступных размерностей для сбора статистики у нас предостаточно. Есть как внутренние: рекламный блок, сайт, различные маркеры трафика так и пользовательские: геолокация, ОС, браузер и т. д.

Среди метрик для сбора можно выделить основные:

  • событие показа рекламы на экране пользователя;
  • событие клика на рекламный блок;
  • итоговые затраты клиента с учетом всех коэффициентов.

Есть еще ряд дополнительных метрик, но они очень специфические для нашей отрасли.

Метрики можно разделить на две группы: неизменяемые и те, которые могут изменить свой статус в будущем. К неизменяемым можно отнести показы: после отображения рекламы на экране пользователя мы считаем, что показ состоялся и изменению это не подлежит. А статусы клика и траты могут быть пересчитаны потом (отсеивание ботов и т. д.).

Было решено хранить постоянные и изменяемые данные отдельно. Показы — в MergeTree, а клики и траты — в CollapsingMergeTree.

Первый этап сбора неизменяемых данных показал, что для хранения одного дня в MergeTree нам понадобится примерно 35 GB. Показов в нашей системе достаточно много.

Философия ClickHouse, по крайней мере его документации, гласит о том, что нужно хранить каждое событие отдельно. Но в рамках фичи интересен был именно счетчик, поэтому мы сменили движок — MergeTree на SummingMergeTree. Переход на новый движок сильно ужал таблицу.

Точных цифр на момент реализации не осталось, но могу привести пример того, сколько данные занимают сейчас:

SELECT SUM(shows)
FROM advertisement_statistics
WHERE date = yesterday()

┌─SUM(shows)─┐
│  725171805 │
└────────────┘

1 rows in set. Elapsed: 0.417 sec. Processed 99.99 million rows, 999.88 MB (239.89 million rows/s., 2.40 GB/s.)

Нужно учитывать, что объемы обрабатываемых данных растут постоянно и за 2 года существенно увеличились. Тем не менее, полные партиции за месяц занимают:

SELECT
    partition,
    formatReadableSize(SUM(bytes)) AS tableSize
FROM system.parts
WHERE table = 'advertisement_statistics'
GROUP BY partition
ORDER BY partition DESC
LIMIT 1, 4

┌─partition─┬─tableSize──┐
│ 201808    │ 158.06 GiB │
│ 201807    │ 147.63 GiB │
│ 201806    │ 142.91 GiB │
└───────────┴────────────┘

Явно меньше чем 35 GB в день.

Данные, которые могут быть изменены, начали хранить в CollapsingMergeTree, 1 строка = 1 ивент. Событий не так много, как показов. Первичный ключ позволяет идентифицировать конкретную строку. При изменении статуса события происходит повторная вставка строки с обновленными данными. При слиянии файлов ClickHouse оставит в таблице только последнюю вставленную строку.

Таблицы имеют схожую структуру, финальный отчет создается при помощи UNION ALL — двух сгруппированных к необходимому разрезу выборок. ClickHouse выполняет выборки параллельно.

После пары недель работы в режиме «для внутреннего использования и тестирования», в котором не было обнаружено проблем, фича стала доступна клиентам.

Что мы получили

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

Мы получили хранение основных статистических данных системы в едином хранилище с возможностью быстрых выборок. Настолько быстрых, что их можно использовать в пользовательских интерфейсах, даже с учетом UNION ALL. Из-за этого мы и начали использовать ClickHouse в различных частях системы. При налаженном механизме сбора данных оставалось только делать выборки. В таблицы со временем добавились новые данные, но суть не поменялась. Мы получили единое хранилище, которое позволяет работать с данным так, как нам удобно. А доступных фич так много, что мы не успели еще все использовать.

Редактирование данных

Со временем нам таки понадобилось откорректировать данные в ClickHouse. Но это не составило особого труда.

CollapsingMergeTree — движок, который позволяет обойти ограничение ClickHouse на редактирование записей. Вся его логика строится на использовании поля sign(1, −1) и порядка вставки для определения строк, которые нужно оставить в таблице. Но даже если что-то пошло не так и были записаны строки с одинаковым sign подряд, ClickHouse продолжит работу и просто оставит последние вставленные строки в таблице. При необходимости можно сделать дополнительную вставку и, по сути, заменить строки в таблице.

SummingMergeTree также позволяет обойти ограничение на редактирование строк, но немного в другом ключе. Для счетчиков в таблице лучше использовать типы полей Int* вместо UInt*. Это дает возможность откорректировать итоговые показатели счетчиков путем вставки новых записей с корректирующими показателями, в том числе и отрицательными.

Недавно в ClickHouse появилась возможность редактировать данные, но это ресурсоемкая операция и фича пока находится в beta.

Использование внешних словарей

ClickHouse позволяет использовать внешние словари. Очень пригодилась возможность подключать словари именно с MySQL. Необходимые настройки задаются в XML-формате на уровне сервера. В настройках можно указать, какие таблицы нужно подключить с MySQL и как часто их нужно перечитывать. Дальше можно ими пользоваться через семейство функций dictGet*('dict_name', 'attr_name', id).

Реальная жизнь

В процессе работы мы столкнулись с некоторыми сложностями.

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

OPTIMIZE TABLE very_data_table;
OPTIMIZE TABLE very_data_table PARTITION 201808;

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

Изменение структуры SummingMergeTree. Таблица сильно завязана на первичном ключе, в котором перечислены все возможные разрезы, а ClickHouse не позволяет изменять первичный ключ. Поэтому каждое его изменение заключается в том, чтобы:

  • создать новую таблицу;
  • перелить в новую таблицу данные, желательно по временной метке, например: все данные до сегодня или до начала текущего часа;
  • выполнить подмену таблицы с продовой, путем RENAME TO. Можно переименовывать несколько таблиц сразу;
  • долить в уже новую продовую таблицу недостающие данные. Переливание всей таблицы может быть долгим процессом, да и нужно иметь свободное дисковое пространство для таких операций.

В какой-то момент мы решили использовать ReplicatedMergeTree семейство таблиц для удобной master-master репликации, но увы. После перехода на Replicated ноды начали периодически зависать без причин, причем запись была направлена только в одну ноду. Через неделю поисков решения проблемы админы вернули все на обычные MergeTree.

Документация ClickHouse часто не успевает за новыми фичами, которые доступны в релизе, и нужно смотреть дополнительные источники информации (статьи/каналы).

ClickHouse & Quantiles

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

Одна из последних фич, где был использован ClickHouse, — это оценка производительности рекламных блоков. Суть фичи в том, что нужно провести анализ «успешности» рекламного объявления (тизера). Анализ проводится по коэффициентам, которые просчитываются внутренним сервисом отдельно. С помощью ClickHouse проводится финальный срез результатов. Срезы запрашивает интерфейс web-приложения, которым пользуются наши клиенты, поэтому быстродействие критично.

Чтобы не выкладывать сразу всю структуру финального решения, в которой может быть сложно быстро разобраться, опишу путь, пройденный при разработке фичи. Спойлер: очень выручила функция quantileExactWeighted — просчет нескольких квантилей на лету, с учетом веса значений. Подробности — ниже.

Начнем с того, что мы имеем: сервис, который вычисляет оценку «успешности» рекламных блоков. Внутри системы есть множество событий, в которых могут поучаствовать рекламные блоки. Сервис просчитывает оценку рекламного блока, добавляет служебную информацию (геолокацию, время, информацию о пользователе и т. д.) и отправляет это сообщение в Kafka. Сообщение имеет формат TSV (tab-separated values), удобный для передачи данных, если, конечно, в данных нет символа TAB.

Команда, ответственная за сервис, еще на этапе запуска поняла, что ивентов, которые продуцируют сообщения, крайне много, порядка 107 миллионов в час. Проанализировав поток данных за небольшой период, аналитики пришли к выводу, что можно применить сэмплирование еще на этапе продюсинга оценок в Kafka. Они решили собирать данные по каждому N-му событию, а значение N дописывать в сообщение, чтобы понимать, с каким сэмплингом были собраны данные. У оценок разных рекламных тизеров часто общие служебные данные (геолокация, время, информация о пользователе и т. д.). Для экономии трафика начали передавать в одном сообщении данные по нескольким рекламным блокам.

Пример сообщения в Kafka:

{timestamp}TAB{userInfo}TAB{locationId}TAB{advertisementPerfomanceIndexesJSON}TAB{samplingRate}

KafkaEgnine

Для взаимодействия с Kafka в ClickHouse есть специальный движок таблицы. Пример из документации:

 CREATE TABLE queue (
    timestamp UInt64,
    level String,
    message String
  ) ENGINE = Kafka('localhost:9092', 'topic', 'group1', 'JSONEachRow');

Он обладает неким набором настроек и даже умеет работать с TSV форматом. Идея в том, что при выборке данных из таблицы те подтягиваются из Kafka-топика. Как у этого движка обстоят дела с производительностью, сказать ничего не могу, так как использовать нам его не удалось. На момент разработки в движке был критический баг именно при работе с TSV, обидно. Пришлось таки делать консьюмер.

CreateTable

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

В нашем случае, Web-интерфейс будет запрашивать оценки по конкретным рекламным блокам, иногда с учетом геолокации и временных диапазонов. Максимальная требуемая точность временных выборок — 1 час. Данные актуальны для выборки 24 часа, после этого их можно удалять.

Нам нужно хранить:

  • timestamp — с точностью до часа;
  • advertisementId — id рекламного блока, целое число;
  • locationId — id геолокации, целое число;
  • performanceRatio — оценочный коэффициент продуктивности блока. Дробное значение с точностью до 3-го знака, часто имеет значение около единицы;
  • samplingRate — показатель семплирования, целое число.

Для базового примера этого достаточно. Данные пользователя затрагивать не будем, чтобы не нагружать пример.

Выбор партиционирования

Как показывает мой личный опыт, партиционирование лучше делать по полям, хранящим временную метку: час, день, месяц и т. д. В зависимости от характера данных и выборок. Например: если вам нужно часто выбирать данные за день, а не, скажем, за месяц, стоит делать партиционирование по дням. При выборке ClickHouse поймет, что вам нужна конкретная партиция, и не пойдет сканировать другие. Данные разных патриций лежат в отдельных файлах. Один файл не может хранить данные нескольких партиций. Сканирование только одной патриции положительно скажется на производительности выборки.

Не стоит делать партиционирование по полю, которое имеет большой диапазон значений. Это может плохо сказаться как на выборках, так и на количестве места, занимаемого таблицей.

Для фич, которые хранят большие объемы данных с коротким сроком актуальности, например, 24 часа, мы практикуем партиционирование по timestamp. Предварительно timestamp округляем до желаемой временной засечки, например, к целому часу, отбрасывая минуты и секунды выше. При острой необходимости можно на лету поменять логику округления timestamp. Например, можно увеличить засечки до 2-х часов, и новые данные будут собираться в патриции по 2 часа, без дополнительных манипуляций со структурой таблицы.

Для выборок советую использовать такой формат:

hour >= toUnixTimestamp('2018-08-27 16:00:00')

Запросы более читаемые чем, скажем:

hour >= 1535374800

В этой фиче интересны данные за 24 часа. Более старые данные не актуальны. ClickHouse позволяет удалять данные с таблиц по партициям, поэтому почасовое партиционирование на основе округленного timestamp оптимально для этой задачи.

Выбор первичного ключа

Помимо партиционирования нужно определить первичный ключ. Первичный ключ не требует уникальности данных в пределах таблицы. Ключ используется как указатель сортировки данных при хранении. При создании таблицы он задается через структуру ORDER BY, но в документации упоминается как первичный ключ.

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

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

ORDER BY (advertisementId, locationId, hour)

Хранение дробного значения

Оценка рекламного блока — это дробное значение. Точность — 3 знака после запятой. В ClickHouse есть такие типы полей: Float32 и Float64, но они зачастую слишком «float» для большинства реальных задач. Дробные значение лучше хранить в формате Int. В нашем случае перед записью нужно умножить на 1000 и стоит не забывать поделить на 1000 при выборке.

Таблица данных

Учитывая все вышесказанное, структура таблицы выглядит так:

CREATE TABLE advertisements_performance_data
(
    hour UInt32,
    advertisementId UInt64,
    locationId UInt64,
    performanceRatio UInt8,
    samplingRate UInt8
)
ENGINE = MergeTree()
PARTITION BY hour
ORDER BY (advertisementId, locationId, hour)
SETTINGS index_granularity = 8192

Данные из Kafka топика забирает консьюмер. Распаковывает сообщение, так как оно хранит данные сразу по нескольким рекламным блокам.

Оптимизация вставок

Уже на этом этапе можно оптимизировать вставки. Записывать в ClickHouse не отдельными строками по каждому блоку, а по примеру записи в Kafka топик: общие служебные данные + данные по нескольким блокам.

Идея вкратце:

  • пишем в таблицу данные по нескольким блокам одной строкой;
  • распаковываем данные, получаем оценки по блокам;
  • записываем оценки по блокам в целевую таблицу (в ту что, мы уже создали, — advertisements_performance_data).

В этом нам помогут:

  • ARRAY JOIN — распаковка данных по разным блокам внутри ClickHouse;
  • MaterializedView — перекладывание данных в целевую табличку;
  • Engine Null.

ARRAY JOIN

Крайне полезный инструмент ClickHouse. Позволяет выполнять JOIN как с внешним массивом, так и с локальным, находящимся в таблице. Проще объяснить примером.

Внешний массив:

CREATE TABLE gotham
(
    hero String
)
ENGINE = MergeTree()
ORDER BY tuple()

INSERT INTO gotham VALUES
('batman'),
('robin');

SELECT hero
FROM gotham

┌─hero───┐
│ batman │
│ robin  │
└────────┘

SELECT
    hero,
    task
FROM gotham
ARRAY JOIN ['find bad guys', 'do violence', 'save the day'] AS task

┌─hero───┬─task──────────┐
│ batman │ find bad guys │
│ batman │ do violence   │
│ batman │ save the day  │
│ robin  │ find bad guys │
│ robin  │ do violence   │
│ robin  │ save the day  │
└────────┴───────────────┘

Локальный массив:

CREATE TABLE gotham
(
    hero String,
    tasks Array(String)
)
ENGINE = MergeTree()
ORDER BY tuple()


INSERT INTO gotham VALUES
('batman', ['be a billionaire', 'ride a batmobile']),
('robin', ['wash a batmobile']);

SELECT *
FROM gotham

┌─hero───┬─tasks───────────────────────────────────┐
│ batman │ ['be a billionaire','ride a batmobile'] │
│ robin  │ ['wash a batmobile']                    │
└────────┴─────────────────────────────────────────┘

SELECT
    hero,
    task
FROM gotham
ARRAY JOIN tasks AS task

┌─hero───┬─task─────────────┐
│ batman │ be a billionaire │
│ batman │ ride a batmobile │
│ robin  │ wash a batmobile │
└────────┴──────────────────┘

С помощью ARRAY JOIN удобно «распаковывать» входящие данные.

Вернемся к рекламным блокам. Напомню, фича предполагает выборку по конкретному рекламному блоку, а вот хранить данные массивами не очень удобно. Поэтому будет использовано 2 таблицы: одна — для приема данных, вторая — для их хранения. Перекладываться данные будут автоматически с помощью MaterializedView.

MaterializedView

У ClickHouse есть встроенный инструмент под названием view. View делятся на два типа:

1. Обычные: ничего не хранят, по сути, являются сохраненными запросами.

CREATE VIEW gotham_tasks AS
SELECT
    hero,
    task
FROM gotham
ARRAY JOIN tasks AS task

SELECT *
FROM gotham_tasks

┌─hero───┬─task─────────────┐
│ batman │ be a billionaire │
│ batman │ ride a batmobile │
│ robin  │ wash a batmobile │
└────────┴──────────────────┘

2. MaterializedView: могут хранить данные или перекладывать данные на лету в другую таблицу. View собирает данные при вставке строк в таблицу, указанную в SELECT. По сути, MaterializedView работает как триггер на вставку, применяя SELECT к свежим строкам.

CREATE TABLE gotham_tasks_list
(
    hero String,
    task String
)
ENGINE = MergeTree()
ORDER BY tuple()

CREATE MATERIALIZED VIEW gotham_tasks_handler TO gotham_tasks_list
(
    hero String,
    task String
) AS
SELECT
    hero,
    task
FROM gotham
ARRAY JOIN tasks AS task

INSERT INTO gotham VALUES
('batman', ['be a billionaire', 'ride a batmobile']),
('robin', ['wash a batmobile']);

SELECT *
FROM gotham_tasks_list

┌─hero───┬─task─────────────┐
│ batman │ be a billionaire │
│ batman │ ride a batmobile │
│ robin  │ wash a batmobile │
└────────┴──────────────────┘

Данные хранятся в новой таблице gotham_tasks_list. Если удалить view, данные не исчезнут.

DROP TABLE gotham_tasks_handler

SELECT *
FROM gotham_tasks_list

┌─hero───┬─task─────────────┐
│ batman │ be a billionaire │
│ batman │ ride a batmobile │
│ robin  │ wash a batmobile │
└────────┴──────────────────┘

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

NullEngien

Данные в таблице, которая принимает вставки, никак не используются, кроме как для MaterializedView. Следовательно, их хранить не стоит. Можно использовать движок Null, данные в нем не сохраняются, но MaterializedView успешно забирает данные при вставке.

Структура БД с учетом распаковки данных

CREATE TABLE advertisements_performance_data_stream
(
    timestamp UInt32,
    locationId UInt64,
    samplingRate UInt8,
    `advertisements.id` Array(UInt64),
    `advertisements.performance` Array(UInt8)
)
ENGINE = Null()


CREATE TABLE advertisements_performance_data
(
    hour UInt32,
    advertisementId UInt64,
    locationId UInt64,
    performanceRatio UInt8,
    samplingRate UInt8
)
ENGINE = MergeTree()
PARTITION BY hour
ORDER BY (advertisementId, locationId)
SETTINGS index_granularity = 8192


CREATE MATERIALIZED VIEW advertisements_performance_data_handler TO advertisements_performance_data
(
    hour UInt32,
    advertisementId UInt64,
    locationId UInt64,
    performanceRatio UInt8,
    samplingRate UInt8
) AS
SELECT
    toUnixTimestamp(toStartOfHour(toDateTime(timestamp))) as hour,
    block.id as advertisementId,
    locationId,
    block.performance as performanceRatio,
    samplingRate
FROM advertisements_performance_data_stream
ARRAY JOIN advertisements as block

Итого, получаем сборку таблиц:

  • данные принимает advertisements_performance_data_stream. Таблица не хранит данные, просто принимает их;
  • к строкам, которые приходят на вставку, применяется выборка из advertisements_performance_data_handler;
  • результат выборки сохраняется в advertisements_performance_data.

Как можно заметить, округлением timestamp до прошлого полного часа занимается ClickHouse.

toUnixTimestamp(toStartOfHour(toDateTime(timestamp))) as hour

Пример вставки:

INSERT INTO advertisements_performance_data_stream VALUES
(1535655600, 1, 30, [101,102,103], [1100, 600, 1000])
(1535655600, 1, 30, [201,202,203], [1200, 700, 1150])
(1535655600, 2, 30, [101,102,103], [1300, 800, 1250])

SELECT *
FROM advertisements_performance_data

┌───────hour─┬─advertisementId─┬─locationId─┬─performanceRatio─┬─samplingRate─┐
│ 1535655600 │             101 │          1 │               76 │           30 │
│ 1535655600 │             101 │          2 │               20 │           30 │
│ 1535655600 │             102 │          1 │               88 │           30 │
│ 1535655600 │             102 │          2 │               32 │           30 │
│ 1535655600 │             103 │          1 │              232 │           30 │
│ 1535655600 │             103 │          2 │              226 │           30 │
│ 1535655600 │             201 │          1 │              176 │           30 │
│ 1535655600 │             202 │          1 │              188 │           30 │
│ 1535655600 │             203 │          1 │              126 │           30 │
└────────────┴─────────────────┴────────────┴──────────────────┴──────────────┘

В чем плюсы такого подхода:

  • Данные можно преобразовать на этапе выборки в MaterializedView. В ClickHouse есть огромный набор встроенных функций обработки данных.
  • На единый поток данных можно подвязать несколько MaterializedView. Иногда из общего пула данных нужно выбрать что-то для разных фич или, скажем, собирать в разные таблицы, в силу разности партиционирования.

Выборки

На этапе внедрения данные за 24 часа занимали примерно 1 TB. Размер данных большой, но проводить выборки по всем данным нет необходимости, срезы нужны именно по конкретному рекламному блоку. Ключ сортировки начинается именно с id рекламного блока, что сильно снижает количество строк, которые ClickHouse будет читать при выборке.

Нам нужно провести анализ производительности/успешности рекламного блока, выставить ему оценку. Примерная шкала оценивания:

  • успешный рекламный блок — advertisements_performance_data. performanceRatio выше единицы в 70% и более случаев. Это значит, что в 70% обработанных событий блок показал себя хорошо;
  • хороший блок — performanceRatio выше единицы в 50%;
  • нормальный — 30%;
  • ниже 30% — считаем таковым, который не оправдал надежд.

Нужные данные можно рассчитать через квантиль/персентиль. Описание с Wikipedia:

«Квантиль в математической статистике — значение, которое заданная случайная величина не превышает с фиксированной вероятностью. Если вероятность задана в процентах, то квантиль называется процентилем или перцентилем. Например, фраза „для развитых стран 95-процентиль продолжительности жизни составляет 100 лет“ означает, что ожидается, что 95 % людей не доживут до 100 лет».

Пример о возрасте достаточно исчерпывающий. Также квантили часто используют для замеров ответа приложения.

Что касается оценки рекламных блоков, это выглядит так:

Нужно понять, какова оценка блока на 30-м, 50-м и 70-м квантилях. В ClickHouse есть целое семейство функций, посвященных расчету квантилей — quantile(), а также копия этого семейства для расчета нескольких квантилей сразу — quantiles(). Если нужно вычислить несколько значений, всегда лучше использовать quantiles. Эта функция использует общие обработанные данные для расчета нескольких квантилей. Под этот кейс подходит quantilesExact(level1, level2, ...)(x). level — какой квантиль считать, задается значением 0 < level < 1. x — по какому полю считать. По сравнению с quantiles, quantilesExact вычисляет квантили более точно, хоть и более затратно.

SELECT
    advertisementId,
    quantilesExact(0.3, 0.5, 0.7)(performanceRatio) AS quantiles
FROM advertisements_performance_data
GROUP BY advertisementId

┌─advertisementId─┬─quantiles─────┐
│             203 │ [126,126,126] │
│             101 │ [20,76,76]    │
│             202 │ [188,188,188] │
│             103 │ [226,232,232] │
│             201 │ [176,176,176] │
│             102 │ [32,88,88]    │
└─────────────────┴───────────────┘

Результат квантилей возвращается в виде массива. Напомню, что ClickHouse использует ключи массивов, начиная с 1.

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

Также можно как угодно варьировать группировку и условие выборки. Например, указав несколько геолокаций, получить отчет по конкретному городу или стране за последние 3 часа и т. д.

Оптимизация хранения места

Остался всего один момент, который стоит оптимизировать — данные занимают 1TB.

Улучшить ситуацию нам помогут:

  • SummingMergeTree — движок таблицы для автоматического схлопывания данных;
  • quantilesExactWeighted(level1, level2, ...)(x, weight) — тот же метод расчета квантилей, но с учетом веса записи.

Структура:

CREATE TABLE advertisements_performance_data
(
    hour UInt32,
    advertisementId UInt64,
    locationId UInt64,
    performanceRatio UInt8,
    eventsCount UInt64
)
ENGINE = SummingMergeTree(eventsCount)
PARTITION BY hour
ORDER BY (advertisementId, locationId, performanceRatio)
SETTINGS index_granularity = 8192

CREATE MATERIALIZED VIEW advertisements_performance_data_handler TO advertisements_performance_data
(
    hour UInt32,
    advertisementId UInt64,
    locationId UInt64,
    performanceRatio UInt8,
    eventsCount UInt64
) AS
SELECT
    toUnixTimestamp(toStartOfHour(toDateTime(timestamp))) as hour,
    block.id as advertisementId,
    locationId,
    block.performance as performanceRatio,
    toUInt64(samplingRate) as eventsCount
FROM advertisements_performance_data_stream
ARRAY JOIN advertisements as block

Основные изменения:

  • поле advertisements_performance_data. samplingRate превратилось в eventsCount с типом UInt64. Для хранения количества всех событий нужна более высокая размерность поля.
  • таблица advertisements_performance_data поменяла движок на SummingMergeTree, с суммированием eventsCount. В первичный ключ, он же ключ сортировки, теперь входит фактическое значение оценки рекламного блока. По сути, теперь мы храним все уникальные оценки блока + их количество. Строки будут автоматически суммироваться, а подсчет счетчика eventsCount — это забота ClickHouse.

Заполним таблицу:

# можно выполнить несколько раз
INSERT INTO advertisements_performance_data_stream VALUES
(1535655600, 1, 30, [101,102,103], [1100, 600, 1000])
(1535655600, 1, 30, [201,202,203], [1200, 700, 1150])
(1535655600, 2, 30, [101,102,103], [1300, 800, 1250])

SELECT *
FROM advertisements_performance_data

┌───────hour─┬─advertisementId─┬─locationId─┬─performanceRatio─┬─eventsCount─┐
│ 1535655600 │             101 │          1 │               76 │         150 │
│ 1535655600 │             101 │          2 │               20 │         150 │
│ 1535655600 │             102 │          1 │               88 │         150 │
│ 1535655600 │             102 │          2 │               32 │         150 │
│ 1535655600 │             103 │          1 │              232 │         150 │
│ 1535655600 │             103 │          2 │              226 │         150 │
│ 1535655600 │             201 │          1 │              176 │         150 │
│ 1535655600 │             202 │          1 │              188 │         150 │
│ 1535655600 │             203 │          1 │              126 │         150 │
└────────────┴─────────────────┴────────────┴──────────────────┴─────────────┘

Здесь уже видно, что данные схлопнулись, вставляем мы с samplingRate 30, а eventsCount уже 150, да и лишних записей нет.

Пример выборки:

SELECT
    advertisementId,
    quantilesExactWeighted(0.3, 0.5, 0.7)(performanceRatio, eventsCount) AS quantiles
FROM advertisements_performance_data
GROUP BY advertisementId

┌─advertisementId─┬─quantiles─────┐
│             203 │ [126,126,126] │
│             101 │ [20,20,76]    │
│             202 │ [188,188,188] │
│             103 │ [226,226,232] │
│             201 │ [176,176,176] │
│             102 │ [32,32,88]    │
└─────────────────┴───────────────┘

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

Благодаря тому, что значения оценок колеблются вокруг единицы, такой подход уменьшает размер данных с 1 TB до 30 GB, что также положительно сказалось на выборках. Сканировать, как не крути, теперь нужно меньше.

Коротко о консьюмере

Консьюмер принимает данные из Kafka и пишет в ClickHouse. Пишем данные через GitHub — roistat/go-clickhouse: Golang ClickHouse connector пакетами по 100k, параллельными потоками. Потоков может быть до 12-ти, стартуют потоки не чаще, чем раз в секунду. Такие настройки выведены вручную опытным путем. Увеличение и уменьшение размера пакета негативно сказывались на скорости вставки. ¯_(ツ)_/¯ В самую крупную ноду пишем порядка 2kkk вставок за 24 часа, что примерно — 71kk в час. Визуализация из Datadog:

Production

Теория — это хорошо но, что мы имеем на проде.

Конфигурация сервера в Google Cloud:

  • n1-standard-16 (16 виртуальных ЦП, 60 ГБ памяти) — для менее нагруженных нод;
  • n1-standard-32 (32 виртуальных ЦП, 120 ГБ памяти) — для основных.

Занимаемое место:

SELECT
    table,
    formatReadableSize(SUM(bytes)) AS tableSize
FROM system.parts
WHERE active
GROUP BY table
ORDER BY tableSize DESC

┌─table────────────────┬─tableSize─┐
│ auction_data_teasers │ 36.19 GiB │
└──────────────────────┴───────────┘

Количество строк и хранимых ивентов в них:

SELECT
    COUNT(),
    SUM(auctions)
FROM auction_data_teasers

┌─────COUNT()─┬─SUM(auctions)─┐
│ 11533653287 │ 3883532374770 │
└─────────────┴───────────────┘

1 rows in set. Elapsed: 313.706 sec. Processed 11.53 billion rows, 92.27 GB (36.77 million rows/s., 294.13 MB/s.)

11kkk строк, которые хранят почти 3.8kkkk ивентов, c учетом семплирования.

Показатели выборки:

11 rows in set. Elapsed: 0.752 sec. Processed 4.51 million rows, 108.33 MB (6.00 million rows/s., 144.03 MB/s.)

Заключение

За 2 года использования ClickHouse на продакшине у нас были как стремительные взлеты, так и взлеты пониже. Но все же мы склоняемся к тому, чтобы использовать его активнее. ClickHouse — хороший инструмент для ряда задач.

И напоследок — несколько полезных ссылок:

👍ПодобаєтьсяСподобалось1
До обраногоВ обраному8
LinkedIn

Схожі статті




43 коментарі

Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.

Для тих, хто юзає jdbc драйвер клікхауса — вчора ми релізнулу свій форк офіційного драйвера. Швидший і в 7 раз менший, без лівих залежностей — github.com/blynkkk/clickhouse4j
Проект вже є в mvn central. Буду вдячний за будь які відгуки.

TABIX — Open source simple business intelligence application and sql editor tool for ClickHouse Database — web интерфейс.
Install

!!!! NOT READY FOR USE !!!!
^^^^^^^^^^^^^^^^^^^^^^^^^^^

не вижу данного текста здесь tabix.io/doc/Install , давно используем, все ок

не вижу данного текста здесь tabix.io/doc/Install

tabix.io/doc/Server/Install

давно используем, все ок

какой из вариантов?

tabix.io/doc/Install

этот

tabix.io/doc/Server/Install

возможно или прошлая версия, или что-то еще, уже и реп таких не вижу

Параллельно соседняя команда занималась тестированием Google BigQuery, но у них все тоже не было гладко.

Что именно не так гладко получилось с BigQuery? А то используем и вдруг не знаем чего...

Точно не скажу, тестировал таки не я.
После внедрения CH, ребята также провели тесты для сравнения, и на сколько я знаю то основные минусы были:

  • на конкретном тестируемом примере получалось медленнее чем в CH;
  • стоимость превышала аренду серверов для CH.

При наличники, к тому времени, готовых наработок по CH, которые решают наши проблемы, сильно копать в сторону BQ уже не стали.

Не смотрели в сторону аппаратных решений? Например, Swarm64, стоит денег, но по факту продолжаете работать с тем же mysql, только быстрее.

В таком направлении не думали. Бегло просмотрел сайт, стоящая штука?

Вот небольшое видео с цифрами youtu.be/eDNUEhceQcs

Я не автор темы, но со своей колокольни отвечу.

* Судя по видео, в разных кейсах ускорение в 4-5 раз. На задачах автора, использование column store вместо rdbms, даёт выигрыш в десятки раз.
* Не совсем ясно что там насчёт сжатия данных.
* Вендор-лок по железу, что тоже большой минус.
* Не нашел живых примеров кто его использует в продакшн и независимых бенчмарков.

Не спорю что ребята молодцы, ускорили существующее решение в разы. Но мне видится его область применения — это большие, сложные legacy проекты, которые менять сложно, непонятно и дорого, и вертикальным масштабированием с тюнингом уже всё выжали из железа (а желозо сейчас хорошее).

Всегда было интересно, почему первичный ключ некоторые делают «естественным», а потом DBA страдает в ситуации «особенно когда нужно было изменить первичный ключ»... Как я понял из статьи, вы и дальше так делаете... Почему?

На том этапе были проблемы как с «естественными» ключами, так и с «суррогатными», в нашем случае автоинкремент.

Был, например, кейс с таблицей, которая хранит лог неких событий клиента, которые могут потом дополнятся информацией. Записей не предполагалось очень много, был выбран автоинкрементный PK.
Спустя некоторое время, сегмент бизнеса сильно вырос, и мы внезапно уперлись в максимальное значение PK. Таблица к тому времени была довольно большая, и у DBA как раз таки были проблемы с переводом ее на «естественный» ключ.

Правда в достижении лимита PK также сыграл метод обновления записей:
INSERT ON DUPLICATE KEY UPDATE
Попытка INSERT выполняется раньше, чем UPDATE. Потому MySQL увеличивал счетчик автоинкремента, хоть потом и обновлял существующую запись.

Изменение структуры данных на больших таблтцах в mysql это вообще боль. Даже новые возможности online ddl как-то странно работают, всё равно лочилась таблица хоть по доке и не должна. Благо хоть pt-online-schema-change есть.

Вы случаем не мигрировали с 5.5. на 5.6 mysql? Обычно такое бывает, когда после миграции над таблицами не проводи alter table(optimize), и таблицы не конвертировались в новые структуры. Поэтому таблицы продолжают лочиться.

упереться в

максимальное значение PK

это прикольно )
присоединюсь к вопросу выше — при таких объемах почему не Goggle BigQuery/ AWS Athena?
там обещают отсутствие проблемы с объемами...

У нас bigquery получался намного дороже clickhouse’а. Также bigquery сканировал все строки таблицы на наших запросах, в то время как кликхаус использовал индекс для минимизации количества сканируемых строк. Выходило медленнее и дороже. Это было год назад. Может, за год что-то изменилось в лучшую сторону.

Почему именно ClickHouse?
Какие альтернативы рассматривались?
Альтернативой ClickHouse например является Druid. Простой вопрос «почему скажем не Druid»?

Чукча не читатель — чукча писатель:

С внедрением возникли затруднения. Учитывая тогдашние объемы данных, проблемы с производительностью возникли уже на предварительных тестах. Особенно запомнился момент, когда нужно было запускать множество демонов вручную. Процесс чем-то напоминал покупку мебели в IKEA: вроде купил шкаф, но его еще нужно собрать из досок, а здесь вроде установил хранилище данных druid, а демон индексации данных запусти сам.

Да, пробежал бегло, каюсь.
Полнофункциональны docker контейнер druid-a поднимается ровно 5 минут.
Для более серьезных инсталяций, druid является частью стэка Hadoop-a от ( HortonWorks).

Спасибо за отличный материал, ClickHouse не гарантирует консистентности MV с таблицей донором, правильно ли я понимаю что при использовании NullEngine эта проблема отпадает поскольку у вас оригинальной таблицы нет вовсе. Критично ли для вашего сценария потеря данных? Может быть вы её просто не заметили... Или может быть действительно этот вариант записи нормально работает ?

Спасибо за комментарий.

В нашем случае небольшая потеря данных не критична. C использованием NullEngine, таки нет с чем сравнить данные в MV.

Еще не использовали MV в продакшин фичах, где можно четко проверить консистентность их работы на долгом временном отрезке. Для локальных потребностей что-то собрать — все было хорошо.

Из последнего, что слышал о MV: поменяли pre-insert на post-insert при вставке в MV.

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

MongoDB
query time (s) 300
storage (GiB) 45
alter (add column) (s) 0

MonetDB
query time (s) 0.144
storage (GiB) 3.9
alter (add column) (s) 2

ClickHouse
query time (s) 0.187
storage (GiB) 3
alter (add column) (s) 0.018

Vertica
query time (s) 0.115
storage (GiB) 3.6
alter (add column) (s) 3.6

Мы делали тест Druid vs Vertica на groupBy запросах ( это самый тяжелый запрос для Druid-a). Это вообше не сравнимые вещи. Если входной поток более или менее интенсивный Vertica просто затыкается.

ClickHouse это же ж Яндекс да? Это же ж враги!?

Ну нормальная техническая статья жеж, набросить других топиков хватает.

Нет дорогой, на языке бизнеса это называется «не учесть политически риски внедрения продукта», а если попроще — это просто банальная недальновидность.

Решения то в любом случае принимаются не джунами в целях resume drive development.

Естественно это нужно учитывать. Когда бизнес — это гос.компания или международная корпорация, то там и так прописаны правила покупки/лицензирования софта и в Украине вряд ли какой банк позволит себе использовать рос.разработки (точно знаю что как минимум Приват не позволит).

Но бизнес — это не только корпорации с миллиардными оборотами, тысячами человек штата, жёсткими политиками безопасности и индексом на бирже.

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

Спасибо за статью!
Расскажите, как вы бэкапите данные и как лимитируете кол-во и сложность запросов от пользователей при построении отчетов?

Небольшое уточнение — КХ уже поддерживает UPDATE и DELETE

это не стандартный ANSI SQL UPDATE/DELETE, и работает не так, как обычно ожидается в обычной базе. Там все еще куча приколов, см www.altinity.com/...​/16/updates-in-clickhouse — вообще их добавили в спешке частично из за GDPR, на тот редкий случай если уж очень надо удалить\обновить конкретные записи — чтобы техническая возможность была.

Спасибо за коммент и вопросы.

По бэкапам: пока только снапшоты, данные которые были добавлены после снапшота можно оперативно долить c тех же источников. Если будет все совсем плохо — всегда есть HDFS. Но честно сказать, кейса с корраптом данных, чтобы их нужно было восстанавливать из снапшота у нас еще не было. Если же даунтайм ClickHouse — консьюмеры kafka просто ждут пока он вернется к жизни.

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

По обновлению данных: я упомянул новую фичу в разделе «Редактирование данных», в самом конце. Пока не использовали, отпугивает слово beta.

Не пробовали использовать chproxy для ограничения нагрузки на кликхаус от пользователей? github.com/Vertamedia/chproxy

запущен, но «на все 100» не используем

ClickHouse очень интересная база для real-time аналитики. Как любой относительно свежий опенсорц, местами сыровата и для production нужен толковый девопс который сумеет правильно приготовить кластер.

На самом деле, CH хорош даже для small data в режиме single-node — в этом случае поднять его дело пары минут, а способность молотить сотни ГБ очень быстро на скромном сервере может очень приятно удивить.

Что еще можно отметить: своеобразный, нестандартный JOIN syntax. В общем и в целом, JOIN вообще не рекомендуется: можно использовать словари (dictionary) для резолва лукапов по внешней базе, и вообще следовать принципу one big table — тысяча колонок нормально для этой базы, а повторяемые значения не проблема из за встроенной компрессии.

Да кстати, в SeekTable есть нативный коннектор (бинарный протокол) к ClickHouse — приглашаю всех желающих попробовать эту связку.

Толковый технический материал. Большое спасибо !

Хоть раз в несколько месяцев есть что интересное здесь почитать. Спасибо.

Спасибо за отличную статью. Скажите, а как изменилось потребление ресурсов в ClickHouse в вашей задаче по сравнению с MySQL? Диск/память/CPU.

Спасибо за коммент.

По метрикам использования ресурсов в масштабах системы сказать сложно. В ClickHouse мы храним сильно больше данных, и в более сыром виде. С появлением ClickHouse аппетиты по хранению данных сильно выросли.

Сейчас в ClickHouse храним ~4TB + ведутся работы по увеличению доступного места.

Но, касательно места, как раз на этой неделе выполнял перенос небольшой таблицы из MySQL в ClickHouse.

Занимаемое место в MySQL: 11.3GB
В ClickHouse с аналогичной структурой: 1.12GB
Если навесить дополнительный MATERIALIZED View для сбора агрегатов, по интересующему разрезу и метрикам, с временной засечкой к часу: 19MB

Вы ведь используете gcloud — там не должно быть проблем с расширением дисков (вплоть до 60TB).

Можно. Работы больше о том, что мы попутно хотим выполнить/потестировать некоторые улучшения нашей структуры таблиц.

Был одинаковый набор данных в mysql в 2 таблицах- одна InnoDB, вторая партицианированная по дня Archive таблица. Между ними разница в объёме 12:1

После переноса в ClickHouse — размер стал чуть меньше чем у архивной таблицы в mysql. Скорость выполнения запросов используя только индексы по партициям у clickhouse от десятков до сотни раз выше чем у mysql.

Ну и партиционированные archive таблицы по достижении размера в сотни гигабайт становятся «хрупкими», могут «ломаться» какие-то партиции при рестарте сервера или из-за фазы луны. Не часто, но раз в несколько месяцев точно. В общем когда данные в сжатом виде занимают хотя бы 500GB+ не рекомендую archive движок, тем более с mysql 8.0 в нём партициионирование убрали. Нужно использовать другие технологии и ClickHouse выглядит очень интересно.

Підписатись на коментарі