ClickHouse для продвинутых пользователей в вопросах и ответах

В апреле инженеры Авито собирались на онлайн-посиделки с главным разработчиком ClickHouse Алексеем Миловидовым и Кириллом Шваковым, Golang-разработчиком из компании Integros. Обсуждали, как мы используем систему управления базами данных и какие сложности у нас возникают.

По мотивам встречи мы собрали статью с ответами экспертов на наши и зрительские вопросы про бэкапы, решардинг данных, внешние словари, Golang-драйвер и обновление версий ClickHouse. Она может быть полезна разработчикам, которые уже активно работают с СУБД «Яндекса» и интересуются её настоящим и будущим. По умолчанию ответы Алексея Миловидова, если не написано иное.

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

ClickHouse для продвинутых пользователей в вопросах и ответах

Содержание

Если не хочется читать текст, можно посмотреть запись посиделок на нашем ютуб-канале. Таймкоды — в первом комментарии под видео.

ClickHouse постоянно обновляется, а наши данные — нет. Что с этим делать?

ClickHouse постоянно обновляется, а наши данные, которые были optimize final обработаны, не обновляются и лежат в резервной копии.

Предположим, у нас случилась какая-нибудь проблема, и данные были утеряны. Мы решили восстановиться, и оказалось, что старые партиции, которые лежат на сохранении в серверах бэкапов, очень сильно расходятся с используемой на данный момент версией ClickHouse. Что делать в такой ситуации, и возможна ли она?

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

Какие лучшие практики есть на данный момент по резервному копированию данных из ClickHouse?

Как делать резервные копии с тем учетом, что у нас есть операции optimize final, огромная база данных на терабайты, и данные, которые обновляются, предположим, за последние три дня, а дальше с ними никаких процедур не происходит?

Мы можем накостылять собственное решение и на баше написать: собирай так-то и так-то эти резервные копии. Может быть, костылять ничего не надо, и велосипед давно изобретён?

Для начала по поводу лучших практик. Мои коллеги всегда советуют в ответ на вопросы про бэкапы напоминать про сервис «Яндекс.Облако», где эта задача уже решена. Так что пользуйтесь им, если есть такая возможность.

Полного решения, на сто процентов встроенного в ClickHouse, для бэкапов нет. Есть некоторые заготовки, которые можно использовать. Чтобы получить полное решение, придётся либо немножко вручную повозиться, либо сделать обёртки в виде скриптов.

Начну с самых простых решений и закончу самыми навороченными в зависимости от объёма данных и размера кластера. Чем больше кластер — тем сложнее становится решение.

Если таблица с данными занимает всего несколько гигабайт, бэкап можно сделать так:

  1. Сохранить определение таблиц, то есть метаданные — show create table.
  2. Сделать дамп с помощью ClickHouse клиента — select * from table в файл. По умолчанию вы получите файл в формате TabSeparated. Если хочется более эффективно — можно в формате Native.

Если объём данных больше, то бэкап займёт больше времени и много места. Это называется логический бэкап, он не привязан к формату данных ClickHouse. Если он есть, то в крайнем случае вы сможете взять бэкап и загрузить в MySQL для восстановления.

Для более продвинутых случаев в ClickHouse встроена возможность создать снапшот партиций в локальной файловой системе. Эта возможность доступна в виде запроса alter table freeze partition. Или просто alter table freeze — это снапшот всей таблицы.

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

Восстановить такой бэкап достаточно легко. Первое — создаёте таблицы по имеющимся определениям таблиц. Далее копируете сохраненные снапшоты партиций в Directory-Detached для данных таблиц и выполняете запрос attach partition. Такое решение вполне подходит для самых серьёзных объемов данных.

Иногда требуется что-то ещё круче — в тех случаях, когда у вас десятки или даже сотни терабайт на каждом сервере и сотни серверов. Тут есть решение, которое я подсмотрел у коллег из «Яндекс.Метрики». Я бы не рекомендовал его каждому — прочитайте и сами решите, подходит оно или нет.

Сначала нужно создать несколько серверов с большими дисковыми полками. Далее на этих серверах поднять несколько ClickHouse серверов и настроить их так, чтобы они работали как ещё одна реплика для тех же шардов. И дальше использовать на этих серверах файловую систему или некий инструмент, который позволяет создавать снапшоты. Тут есть два варианта. Первый вариант — это LVM снапшоты, второй вариант — ZFS на Linux.

После этого каждый день нужно создавать снапшот, он будет лежать и занимать какое-то место. Естественно, если данные меняются, то со временем объём места будет увеличиваться. Этот снапшот можно достать в любой момент и восстановить данные, вот такое странное решение. Плюс ещё надо ограничить эти реплики в конфиге, чтобы они не пытались становиться лидерами.

Можно ли будет организовать контролируемое отставание реплик в валах?

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

Можно ли сделать какие-то roll back для альтеров? Например, в существующем вале взять и сказать, что до этого момента применяй изменения, а с этого момента изменения перестань применять?

Если в наш кластер пришла команда и поломала его, то у нас есть условная реплика с отставанием в час, где мы можем сказать, что давайте будем именно её использовать на данный момент, но последние десять минут изменений в ней применять не будем?

Для начала про контролируемое отставание реплик. Такой запрос от пользователей был, и мы создали issue на Гитхабе с просьбой: «Если кому-то это надо, ставьте лайк, ставьте сердечко». Никто не поставил, и issue закрыли. Тем не менее, уже сейчас можно получить такую возможность, настроив ClickHouse. Правда, только начиная с версии 20.3.

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

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

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

Теперь возникает вопрос, как это защищает от альтеров. Здесь стоит посмотреть глубже, потому что в старых версиях ClickHouse альтер работал таким образом, что просто непосредственно менял куски. Есть кусок данных с какими-то файлами, и мы делаем, например, alter drop column. Тогда этот столбец физически удаляется из всех кусков.

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

То же касается альтеров типа мутаций. Когда вы делаете alter delete или alter update, он не изменяет кусок, а создаёт новый. А потом удаляет старый.

Как быть, если структура таблицы поменялась?

Как поднять бэкап, который был сделан со старой схемой? И второй вопрос про кейс со снапшотами и средствами файловой системы. Годится ли здесь Btrfs вместо ZFS на Linux LVM?

Если вы делаете attach partition партиции с другой структурой, то ClickHouse вам скажет, что так нельзя. Решение такое. Первое — создать временную таблицу типа MergeTree со старой структурой, прицепить туда данные с помощью attach, сделать запрос alter. Потом можно либо скопировать или перенести эти данные и сделать attach снова, либо использовать запрос alter table move partition.

Теперь второй вопрос — можно ли использовать Btrfs. Для начала, если у вас есть LVM, то достаточно LVM снапшотов, а файловая система может быть и ext4, это не имеет значения. С Btrts всё зависит от вашего опыта её эксплуатации. Это зрелая файловая система, но всё равно возникают некоторые подозрения по поводу того, как всё отработает на практике в конкретном сценарии. Я бы не советовал это использовать, если у вас нет Btrfs в продакшне.

Какие сейчас лучшие практики в решардинге данных?

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

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

Первый способ, которым это можно сделать, — это скопировать часть партиций на новые серверы с помощью запроса alter table fetch partition. Например, были у вас партиции по месяцам, и вы берёте первый месяц 2017 года и копируете на новый сервер, потом — третий месяц копируете на какой-то ещё новый сервер. И так делаете, пока не станет более-менее равномерно.

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

У этого способа есть один недостаток, и он зависит от схемы шардирования, закладывались ли вы на эту схему шардирования, какой у вас был ключ шардирования. В вашем примере для случая с метриками ключ шардирования — это хеш от пути. Когда у вас идёт select в Distributed таблицу, он идёт сразу на все шарды кластера и забирает оттуда данные.

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

Но бывают случаи и более сложные. Если на уровне логики приложения вы закладываетесь на специальную схему шардирования, что этот клиент расположен на таком-то шарде, и запрос можно отправить сразу туда, а не в Distributed таблицу. Или вы используете достаточно свежую версию ClickHouse и включили настройку optimize skip unused shards. В этом случае во время запроса select выражение в секции where будет проанализировано, и будет вычислено, на какие шарды необходимо пойти согласно схеме шардирования. Это работает при условии, что данные разложены именно в соответствии с этой схемой шардирования. Если вы их переложили вручную, соответствие может поменяться.

Итак, это способ номер один. И я жду вашего ответа, подходит способ, или идём дальше.

Владимир Колобаев, lead system administrator в Авито: Алексей, тот способ, который вы упомянули не очень хорошо ложится, когда надо размазать нагрузку в том числе и на чтение. Мы можем взять партицию, которая месячная и можем предыдущий месяц унести на другую ноду, но когда придёт запрос за этими данными, мы будем нагружать только её. А хотелось бы нагружать весь кластер, потому что, в противном случае, у нас какое-то время вся нагрузка на чтение будет обрабатываться двумя шардами.

Алексей Миловидов: Ответ тут странный — да, плохо, но может и прокатить. Объясню, как именно. Стоит посмотреть на сценарий нагрузки, который идёт за вашими данными. Если это данные мониторинга, то почти наверняка можно сказать, что подавляющее большинство запросов идёт за свежими данными.

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

Но зачастую у вас не будет запросов именно за февраль 2019 года. Скорее всего, если уж запросы идут в 2019 год, то они будут за весь 2019 год — за большой интервал времени, а не за какую-то маленький диапазон. И такие запросы тоже смогут равномерно нагрузить кластер. Но в целом ваше замечание совершенно верно, что это такое ad hoc решение, которое не размазывает данные полностью равномерно.

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

Например, у вас есть данные мониторинга. Данные мониторинга растут по трём причинам. Первая — накопление исторических данных. Вторая — рост трафика. И третья — увеличение количества тех вещей, которые подпадают под мониторинг. Появляются новые микросервисы и метрики, которые нужно сохранять.

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

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

Владимир Колобаев: Дело в том, что мы очень часто апеллируем к историческим данным, так как мы в режиме реального времени сравниваем текущее положение с историческим. И для нас важно иметь быстрый доступ к большому объёму данных, и ClickHouse с этим отлично справляется.

Вы абсолютно правы, большинство запросов на чтение мы испытываем за последний день, как и любая система мониторинга. Но при этом на исторические данные нагрузка тоже достаточно большая. Она в основном от системы алертинга, которая каждые тридцать секунд ходит и говорит ClickHouse: «Отдай мне данные за последние шесть недель. А теперь построй-ка мне из них какую-нибудь скользящую среднюю, и давай сравним текущее значение с историческим».

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

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

Есть основной кластер с событиями «Яндекс.Метрики». События — это просмотры страниц, клики и переходы. Большинство запросов идёт к конкретному веб-сайту. Вы открываете сервис «Яндекс.Метрика», у вас есть сайт — avito.ru, заходите в отчёт, и идёт запрос по вашему сайту.

Но есть и другие запросы — аналитические и глобальные, которые делают внутренние аналитики. На всякий случай отмечу, что внутренние аналитики делают запросы только по сервисам «Яндекса». Но тем не менее, даже сервисы «Яндекса» занимают существенную долю от всех данных. Это запросы не по конкретным счётчикам, а по более широкой фильтрации.

Как организовать данные таким образом, чтобы и по одному счётчику всё работало эффективно, и глобальные запросы тоже? Сложность ещё состоит в том, что количество запросов в ClickHouse на кластер «Метрики» — несколько тысяч в секунду. При этом нетривиальных запросов, например, несколько тысяч в секунду один сервер ClickHouse не тянет.

Размер кластера — шестьсот с чем-то серверов. Если над этим кластером просто натянуть Distributed таблицу и отправлять туда несколько тысяч запросов, то станет ещё хуже, чем отправлять их на один сервер. С другой стороны, вариант с тем, что данные размазаны равномерно, а мы идём и запрашиваем со всех серверов, сразу отметаем.

Есть вариант диаметрально противоположный. Представьте, если мы данные будем шардировать по сайтам, и запрос для одного сайта пойдёт на один шард. Теперь кластер вполне сможет вытянуть десять тысяч запросов в секунду, но на одном шарде какой-то один запрос будет работать слишком медленно. Он уже не будет масштабироваться по пропускной способности. Особенно если это сайт avito.ru. Я не раскрою секрет, если скажу, что Авито — это один из наиболее посещаемых сайтов в рунете. И обрабатывать его на одном шарде было бы безумием.

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

Как это всё масштабируется? Количество кластерков не меняется — как было несколько лет назад тридцать девять, так и осталось. Но внутри каждого из них мы постепенно увеличиваем количество шардов по мере накопления данных. И схема шардирования в целом такая — разбиение на эти кластерки идёт по веб-сайтам, и для того чтобы понять, какой сайт на каком кластере, используется вообще отдельная метабаза в MySQL. Один сайт — на одном кластерке. А внутри него шардирование идёт по идентификаторам посетителей.

При записи мы их разбиваем по остатку от деления идентификатора посетителя. Но при добавлении нового шарда схема шардирования меняется, мы продолжаем разбивать, но при остатке от деления на другое число. Это значит, что один посетитель уже всё-таки расположен на нескольких серверах, и закладываться на это нельзя. Это сделано исключительно для того, чтобы данные лучше сжимались. А при запросах мы идем в Distributed таблицу, которая смотрит на кластерок и обращается к десяткам серверов. Вот такая дурацкая схема.

Но мой рассказ будет неполным, если я не скажу, что от этой схемы мы отказались. В новой схеме мы всё изменили и все данные скопировали с помощью clickhouse-copier.

В новой схеме все сайты делятся на две категории — большие и маленькие. Я не знаю, как там выбран порог, но в результате получилось так, что большие сайты записываются на один кластер, где 120 шардов по три реплики в каждом — то есть 360 серверов. И схема шардирования такова, что любой запрос идёт сразу на все шарды. Если вы сейчас в «Яндекс.Метрике» откроете любую страницу отчёта для avito.ru, запрос пойдёт на 120 серверов. Больших сайтов в рунете мало. И запросов получается не тысяча в секунду, а даже меньше сотни. Всё это спокойно прожёвывает Distributed таблица, которую каждый из них обрабатывает 120 серверами.

А второй кластер — для маленьких сайтов. Здесь схема шардирования по идентификатору сайта, и каждый запрос идёт ровно на один шард.

В ClickHouse есть утилита clickhouse-copier. Можете про неё рассказать?

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

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

Например, было четыре сервера, стало восемь. Вы создаёте на всех серверах новую Distributed таблицу, новые локальные таблицы и запускаете clickhouse-copier, указывая в нём схему работы, что он должен читать оттуда, принять новую схему шардирования и перекладывать данные туда. И вам на старых серверах потребуется места в полтора раза больше, чем есть сейчас, потому что старые данные должны на них оставаться, и сверху на них приедет половинка от этих же старых данных. Если вы заранее подумали о том, что данные нужно перешардировать и место есть, то такой способ подойдёт.

Как внутри устроен clickhouse-copier? Он разбивает всю работу на набор задач по обработке одной партиции одной таблицы на одном шарде. Все эти задачи могут выполняться параллельно, и clickhouse-copier может быть запущен на разных машинах в нескольких экземплярах, но то, что он делает для одной партиции — это не что иное как insert select. Данные читаются, разжимаются, переразбиваются, потом снова сжимаются, записываются куда-то, пересортируются. Это более тяжёлое решение.

У вас была пилотная штука, которая называлась решардинг. Что с ней?

У вас была ещё в 2017 году пилотная штука, которая называлась решардинг. Даже есть опция в ClickHouse. Я так понимаю, это не взлетело. Можете рассказать, почему так получилось? Вроде бы очень даже актуально.

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

Можно ли сливать все части данных воедино перед перемещением на медленные диски?

Вопрос по TTL с опцией move to slow disk в контексте мерджей. Есть ли способ, кроме как по cron, сливать все части в одну перед перемещением на медленные диски?

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

У нас два критерия правил переноса. Первый — по мере заполнения. Если на текущем уровне хранилища меньше какого-то процента свободного места, мы выбираем один кусок и переносим его на более медленное хранилище. Вернее не более медленное, а следующее — как настроите.

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

Как переезжать на новые версии ClickHouse, если нет возможности заранее проверить совместимость?

Эта тема регулярно обсуждается в телеграм-чате ClickHouse с учётом разных версий, и всё же. Насколько безопасно обновляться с версии 19.11 на 19.16 и, например, с 19.16 на 20.3. Как лучше переезжать на новые версии, не имея возможности заранее проверить совместимость в песочнице?

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

Второе — если нет возможности проверить совместимость в песочнице, и вы хотите обновиться сразу в продакшне, рекомендация такая — не надо этого делать. Сначала создайте песочницу и проверьте. Если тестовой среды нет, то у вас, скорее всего, не очень большая компания, а значит можно скопировать часть данных на свой ноутбук и на нём убедиться, что всё корректно работает. Можно даже поднять несколько реплик локально на своей машине. Или можно где-нибудь рядышком поднять новую версию, и залить туда часть данных — то есть сделать импровизированную тестовую среду.

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

Есть версия 20.3.4. Число 20 обозначает год выпуска — 2020. С точки зрения того, что внутри, это никакого значения не имеет, так что обращать внимание на это не будем. Дальше — 20.3. Вторую цифру — в данном случае 3 — мы увеличиваем каждый раз, когда выпускаем релиз с какой-то новой функциональностью. Если мы хотим добавить в ClickHouse какую-то возможность, мы обязаны увеличить это число. То есть в версии 20.4 ClickHouse станет работать ещё лучше. Третья цифра — 20.3.4. Здесь 4 — это количество патч-релизов, в которых мы новых возможностей не добавляли, но исправляли какие-то баги. И 4 значит, что мы сделали это четыре раза.

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

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

Кирилл Шваков: Хочу дополнить немного про тестовые среды. Все очень боятся тестовых сред и почему-то считают, что, если у вас очень большой кластер ClickHouse, то и тестовая среда должна быть не меньше или хотя бы в десять раз меньше. Это совсем не так.

Могу сказать на своём примере. У меня есть проект, и там есть ClickHouse. Наша тестовая среда именно для него — это маленькая виртуалка в Hetzner за двадцать евро, где абсолютно всё развёрнуто. Чтобы это делать, у нас есть полная автоматизация в Ansible, и поэтому в принципе нет разницы, куда катить — на железные серверы или просто развернуться в виртуалках.

Что можно сделать? Было бы неплохо сделать в документации ClickHouse пример, как развернуть у себя небольшой кластер — в Docker, в LXC, возможно, создать Ansible playbook, потому что у разных людей разные деплои. Это многое упростит. Когда ты берёшь и за пять минут разворачиваешь кластер, гораздо проще пытаться в чём-то разобраться. Так гораздо удобнее, потому что катить в прод версию, которую ты не проверил — это путь в никуда. Иногда это работает, а иногда нет. И поэтому надеяться на успех — плохо.

Максим Котяков, senior backend engineer Авито: Немного дополню о тестовых средах из серии проблем больших компаний. У нас есть полноценный приёмочный кластер ClickHouse, по схемам данных и настройкам точная копия того, что есть в продакшне. Этот кластер развёрнут в достаточно жухлых контейнерах с минимумом ресурсов. Мы пишем туда некоторый процент от продакшн-данных, благо есть возможность отреплицировать в Кафке поток. Там всё синхронизировано и отскейлено — и по мощностям, и по потоку, и, в теории, при прочих равных должно по метрикам вести себя как продакшн. Всё потенциально взрывоопасное сперва катится на этот стенд и несколько дней там настаивается до готовности. Но естественно, это решение дорогое, тяжелое и с ненулевыми затратами на поддержку.

Алексей Миловидов: Расскажу, что представляет собой тестовая среда наших друзей из «Яндекс.Метрики». Один кластер был на 600 с чем-то серверов, другой на 360, и есть ещё третий и несколько кластеров. Тестовая среда для одного из них — это просто два шарда с двумя репликами в каждом. Почему два шарда? Чтобы был не один. И реплики тоже чтобы были. Просто некоторое минимальное количество, которое можно себе позволить.

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

Приведу пример. Решили установить новую версию ClickHouse. Она выложена на тестовую среду, пройдены автоматизированные тесты в самой «Яндекс.Метрике», которые сравнивают данные на старой версии и на новой, прогоняя весь конвейер. И естественно, зелёные тесты нашего CI. Иначе бы мы даже не предложили эту версию.

Всё прекрасно. Начинаем катить в продакшн. Мне приходит сообщение, что на графиках нагрузка выросла в несколько раз. Мы откатываем версию. Я смотрю на график и вижу: нагрузка действительно выросла в несколько раз во время выкатки, и обратно уменьшилась, когда выкатили. Потом мы стали откатывать версию. И нагрузка точно так же увеличилась и точно так же обратно упала. Так что вывод такой — нагрузка выросла в связи с выкладкой, ничего удивительного.

Дальше было сложно убедить коллег все-таки установить новую версию. Я говорю: «Всё нормально, выкатывайте. Держите кулачки, всё будет работать. Сейчас нагрузка выросла на графиках, но всё нормально. Держитесь». В общем, мы так сделали, и все — версия выложена на продашкн. Но чуть ли не при каждой выкладке возникают похожие проблемы.

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

Ко мне пришёл пользователь, какой-то аналитик, и создал некий запрос, который положил мой кластер ClickHouse. Какую-то ноду или кластер целиком — в зависимости от того, в какую реплику или шард запрос попал. Я вижу, что все ресурсы по CPU на этом сервере в полку, всё красное. При этом сам ClickHouse на запросы отвечает. И я пишу: «Покажи мне, пожалуйста, process list, какой запрос породил это безумие».

Я нахожу этот запрос и пишу ему kill. И вижу, что ничего не происходит. Мой сервер в полку, ClickHouse дальше отдаёт мне какие-то команды, показывает, что сервер жив, и всё здорово. Но у меня деградация во всех пользовательских запросах, начинается деградация по записи в ClickHouse, и мой kill query не отрабатывает. Почему? Я думал, что kill query должен убивать запросы, а этого не происходит.

Сейчас будет довольно странный ответ. Дело в том, что kill query не убивает запросы.

Kill query выставляет маленький флажок под названием «я хочу, чтобы этот запрос был убит». А сам запрос при обработке каждого блока смотрит на этот флажок. Если он выставлен, запрос прекращает работать. Получается, что никто запрос не убивает, он сам должен всё проверить и остановиться. И это должно работать во всех случаях, когда запрос находится в состоянии обработки блоков данных. Он обработает следующий блок данных, проверит флажок и остановится.

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

Как рассчитать время ответа при читающей нагрузке?

Есть таблица, в которой хранятся агрегаты по item — различные счетчики. Количество строк — примерно сто миллионов. Можно ли рассчитывать на предсказуемое время ответа, если налить 1K RPS по 1K item’ов?

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

Читающие запросы бывают самые разные. В select 1 ClickHouse может выполнять около десятков тысяч запросов в секунду, поэтому даже запросы по одному ключу уже потребуют некоторых ресурсов. И такие точечные запросы будут сложнее, чем в каких-нибудь key-value базах данных, потому что на каждое чтение необходимо прочитать блок данных по индексу. Индекс у нас адресует не каждую запись, а каждый диапазон. То есть придётся почитать весь диапазон — это 8192 строки по умолчанию. И придётся разжать сжатый блок данных с 64 Кб до 1 Мб. Обычно такие точечные запросы занимают от нескольких миллисекунд. Но это самый простой вариант.

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

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

Иногда, конечно, можно и ClickHouse настроить на максимальное количество точечных чтений. Что для этого нужно? Первое — уменьшить гранулированность индекса. При этом уменьшать её следует не до единицы, а из расчёта, что количество записей в индексе будет несколько миллионов или десятков миллионов на сервер. Если в таблице сто миллионов строк, то в качестве гранулированности можно выставить 64.

Можно уменьшить размер сжатого блока. Для этого есть настройки min compress block size, max compress block size. Их можно уменьшить, переналить данные, и тогда точечные запросы будут быстрее. Но всё равно ClickHouse — это не key-value база данных. Большое количество маленьких запросов — это антипаттерн нагрузки.

Кирилл Шваков: Дам совет на случай, если там обычные экаунтеры. Это достаточно стандартная ситуация, когда в ClickHouse хранят какой-то счётчик. У меня есть пользователь, он из такой-то страны, ещё какое-то третье поле, и нужно инкрементно что-то увеличивать. Берёте MySQL, делаете уникальный ключ — в MySQL он duplicate key, а в PostgreSQL он conflict — и плюсиком добавляете. Это будет работать сильно лучше.

Когда у вас немного данных, смысла использовать ClickHouse особенно нет. Есть обычные базы данных, и они хорошо с этим справляются.

Что подтюнить в ClickHouse, чтобы больше данных было в кэше?

Представим ситуацию — на серверах имеется 256 Гб ОЗУ, в ежедневной рутине ClickHouse берёт примерно 60—80 Гб, в пике — до 130. Что можно включить и подтюнить, чтобы больше данных было в кэше и, соответственно, было меньше походов на диск?

Как правило, page cache операционной системы хорошо справляется с этой задачей. Если вы открываете просто топ, смотрите там cached или free — там тоже написано, сколько закэшировано — то можно заметить, что вся свободная память использована под кэш. И эти данные при чтении будут читаться не с диска, а из оперативки. При этом я могу сказать, что кэш используется эффективно, потому что кэшируются именно сжатые данные.

Тем не менее, если вы хотите ускорить некоторые простые запросы ещё больше, есть возможность включить внутри ClickHouse кэш в разжатых данных. Это называется uncompressed cache. В конфигурационном файле config.xml выставляете uncompressed cache size в нужное вам значение — я советую не больше половины свободной оперативки, потому что остальное уйдёт под page cache.

Кроме того, есть две настройки уровня запроса. Первая настройка — use uncompressed cache — включает его использование. Её рекомендуется включать для всех запросов, кроме тяжёлых, которые могут все вообще данные прочитать и этот кэш вымыть. И вторая настройка — это что-то вроде максимального количества строк для использования кэша. Она автоматически ограничивает большие запросы, чтобы они были мимо кэша.

Как можно настроить storage_configuration для хранения в оперативке?

В новой документации ClickHouse я вычитал раздел, связанный с data storage. В описании есть пример с fast SSD.

Интересно, как можно сконфигурировать то же самое с volume hot memory. И ещё один вопрос. Как работает select с такой организацией данных, будет ли он считывать весь набор или только тот, что лежит на диске, и сжимаются ли эти данные в памяти? И как отрабатывает секция prewhere на такой организации данных?

Эта настройка влияет на хранение кусков данных, и их формат никак не меняется.
Давайте рассмотрим подробнее.

Настроить хранение данных в оперативке можно. Всё, что конфигурируется для диска — это его путь. Вы создаёте tmpfs раздел, который подмонтирован по какому-то пути в файловой системе. Указываете этот путь в качестве пути для хранения данных для самого горячего раздела, туда начинают поступать и записываться куски данных, всё хорошо.

Но я не рекомендую так делать из-за низкой надёжности, хотя, если у вас минимум три реплики в разных дата-центрах, то можно. Если что, данные будут восстановлены. Представим, что сервер вдруг выключили и включили обратно. Раздел подмонтировался снова, но там пустота. ClickHouse сервер при старте видит, что у него эти куски отсутствуют, хотя, согласно метаданным ZooKeeper они должны быть. Он смотрит, на каких репликах они есть, запрашивает их и скачивает. Таким образом данные будут восстановлены.

В этом смысле хранение данных в оперативке принципиально не отличается от их хранения на диске, потому что при записи данных на диск они тоже сначала попадают в page cache и физически записываются отложенно. Это зависит от варианта монтирования файловой системы. Но на всякий случай скажу, что ClickHouse не делает fsync при insert.

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

До какого количества уникальных значений эффективен Low Cardinality?

Low Cardinality хитро устроен. Он составляет словари данных, но они локальные. Во-первых, словари свои для каждого куска, во-вторых, даже внутри одного куска они могут быть разными для каждого диапазона. Когда количество уникальных значений достигает порогового количества — по-моему, один миллион — словарь просто откладывается, и создаётся новый.

Ответ в целом: для каждого локального диапазона — скажем, для каждого дня — где-то до миллиона уникальных значений Low Cardinality эффективен. После будет просто fallback, при котором будет использоваться много разных словарей, а не один. Работать оно будет примерно так же, как обычный столбец типа string, может быть, несколько менее эффективно, но серьёзной деградации производительности не случится.

Какие лучшие практики по полнотекстовому поиску по таблице с пятью миллиардами строк?

Есть разные варианты ответа. Первый — сказать, что ClickHouse — это не система для полнотекстового поиска. Для этого есть специальные системы например, Elasticsearch и Sphinx. Тем не менее, я всё чаще встречаю людей, которые говорят, что переходят с Elasticsearch на ClickHouse.

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

В этом случае вы в ClickHouse создаёте индекс, первым полем в котором будет дата со временем. И самое большое отсечение данных будет именно по диапазону дат. Внутри выбранного диапазона дат уже, как правило, можно осуществить полнотекстовый поиск даже брутфорс-методом с помощью like. Оператор like в ClickHouse — это самый эффективный оператор like, который вы можете найти. Если найдете лучше — скажите мне.

Но всё равно like — это full scan. И full scan может быть медленным не только по CPU, но и по диску. Если вдруг у вас один терабайт данных в сутки, и вы за сутки ищете какое-то слово, то придётся отсканировать терабайт. А он наверняка на обычных жестких дисках, и в итоге они будут загружены так, что вы на этот сервер по SSH не зайдёте.

В этом случае я готов предложить ещё один маленький трюк. Он из разряда экспериментальных — может сработает, а может нет. В ClickHouse есть полнотекстовые индексы в виде триграмных Блум-фильтров. Наши коллеги из компании Arenadata уже опробовали эти индексы, и зачастую они работают именно так, как предназначено.

Для того чтобы правильно их использовать, следует хорошо понимать, как именно они работают: что из себя представляет триграмный Блум-фильтр и как выбрать его размер. Могу сказать, что они помогут для запросов по каким-то редким фразам, подстрокам, которые редко встречаются в данных. В этом случае по индексам будут выбраны поддиапазоны, и будет прочитано меньше данных.

Недавно в ClickHouse появились ещё более продвинутые функции для полнотекстового поиска. Это, во-первых, поиск сразу кучи подстрок за один проход, в том числе варианты с учётом регистра, без учёта регистра, с поддержкой UTF-8 или только для ASCII. Выбирайте самый эффективный, который вам нужен.

Появился также поиск нескольких регулярных выражений за один проход. Вам не нужно писать X like одна подстрока or X like другая подстрока. Сразу пишете, и всё выполняется максимально эффективно.

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

Как лучше организовать доступ в ClickHouse для большого количества пользователей?

Расскажите, как лучше организовать доступ для большого количества потребителей и аналитиков. Как сформировать очередь, приоритизировать запросы max concurrent queries, и какими инструментами?

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

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

Важно посмотреть на настройки, которые связаны со всеми возможными лимитами. Если я сейчас зайду на кластер «Яндекс.Метрики» как аналитик и задам запрос select count from hits, то мне сразу же выдадут исключение, что выполнить запрос я не могу. Максимальное количество строк, которое мне разрешено сканировать, — это сто миллиардов, а всего на кластере их пятьдесят триллионов в одной таблице. Это первое ограничение.

Допустим, я уберу ограничение по количеству строк, и выполню запрос повторно. Тогда я увижу следующее исключение — включена настройка force index by date. Я не могу выполнить запрос, если я не указал диапазон дат. Не нужно рассчитывать на то, что аналитики будут указывать его вручную. Типичный случай — написан диапазон дат where event date between неделя. А потом просто не там указали скобку, и вместо and получился or — or URL match. Если ограничения нет, оно пойдет сканировать столбец URL и потратит просто тонну ресурсов.

Кроме того, в ClickHouse есть две настройки приоритетов. К сожалению, они очень примитивные. Одна называется просто priority. Если priority ≠ 0, и выполняются запросы с каким-то приоритетом, но при этом выполняется запрос с приоритетом, который имеет значение меньше, что значит более высокий приоритет, то запрос со значением приоритета больше, что обозначает более низкий приоритет, просто приостанавливается и не будет работать вообще за это время.

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

Следующая настройка приоритетов называется OS thread priority. Она просто выставляет для всех потоков выполнения запроса величину nice для шедулера Linux. Работает она так себе, но всё-таки работает. Если выставить самое минимальное значение nice — оно самое большое по величине, и значит самый низкий приоритет — а у запросов с высоким приоритетом выставить -19, то CPU будет потреблять низкоприоритетные запросы примерно в четыре раза меньше, чем высокоприоритетные.

Ещё нужно настроить максимальное время выполнения запроса — скажем, пять минут. Минимальную скорость выполнения запроса — это самое крутое. Эта настройка есть давно, и она требуется, чтобы не просто утверждать, что ClickHouse не тормозит, а чтобы это форсировать.

Представьте, вы настраиваете: если какой-то запрос обрабатывает меньше одного миллиона строк в секунду — так делать нельзя. Это позорит наше доброе имя, нашу добрую базу данных. Давайте это просто запретим. Там на самом деле две настройки. Одна называется min execution speed — в строчках в секунду, а вторая называется timeout before checking min execution speed — по умолчанию пятнадцать секунд. То есть пятнадцать секунд можно, а потом, если медленно, то просто кинуть исключение — прервать запрос.

Ещё нужно настроить квоты. В ClickHouse есть встроенная возможность квот, которая считает потребление ресурсов. Но, к сожалению, не железных ресурсов типа CPU, дисков, а логических — количество обработанных запросов, строк и прочитанных байт. И можно настроить, например, максимум сто запросов в течение пяти минут и тысячу запросов в час.

Почему это важно? Потому что часть запросов аналитики будут выполнять вручную прямо из ClickHouse клиента. И всё будет хорошо. Но если у вас в компании продвинутые аналитики, они напишут скрипт, а в скрипте может быть ошибка. И эта ошибка приведет к тому, что запрос выполнится в бесконечном цикле. От этого и надо защититься.

Можно ли отдать результаты одного запроса десяти клиентам?

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

Проблема в том, что именно результатов кэша или кэша промежуточных данных у нас нет. Есть page cache операционной системы, который позволит не читать данные с диска повторно, но, к сожалению, данные всё равно будут разжиматься, десериализовываться и обрабатываться повторно.

Хотелось бы каким-то образом этого избежать, либо кэшируя промежуточные данные, либо выстроив похожие запросы в какую-то очередь и добавив кэш результатов. Сейчас у нас есть в разработке один pull request, который добавляет кэш запросов, но только для подзапросов в секции in и join — то есть решение неполноценное.

Тем не менее, у нас тоже возникает такая ситуация. Особенно канонический пример — это запросы с пагинацией. Есть отчёт, в нём есть несколько страниц, и идёт запрос limit 10. Потом то же самое, но limit 10,10. Затем ещё следующая страница. И спрашивается, зачем мы каждый раз это всё считаем? Но сейчас решения нет, и избежать этого никак нельзя.

Есть альтернативное решение, которое ставится сайдкаром рядом с ClickHouse — ClickHouse Proxy.

Кирилл Шваков: В ClickHouse Proxy есть встроенный рейт лимитер и встроенный кэш результатов. Там сделано очень много настроек, потому что решалась схожая задача. Proxy позволяет лимитировать запросы, выстраивая их в очередь, и настраивать, сколько по времени живёт кэш запросов. Если запросы были действительно одинаковые, Proxy отдаст их много раз, а сходит в ClickHouse всего однажды.

В Nginx тоже есть кэш в бесплатной версии, и это тоже будет работать. У Nginx даже есть настройки, что, если запросы пришли одновременно, он будет тормозить другие, пока один не выполнится. Но именно в ClickHouse Proxy настройка сделана гораздо лучше. Он делался именно под ClickHouse, именно под эти запросы, поэтому больше подходит. Ну и ставится просто.

Как быть с асинхронными операциями и материализованными представлениями?

Есть такая проблема, что операции с реплейсинг-движком асинхронны — сперва записываются данные, потом происходит их схлопывание. Если под табличкой живёт материализованная табличка с какими-то агрегатами, то дубли в неё запишутся. И если нет какой-то сложной логики, то данные будут задублированы. Что можно с этим сделать?

Есть очевидное решение — реализовать триггер на определённый класс матвьюх при асинхронной операции схлопывания. Есть ли какие-нибудь «серебряные пули», планы по реализации подобных функциональностей?

Стоит разобраться с тем, как работает дедупликация. То, о чём я сейчас расскажу, не относится к вопросу, но на всякий случай стоит об этом вспомнить.

При вставке в реплицированную таблицу есть дедупликация целиком вставленных блоков. Если вы повторно вставили один и тот же блок, содержащий такое же количество тех же самых строк в том же самом порядке, то данные дедуплицируются. Вы получите “Ok” в ответ на insert, но фактически будет записана одна пачка данных, и она продублирована не будет.

Это нужно для определённости. Если во время вставки вы получили “Ok”, значит ваши данные вставлены. Если вы получили ошибку от ClickHouse, значит они не вставлены, и нужно вставку повторить. Но если во время вставки разорвалось соединение, то вы не знаете, вставлены данные или нет. Единственный вариант — это повторить вставку снова. Если данные на самом деле были вставлены, и вы их вставили повторно, есть дедубликация блоков. Она нужна во избежание дубликатов.

И важно ещё, как она работает для материализованных представлений. Если данные были дедуплицированы при вставке в основную таблицу, то в материализованное представление они тоже не пойдут.

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

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

Кирилл Шваков: У нас тоже костылестроение в своё время ещё то было. Была проблема, что есть показы рекламы, и есть некоторые данные, которые мы можем показывать в реалтайме — это просто показы. Они редко когда дублируются, но, если такое происходит, мы их потом всё равно схлопнем. И были вещи, которые дублировать нельзя — клики и вся эта история. Но показывать тоже их хотелось практически сразу.

Как были сделаны материализованные представления? Были представления, куда пишется напрямую — идёт запись в сырые данные, и пишется во вьюшки. Там в какой-то момент данные не очень правильные, они дублируются и так далее. И есть вторая часть таблицы, где они абсолютно так же выглядят, как и материализованные представления, то есть по структуре они абсолютно одинаковые. Раз в какое-то время мы пересчитываем данные, досчитываем данные без дубликатов, пишем в те таблицы.

Мы ходили через API — в ClickHouse руками это работать не будет. И API смотрит: когда у меня есть дата последнего добавления в таблицу, где гарантированно уже правильные данные, посчитанные, и он делает запрос к одной таблице и к другой таблице. Из одной запрос выбирает до определённого количества времени, и из другой добирает то, что ещё не посчитано. И это работает, но не средствами одного ClickHouse.

Если у вас API какое-то есть — для аналитиков, для пользователей — то в принципе, это вариант. Вы всегда досчитываете, всегда пересчитываете. Это можно делать раз в сутки или ещё в какое-то время. Вы сами для себя выбираете диапазон, который вам не нужен и не критичен.

В ClickHouse много логов. Как я могу видеть всё, что происходит с сервером, в моменте?

В ClickHouse очень большое количество различных логов, и это количество повышается. В новых версиях какие-то из них даже по умолчанию включены, в старых версиях их надо включать при обновлении. Тем не менее, их всё больше и больше. Хотелось бы видеть в конечном счёте, что происходит сейчас у меня с сервером, может быть, на каком-то сводном дашборде.

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

Дашборды есть, правда они не стандартизированные. У нас в компании где-то 60 команд используют ClickHouse, и самое странное, что у многих из них дашборды, которые они сами себе сделали, и чуть-чуть другие. Некоторые команды используют внутреннюю инсталляцию «Яндекс.Облака». Там есть некоторые готовые отчеты, хотя и не все необходимые. У других своё.

У моих коллег из «Метрики» есть свой дашборд в Grafana, а у меня есть свой по их же кластеру. Я там смотрю штуки типа кэш хит для кэша засечек. И даже ещё сложнее то, что мы используем разные инструменты. Свой дашборд я создавал на очень старом инструменте, который называется Graphite-web. Он совершенно некрасивый. И я так до сих пор им и пользуюсь, хотя Grafana, наверное, была бы удобнее и красивее.

Базовая вещь в дашбордах одинаковая. Это системные метрики по кластеру: CPU, память, диск, сеть. Другие — количество одновременных запросов, количество одновременных мерджей, количество запросов в секунду, максимальное количество кусков для партиций таблиц MergeTree, лаг репликации, размер очереди репликации, количество вставленных строк в секунду, количество вставленных блоков в секунду. Это всё, что получается не из логов, а из метрик.

Владимир Колобаев: Алексей, я хотел бы немножко скорректировать. Есть Grafana. У Grafana есть datasource, которым является ClickHouse. То есть я могу из Grafana делать запросы сразу в ClickHouse. В ClickHouse есть таблица с логами, она у всех одинаковая. Я хочу в результате в Grafana обращаться к этой таблице логов и видеть те запросы, которые прикладывает мой сервер. Было бы здорово иметь такой дашборд.

Я его навелосипедил сам. Но у меня возникает вопрос — если оно всё стандартизированное, и Grafana используется всеми подряд, почему в «Яндексе» нет такого официального дашборда?

Кирилл Шваков: На самом деле datasource, который к ClickHouse, сейчас поддерживает Altinity. И я просто хочу дать вектор, куда копать и кого толкать. Можно спросить с них, потому что «Яндекс» всё-таки делает ClickHouse, а не историю вокруг него. Altinity — это основная компания, которая сейчас продвигает ClickHouse. Они его не бросят, а будут поддерживать. Потому что в принципе, чтобы загрузить дашборд на сайт Grafana, нужно только зарегистрироваться и залить его — проблем особых нет.

Алексей Миловидов: За последний год в ClickHouse добавилось много возможностей по профилированию запросов. Есть метрики для каждого запроса по использованию ресурсов. А совсем недавно добавлен ещё более низкоуровневый профайлер запросов, чтобы смотреть, где запрос проводит каждую миллисекунду. Но чтобы воспользоваться этой функциональностью, мне приходится открыть консольный клиент и набрать запрос, который я постоянно забываю. Я его куда-то сохранил и постоянно забываю, куда именно.

Я бы хотел, чтобы был инструмент, в котором просто написано — вот ваши тяжёлые запросы, сгруппированные по классам запросов. Нажал на какой-нибудь, и мне бы сказали, что он тяжёлый поэтому. Сейчас такого решения нет. И действительно довольно странно, что, когда люди меня спрашивают: «Скажите, есть какие-нибудь готовые дашборды для Grafana?», я говорю: «Зайдите на сайт Grafana, там комьюнити “Дашборды”, и там есть дашборд от Димка, есть дашборд от Костяна. Что это такое, я не знаю, я сам не пользовался».

Как воздействовать на мерджи, чтобы сервер не падал в OOM?

У меня есть таблица, в таблице всего одна партиция, она ReplacingMergeTree. Я в неё пишу данные в течение четырёх лет. Мне надо было сделать в неё альтер и удалить кое-какие данные.

Я это сделал, и в ходе обработки этого запроса была съедена вся память на всех серверах кластера, и все серверы кластера дружно ушли в OOM. Потом они все вместе встали, начали выполнять мердж этой же самой операции, этого блока данных, и снова упали в OOM. Потом они снова встали и снова упали. И эта штука не прекращалась.

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

У меня есть таблица, которая называется «Метрики», обрабатывай мне её, пожалуйста, в два потока. Не надо плодить десять или пять мерджей параллельно, сделай в два. Я думаю, что в два у меня памяти хватит, а на то, чтобы десять обрабатывать, может не хватить. Почему страх остаётся? Потому что таблица растёт, и когда-нибудь я столкнусь с ситуацией, что в принципе уже не из-за бага, а из-за того, что данные будут меняться в таком большом количестве, что мне просто памяти на сервере не хватит. И тогда сервер будет падать в OOM при мердже. Причем мутацию я могу отменить, а мерджи уже нет.

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

Владимир Колобаев: Хорошо. Тут момент такой, что после того, как сделали баг фикс, я скачал себе новую версию, и на другой таблице, более маленькой, где партиций много, сделал подобную операцию. И в ходе мерджа на сервере было сожжено порядка 100 Гб оперативной памяти. У меня было 150 занято, 100 съело, и осталось окно на 50 Гб, поэтому я в OOM не упал.

Что меня на данный момент защищает от того, чтобы я не падал в OOM, если он действительно потребляет по 100 Гб оперативной памяти? Как быть в ситуации, если вдруг оперативная память на мерджах закончится?

Алексей Миловидов: Есть такая проблема, что расход оперативной памяти именно на мерджи не ограничивается. И вторая проблема состоит в том, что, если какой-то мердж был назначен, то его необходимо выполнить, потому что он записан в лог репликации. Лог репликации — это те действия, которые нужны, чтобы привести реплику в консистентное состояние. Если не сделать ручные манипуляции, которые этот лог репликации откатит назад, мердж придётся, так или иначе, выполнить.

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

Как будет происходить разработка Golang-драйвера для ClickHouse?

Golang-драйвер, который написал Кирилл Шваков, сейчас официально вроде как поддерживается командой ClickHouse. Он в репозитории ClickHouse, он теперь большой и настоящий.

Небольшая ремарка. Есть замечательное и всеми любимое хранилище нормальных форм бесконечного порядка — это Vertica. У них тоже есть свой официальный python-драйвер, который поддерживается разработчиками Vertica. И несколько раз было такое, что версии хранилища и версии драйвера весьма круто разъезжались, и драйвер в какой-то момент переставал работать. И второй момент. Поддержка этого официального драйвера, сдается мне, ведётся системе «ниппель» — ты пишешь им issue, и оно висит вечно.

У меня два вопроса. Сейчас Golang-драйвер Кирилла — это почти дефолтный способ коммуницировать из Golang с ClickHouse. Разве что кто-то через http интерфейс коммуницирует до сих пор, потому что ему так нравится. Как будет происходить разработка этого драйвера? Будет ли она синхронизироваться с какими-то breaking changes в самом хранилище? И каков порядок рассмотрения issue?

Кирилл Шваков: Первое — как всё устроено бюрократически. Этот момент не обсуждался, поэтому ответить мне нечего.

Чтобы ответить на вопрос про issue, нужна небольшая история драйвера. Я работал в компании, в которой было много данных. Это была рекламная крутилка с огромным количеством событий, которые нужно было где-то хранить. И в какой-то момент появился ClickHouse. Мы налили туда данные, и первое время всё было хорошо, а потом ClickHouse упал. На тот момент мы решили, что оно нам не надо.

Через год мы вернулись к идее использования ClickHouse, и нам нужно было как-то туда писать данные. Вводная была такая — железо очень слабое, ресурсов мало. Но мы всегда так работали, и поэтому посмотрели в сторону нативного протокола.

Так как мы работали на Go, было понятно, что нужен драйвер на Go. Я делал его практически фултайм — это была моя рабочая задача. До какого-то момента мы его довели, и в принципе никто не предполагал, что кто-то кроме нас будет его использовать. Потом пришёл CloudFlare ровно с такой же проблемой, и в какое-то время мы с ними работали очень ровно, потому что у них были одни и те же задачи. Причём мы это делали и в ClickHouse сам, и в драйвер.

В какой-то момент я просто перестал им заниматься, потому что моя активность с точки зрения ClickHouse и с работой немножко поменялась. Поэтому issues не закрываются. Периодически в репозиторий коммитят люди, которым сами что-то нужно. Тогда я смотрю pull request и иногда даже что-то сам правлю, но это случается редко.

К драйверу хочется вернуться. Несколько лет назад, когда всё это дело начиналось, ClickHouse был тоже другой и с другими возможностями. Сейчас же есть понимание, как переделать драйвер, чтобы было хорошо. Если это произойдёт, то версия 2 будет в любом случае несовместима из-за накопленных костылей.

Как это дело организовать, я не знаю. У меня самого времени не так много. Если какие-то люди будут допиливать драйвер, я смогу им помогать и рассказывать, что делать. Но именно активное участие «Яндекса» в развитии проекта пока никак не обсуждалось.

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

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

Внешний словарь не поднимается после перезагрузки с включенной настройкой lazy_load. Что делать?

У нас включена настройка lazy_load, и после перезагрузки сервера словарь сам не поднимается. Он поднимается только после того, как пользователь обратится к этому словарю. И при первом обращении выдаёт ошибку. Можно ли как-то автоматически средствами ClickHouse загружать словари, или нужно самим всегда контролировать их готовность, чтобы пользователи не получали ошибок?

Возможно у нас старая версия ClickHouse, поэтому словарь автоматически не загружался. Может быть такое?

Во-первых, словари можно форсированно загрузить с помощью запроса system reload dictionaries. Во-вторых, по поводу ошибки — если словарь уже загружен, то запросы будут работать по тем данным, которые были загружены. Если словарь ещё не был загружен, то он будет загружаться прямо во время запроса.

Для тяжёлых словарей это не очень удобно. Например, нужно притянуть миллион строк из MySQL. Кто-то делает простой select, но этот select будет ждать тот самый миллиона строк. Здесь есть два решения. Первое — выключить lazy_load. Второе — когда сервер поднимается, перед тем, как включить на него нагрузку, сделать system reload dictionary или просто выполнить запрос, который использует словарь. Тогда словарь будет загружен. Нужно самим контролировать доступность словарей со включенной настройкой lazy_load, потому что автоматом ClickHouse их не подтягивает.

На последний вопрос ответ — то ли версия старая, то ли надо отлаживать.

Как быть с тем, что system reload dictionaries не подгружает ни один из множества словарей, если хотя бы один из них падает с ошибкой?

Есть ещё вопрос по поводу system reload dictionaries. У нас два словаря — один не прогружается, второй прогружается. System reload dictionaries в таком случае не подгружает ни один словарь, и приходится точечно подгружать конкретный по его названию c помощью system reload dictionary. Это тоже связано с версией ClickHouse?

Хочу обрадовать. Это поведение менялось. Значит, если вы обновите ClickHouse, то оно тоже поменяется. Если вас не устраивает текущее поведение system reload dictionaries, обновитесь, и будем надеяться, что оно изменится в лучшую сторону.

Есть ли способ конфигурировать реквизиты в конфиге ClickHouse, но не светить их при ошибках?

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

Мы решили эту ошибку вынесением реквизитов в конфиг драйвера ODBC. Есть ли какой-то способ сконфигурировать реквизиты в конфиге ClickHouse, но не светить эти реквизиты при ошибках?

Здесь решение действительно — указывать эти credentials в odbc.ini, а в самом ClickHouse указывать только ODBC Data Source Name. Для остальных источников словарей такого не будет — ни для словаря с MySQL, ни для остальных вы не должны видеть пароль при сообщении об ошибке. Для ODBC я тоже посмотрю — если такое есть, надо это просто убрать.

Бонус: фоны для Зума с посиделок

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

ClickHouse для продвинутых пользователей в вопросах и ответах

Источник: habr.com

Добавить комментарий