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

На момент написания статьи хранилищем и результатами его работы пользуются 16+ команд (11+ аналитиков и 2 data scientist, 70+ разработчиков, руководители и менеджерский состав).

Ежесуточно в хранилище поступает ~1,2 ТБ данных, пользователи и автоматика для построения отчётности генерируют ~35 000 запросов в сутки на выборки различной сложности. Подробнее про наше хранилище и то, какие задачи для бизнеса им решаем, можно почитать по ссылке.

Мы прошли путь от СlickНouse as a service у облачного провайдера к своей инсталляции, и до недавнего времени у нас уже более года существовал кластер ClickHouse из пяти нод без репликации и был развёрнут на гиперконвергентной инфраструктуре с гибридными дисками (SSD в качестве кеша + HDD), у инсталляции был ряд проблем:

  1. Был риск потери данных в случае выхода из строя одной из нод.
  2. Отсутствовала удобная схема репликации данных.
  3. Непредсказуемое поведение по производительности дисков и влияние на другие виртуальные машины, развёрнутые на кластере, нужна была большая изоляция аналитической нагрузки от других сервисов компании.


Защита от риска потери данных


Большинство данных, которые хранились в ClickHouse, мы в случае потерь могли восстановить из других источников (OLTP-баз данных или S3), но это потребовало бы значительного времени, по нашим оценкам, около 1 недели, потому как при потере одного сервера из кластера нужно было бы восстанавливать весь объём данных, что конечно же ни нас, ни бизнес не устроило.

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

Поэтому было решено использовать механизмы репликации в ClickHouse, которые имеют существенные сложности в использовании, с точки зрения разработчика, если вы привыкли к зрелым RDBMS, таким как PostgreSQL и MySQL.

Проблема производительности и ресурсов


Аналитическая нагрузка от ClickHouse мешала работе сервисов, которые чувствительны к latency, поэтому было принято решение, сразу выносить в отдельный контур на отдельные сервера. Изоляции, которая предлагалась на основе гиперконвергентной инфраструктуры, нам было недостаточно. Не хватало производительности гибридных дисков (SSD + HDD), хотели перейти на SSD only.

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

Выбор и реализация


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

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

Мы заранее понимали, что у нас экспертов по ClickHouse будет один-два, а пользователей будет 40-50. Мы хотели изолировать знания об инфраструктуре, кластере и его топологии от обычных пользователей. Если нужно просто создать табличку — вот вам простая команда, вы её делаете через SQL и не знаете, как кластер в динамике меняется. Классические базы данных изолируют этот уровень и дают удобный инструментарий, чтобы отделить роль человека, который поддерживает инфраструктуру и пользуется инфраструктурой. Вся нужная информация есть в документации, но из неё сложно вытаскивать точечно куски — потому что документации очень много, а не потому что она плохая. И документация предполагает, что ты — эксперт, через пару неделек ковыряния можно выйти со знанием. Но наши пользователи не должны так делать. Для них у нас есть инструкция, которую можно впитать за 1 час, и работать спокойно.

И тут у ClickHouse есть две фишки, которые решают эти потребности.

  1. Использование крутого механизма ON CLUSTER, который развивается и во многом абстрагирует пользователя от понимания топологии кластера. Например, при создании таблиц не нужно самому выполнять запрос на всех нодах кластера.
  2. Использование distributed-таблиц, когда пользователь может обращаться к одной таблице на любой из нод кластера и получать все необходимые данные по этой таблице со всего кластера, так как под капотом ClickHouse сам всё сделает.


Смотрим хорошие практики


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

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



У нас уже был опыт использования такого подхода и с ним были неудобства.

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

    Выглядеть будет примерно так:
    • Shard #1
      • db_shard_1
      • db_shard_2

    • Shard #2
      • db_shard_2
      • db_shard_3

    • Shard #3
      • db_shard_3
      • db_shard_1


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

    Пример запросов создания таблиц для Shard #1:
    CREATE TABLE db_shard_1.test_table_shard
    (
        id UInt32,
        name String,
        cdate DateTime
    )
    ENGINE ReplicatedMergeTree('/clickhouse/tables/db_shard_1/test_table_shard', 'replica_1')
    ORDER BY (id)
    PARTITION BY (cdate);
     
    CREATE TABLE db_shard_2.test_table_shard
    (
        id UInt32,
        name String,
        cdate DateTime
    )
    ENGINE ReplicatedMergeTree('/clickhouse/tables/db_shard_2/test_table_shard', 'replica_2')
    ORDER BY (id)
    PARTITION BY (cdate);
    

    Минус такого подхода ещё в том, что все созданные реплицируемые таблицы в кластере будут находиться только в определённых БД. Нет возможности ограничить права доступа к определённым базам.
  2. Для создания distributed-таблицы поверх реплицируемых таблиц в качестве параметра конфигурации нужно указать название базы и таблицы, которые должны быть одинаковыми на всех нодах. Чтобы это заработало, нужно определить дефолтную базу данных для каждой ноды в шарде, сделать это можно добавив в конфигурацию каждой ноды параметр <default_database>...</default_database>.

    После определения default_database, при создании distributed-таблицы можно не указывать название базы, так как значение подставится из конфига.
    CREATE TABLE default.test_table
    (
        id UInt32,
        name String,
        cdate DateTime
    )
    ENGINE = Distributed('test_cluster', '', 'test_table_shard', rand());
    

  3. При создании таблиц можно использовать макросы с определёнными значениями для каждой ноды.

    • Макросы, добавленные разработчиками:
      • {database} — раскрывается в имя базы данных.
      • {table} — имя таблицы.

    • Макросы, добавленные нами (администраторами хранилища) в конфиг:
      • {shard} — идентификатор шарда.
      • {replica} — имя реплики.


    Такие макросы удобно использовать для создания таблиц с использованием ON CLUSTER, можно выполнить один запрос, и на всех нодах будут созданы необходимые таблицы. Но при круговой репликации есть проблема, так как таблицы лежат в разных базах, предположительно, такой запрос должен создать все необходимые таблицы:
    CREATE TABLE test_table_shard
    (
        id UInt32,
        name String,
        cdate DateTime
    )
    ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/{shard}/{table}', '{replica}')
    ORDER BY (id)
    PARTITION BY (cdate);
    

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

    Code: 371, e.displayText() = DB::Exception: For a distributed DDL on circular replicated cluster its table name must be qualified by database name. (version 21.4.3.21 (official build))

    Можно, конечно, выполнить запрос на каждой паре реплицируемых нод, но для этого нужно знать топологию кластера.
  4. Для восстановления ноды требуется много ручных действий. Основное неудобство заключается в том, что нет возможности вытащить актуальные метаданные таблиц из /var/lib/clickhouse/metadata/с соседней ноды, так как нет ещё одной ноды, на которой были бы созданы точно такие же таблицы. Приходится смотреть конфигурацию и в соответствии с ней править метаданные для новой ноды.
  5. При добавлении новой ноды нужно будет вручную перенести реплики, чтобы не было ситуации, что две реплики находятся на одном сервере. Получится, что реплика для Shard #3 переедет с Node #1 на Node #4.

    Есть сложность ещё и с решардингом партиций, так как ClickHouse не предоставляет это в автоматическом режиме. Для этих целей можно воспользоваться утилитой СlickНouse-copier (её уже немного упоминали на Habr: один, два). Есть ещё один подход, немного о нём ниже.


Наша конфигурация


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



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

  1. Для каждой таблицы или нескольких таблиц можно создавать свои базы. Помимо того, что становится удобнее с ними работать, появляется возможность ограничивать права доступа доступа к определённым базам.
  2. При использовании ON CLUSTER таблицы будут созданы сразу на всех нодах кластера.

    Запрос создания реплицируемой таблицы выглядит так:
    CREATE TABLE test_database.test_table_shard ON CLUSTER test_cluster
    (
        id UInt32,
        name String,
        cdate DateTime
    )
    ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/{shard}/{table}', '{replica}')
    ORDER BY (id)
    PARTITION BY (cdate);
    

    Макросы, используемые в запросе выше, на одной из нод:
    • {shard} = 1.
    • {replica} = node-1.


    С distributed-таблицей  аналогичная ситуация:
    CREATE TABLE test_database.test_table ON CLUSTER test_cluster
    (
        id UInt32,
        name String,
        cdate DateTime
    )
    ENGINE = Distributed('test_cluster', 'test_database', 'test_table_shard', rand());
    

  3. Для восстановления ноды нам нужно сконфигурировать сервер и перенести на него метаданные (/var/lib/clickhouse/metadata/) с оставшейся живой реплики. Запустить ClickHouse, дальше он сам создаст все необходимые базы, таблицы и зальёт данные.
  4. При добавлении новой ноды нам не нужно будет переносить отдельно реплики таблиц, мы можем просто погасить одну виртуальную машину и перенести её на новый сервер. Получится, что виртуальная машина Node #4 переедет с Hardware server #1 на #4.



В дополнение получили увеличение скорости работы за счёт SSD only, обновились с версии 20.9.3.45 на 21.4.3.21 и получили новые фичи.

После переезда на SSD бекап также стал работать стабильнее.

Что хотим ещё


  • Стабилизировать бекап.
  • Добавить аутентификацию через AD.
  • Обновиться на самую свежую версию и наладить регулярные обновления.


В соседнем посте мой коллега рассказывает про то, как мы живём с ClickHouse и чем он стал для нас.

А еще у нас открыты вакансии и мы ищем к нам в команду классного DevOps на развитие этой инфраструктуры.

Комментарии (13)


  1. amarao
    09.09.2021 11:38
    +2

    По поводу виртуальных машин. Почему вы решили использовать виртуальные машины, а не отдельные инстансы clickhouse (или даже контейнеры) со своим IP-адресом? В общем случае миграция инстанса виртуалки (с данными) и миграция каталога с данными между двумя серверами практически одно и то же, но меньше слоёв операционных систем.


    1. Atervita Автор
      09.09.2021 12:13

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


      1. blind_oracle
        10.09.2021 10:39

        Он имел в виду запустить два Кликхауса на одной ОС с разными конфигами, IP и данными в разных директориях. В данном случае не нужно рулить дополнительной ОС и платить цену виртуализации.

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


        1. Atervita Автор
          10.09.2021 15:09

          Да, не так понял вопрос…

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

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

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


    1. oller
      26.09.2021 23:30

      в моих тестах с 2tb пожатых данных виртуализация давала +20% скорости итогового просчета, найти секрет такого непонятного тюнинга я не смог в короткое время

      Для понимания упираюсь только в процессор, диски m2 в простое


  1. DurRandir
    09.09.2021 18:22

    А 1.2 тб — это сырых или пожатых данных? Если пожатых — то сколько ж там ссд, если это хранится хоть сколько-то долго?

    Одним из больших минусов что дефолтной круговой, что вашей, схемы я вижу кросс влияние шардов друг на друга — запись на шард 2 тормозит чтение на шарде 1. А учитывая то, что клик считает (и это в плане дизайна вполне разумно) что он на машине один и все ресурсы его — на hdd я бы такое точно не стал делать, а на ssd не чувствуете эффекта конкуренции между шардами?


    1. Atervita Автор
      09.09.2021 19:49

      1.2 Tб сырых данных, на одну ноду/шард приходится ~4Тб SSD.

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

      Про размещение на HDD я с Вами полностью согласен. Прошлая инсталляция как раз и жила на HDD с SSD кешем и там были замечены странности и влияние на другие виртуалки. Пришлось добавлять лимиты по IOps, что конечно сыграло не в лучшую сторону для ClickHouse. С SSD были опасения, что можем упереться, но пошли на этот шаг, на данный момент эффекта не заметили. У нас есть понимание как можно расширить кластер, увеличить ему ресурсов и в дальнейшем переехать на отдельные инстансы. Скорее сейчас ловим какие-то другие проблемы из-за увеличения объема данных и количества запросов, но вот в SSD пока не упираемся.


      1. blind_oracle
        10.09.2021 10:46

        Дисковая нагрузка КХ в основном последовательна и большой разницы между HDD и SSD я не видел.

        Думается что с быстрыми NVMe SSD КХ упирается в процессор на распаковке партов из LZ4/ZSTD а не в диск уже... у LZ4 вроде около нескольких Гб/с на ядро. И 500мб на сжатие, а SSD уже умеют в 5-7Гб/с


  1. barloc
    10.09.2021 08:17

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


    1. blind_oracle
      10.09.2021 10:42

      Не все используют и любят k8s


      1. Atervita Автор
        10.09.2021 15:20

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


  1. felix0id
    10.09.2021 11:11
    +1

    Хотел спросить, зачем сделано по несколько виртуалок, но понял, что @blind_oracleуже указал на альтернативный способ управления, который кажется более приемлимым, когда несколько инстансов CH запущено с разными --config. Так действительно выглядит более гибко, если один из шардов случайно начал перевешивать по нагрузке.

    /*offtopic*/ Когда зашёл прокомментировать, а тут уже все знакомые лица...


  1. WortexLinux
    07.10.2021 14:18

    Хочу поинтересоваться, возможно что-то не допонимаю. А RAID1 на нодах не решал вопрос сохранности и быстрого восстановления в случае выхода ноды из строя?