Основы ClickHouse для веб разработчика

Spread the love

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

Итак ClickHouse – это распределенная аналитическая столбцовая (column-oriented) СУБД. Это open-source проект созданный в Яндекс и распространяется под лицензией Apache 2.0 Официальный сайт ClickHouse

Отличительные особенности ClickHouse:

  • По настоящему столбцовая хранилище
  • Векторизация выполнения запросов
  • Сжатие данных
  • Параллельное и распределенное выполнение запросов
  • Обработка запросов в реальном времени
  • Запись данных в реальном времени
  • Репликация между центрами данных
  • Высокая доступность
  • Высокая скорость работы с большим объемом данных
  • Поддержка SQL
  • Подключаемые внешние таблицы
  • Полная поддержка IPv6
  • Ориентированная на веб-аналитику
  • Современные алгоритмы
  • Подробная документация
  • Код открыт и с ним можно делать все что нужно.
  • Цена ClickHouse это по сути цена разработчиков.

Для чего используется ClickHouse?

  • Аналитика веб приложений
  • Рекламные компании
  • Анализ операционных логов
  • Мониторинг логов безопасности
  • Финансовый анализ
  • Аналитика DNS и HTTP запросов
  • Биллинг системы
  • Мониторинг производственных процессов
  • Блокчейн аналитика
  • и т.п.

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

И так, первый момент с которого обычно начинают изучать ClickHouse это разъяснение что такое “строковые” СУБД и чем они отличаются от “обычных”.

В обычной, “строковой” СУБД, данные хранятся в таком порядке:

СтрокаWatchIDJavaEnableTitleGoodEventEventTime
#08935431Investor Relations12016-05-18 05:19:20
#19032950Contact us12016-05-18 08:10:20
#28995371Mission12016-05-18 07:38:00
#N

То есть, значения, относящиеся к одной строке, физически хранятся рядом.

Примеры строковых СУБД: MySQL, Postgres, MS SQL Server и т.д..

В столбцовых СУБД, данные хранятся по столбцам в таком порядке:

Строка:#0#1#2#N
WatchID:893543903295899537
JavaEnable:101
Title:Investor RelationsContact usMission
GoodEvent:111
EventTime:2016-05-18 05:19:202016-05-18 08:10:202016-05-18 07:38:00

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

Примеры столбцовых СУБД: Vertica, Paraccel (Actian Matrix, Amazon Redshift), Sybase IQ, Exasol, Infobright, InfiniDB, MonetDB (VectorWise, Actian Vector), LucidDB, SAP HANA, Google Dremel, Google PowerDrill, Druid, kdb+.

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

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

Столбцовые СУБД лучше (от 100 раз по скорости обработки большинства запросов) подходят для описанного выше сценария работы. Причины в деталях будут разъяснены ниже, а сам факт проще продемонстрировать визуально.

Строковые СУБД

Есть таблица в ней какие то столбцы и данные хранятся по строчно. При чтение считывается одна строка потом другая и так далее. Это хорошее решение в общем случае.

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

Столбцовые СУБД

Видите разницу?

По вводу-выводу

  1. Для выполнения аналитического запроса, требуется прочитать небольшое количество столбцов таблицы. В столбцовой БД для этого можно читать только нужные данные. Например, если вам требуется только 5 столбцов из 100, то следует рассчитывать на 20-кратное уменьшение ввода-вывода.
  2. Так как данные читаются пачками, то их проще сжимать. Данные, лежащие по столбцам также лучше сжимаются. За счёт этого, дополнительно уменьшается объём ввода-вывода.
  3. За счёт уменьшения ввода-вывода, больше данных влезает в системный кэш.

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

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

Движки баз данных и движки таблиц

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

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

Движок таблицы (тип таблицы) определяет:

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

Существует множество различных модулей движков таблиц: семейство MergeTree, Log, движки для интергации.

MergeTree

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

Движки семейства:

Log

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

Движки семейства:

Движки для интергации

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

Движки семейства:

Специальные движки

Движки семейства:

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

Как хранятся данные в Merge Tree

В начале опишем проблему которую решает этот алгоритм на простом обобщенном примере.

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

Для доступа к данных нам нужно будет или считывать каждый раз этот файл целиком или использовать индекс (ключ), который позволит при запросе считывать не все данные а только нужные. Для реализации ключа обычно используют B-дерево. Допустим в качестве ключа мы выбрали id сайта из получаемых данных.

1 – сайт aaa.com
2 – сайт bbb.com
3 – сайт ccc.com

Соотвественно b дерево должно будет реализовывать отображение id сайта в смещение на диске.

В итоге у нас будет получаться подобная структура:

1111111 2222222 3333333 44444444

При этом если будут выполнятся команды UPDATE и DELETE то в файле будут образовываться дыры. И поэтому нам нужен будет отдельный процесс оптимизации для удаления таких дыр.
111111 3333333

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

Нет, в реальной жизни у нас данные будут формировать по другому:

111111112222222111111322233333311111222333111

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

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

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

1111111122222222222333333333444444

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

Возникает вопрос, а что бы такое придумать, что бы и вставка и чтение работало быстро.

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

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

Основная идея, заложенная в основу движков семейства MergeTree следующая. Когда у вас есть огромное количество данных, которые должны быть вставлены в таблицу, вы должны быстро записать их по частям, а затем объединить части по некоторым правилам в фоновом режиме. Этот метод намного эффективнее, чем постоянная перезапись данных в хранилище при вставке.

Подробнее о MergeTree можно почитать на странице документации.

Установка и запуск ClickHouse

Подробнее о разных видах установки можно почитать на странице документации

Далее я опишу установку из исходного кода для MacOS.

Установка необходимых компиляторов, инструментов и библиотек

$ brew install cmake ninja libtool gettext

Далее клонируем репозитарий с исходным кодом

git clone --recursive git@github.com:ClickHouse/ClickHouse.git
cd ClickHouse

В итоге у меня получилось где то 4,88 ГБ. Далее собираем проект

$ mkdir build
$ cd build
$ cmake .. -DCMAKE_CXX_COMPILER=`which clang++` -DCMAKE_C_COMPILER=`which clang`
$ ninja
$ cd ..

Так же можно установить ClickHouse на MacOS через brew, благодаря https://github.com/arduanov/homebrew-clickhouse

brew tap arduanov/clickhouse
brew upgrade
brew install clickhouse

Запуск

Запустить сервер можно вручную из консоли:

$ clickhouse-server --config-file=/etc/clickhouse-server/config.xml

Если у вас нет config.xml его можно взять из репозитария. Понадобиться еще файл users.xml

https://github.com/ClickHouse/ClickHouse/blob/master/dbms/programs/server/config.xml

https://github.com/ClickHouse/ClickHouse/blob/master/dbms/programs/server/users.xml

После запуска сервера, соединиться с ним можно с помощью клиента командной строки:

$ clickhouse-client

По умолчанию он соединяется с localhost:9000, от имени пользователя default без пароля. Также клиент может быть использован для соединения с удалённым сервером с помощью аргумента --host.

Терминал должен использовать кодировку UTF-8.

Более подробная информация о клиенте располагается в разделе «Клиент командной строки».

Пример проверки работоспособности системы:

$ ./clickhouse-client
ClickHouse client version 0.0.18749.
Connecting to localhost:9000.
Connected to ClickHouse server version 0.0.18749.

:) SELECT 1

SELECT 1

┌─1─┐
│ 1 │
└───┘

1 rows in set. Elapsed: 0.003 sec.

:)

Поздравляем, система работает!

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

Скачать и извлечь данные таблицы

curl https://clickhouse-datasets.s3.yandex.net/hits/tsv/hits_v1.tsv.xz | unxz --threads=`nproc` > hits_v1.tsv
curl https://clickhouse-datasets.s3.yandex.net/visits/tsv/visits_v1.tsv.xz | unxz --threads=`nproc` > visits_v1.tsv

Извлеченные файлы имеют размер около 10 ГБ.

Создание таблицы

Таблицы логически сгруппированы в «databases». Есть база данных по умолчанию default, но мы создадим новую с именем tutorial:

Команды можно запуска с командной строки в виде clickhouse-client –query “xxx”, либо внутри клиента clickhouse-client:

clickhouse-client --query "CREATE DATABASE IF NOT EXISTS tutorial"

Синтаксис для создания таблиц намного сложнее по сравнению с базами данных (см. Ссылку. В общем случае оператор CREATE TABLE должен указывать три ключевых момента:

  1. Имя таблицы для создания.
  2. Схема таблицы, то есть список столбцов и их типы данных.
  3. Схема таблицы, то есть список столбцов и их типы данных. Механизм таблицы и его настройки, которые определяют все детали физического выполнения запросов к этой таблице.

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

  • hits представляет собой таблицу с каждым действием, выполняемым всеми пользователями на всех веб-сайтах, охватываемых службой.
  • visits таблица, которая содержит предварительно созданные сеансы вместо отдельных действий

Давайте посмотрим и выполним реальные запросы на создание таблиц для этих таблиц:

CREATE TABLE tutorial.hits_v1
(
    `WatchID` UInt64,
    `JavaEnable` UInt8,
    `Title` String,
    `GoodEvent` Int16,
    `EventTime` DateTime,
    `EventDate` Date,
    `CounterID` UInt32,
    `ClientIP` UInt32,
    `ClientIP6` FixedString(16),
    `RegionID` UInt32,
    `UserID` UInt64,
    `CounterClass` Int8,
    `OS` UInt8,
    `UserAgent` UInt8,
    `URL` String,
    `Referer` String,
    `URLDomain` String,
    `RefererDomain` String,
    `Refresh` UInt8,
    `IsRobot` UInt8,
    `RefererCategories` Array(UInt16),
    `URLCategories` Array(UInt16),
    `URLRegions` Array(UInt32),
    `RefererRegions` Array(UInt32),
    `ResolutionWidth` UInt16,
    `ResolutionHeight` UInt16,
    `ResolutionDepth` UInt8,
    `FlashMajor` UInt8,
    `FlashMinor` UInt8,
    `FlashMinor2` String,
    `NetMajor` UInt8,
    `NetMinor` UInt8,
    `UserAgentMajor` UInt16,
    `UserAgentMinor` FixedString(2),
    `CookieEnable` UInt8,
    `JavascriptEnable` UInt8,
    `IsMobile` UInt8,
    `MobilePhone` UInt8,
    `MobilePhoneModel` String,
    `Params` String,
    `IPNetworkID` UInt32,
    `TraficSourceID` Int8,
    `SearchEngineID` UInt16,
    `SearchPhrase` String,
    `AdvEngineID` UInt8,
    `IsArtifical` UInt8,
    `WindowClientWidth` UInt16,
    `WindowClientHeight` UInt16,
    `ClientTimeZone` Int16,
    `ClientEventTime` DateTime,
    `SilverlightVersion1` UInt8,
    `SilverlightVersion2` UInt8,
    `SilverlightVersion3` UInt32,
    `SilverlightVersion4` UInt16,
    `PageCharset` String,
    `CodeVersion` UInt32,
    `IsLink` UInt8,
    `IsDownload` UInt8,
    `IsNotBounce` UInt8,
    `FUniqID` UInt64,
    `HID` UInt32,
    `IsOldCounter` UInt8,
    `IsEvent` UInt8,
    `IsParameter` UInt8,
    `DontCountHits` UInt8,
    `WithHash` UInt8,
    `HitColor` FixedString(1),
    `UTCEventTime` DateTime,
    `Age` UInt8,
    `Sex` UInt8,
    `Income` UInt8,
    `Interests` UInt16,
    `Robotness` UInt8,
    `GeneralInterests` Array(UInt16),
    `RemoteIP` UInt32,
    `RemoteIP6` FixedString(16),
    `WindowName` Int32,
    `OpenerName` Int32,
    `HistoryLength` Int16,
    `BrowserLanguage` FixedString(2),
    `BrowserCountry` FixedString(2),
    `SocialNetwork` String,
    `SocialAction` String,
    `HTTPError` UInt16,
    `SendTiming` Int32,
    `DNSTiming` Int32,
    `ConnectTiming` Int32,
    `ResponseStartTiming` Int32,
    `ResponseEndTiming` Int32,
    `FetchTiming` Int32,
    `RedirectTiming` Int32,
    `DOMInteractiveTiming` Int32,
    `DOMContentLoadedTiming` Int32,
    `DOMCompleteTiming` Int32,
    `LoadEventStartTiming` Int32,
    `LoadEventEndTiming` Int32,
    `NSToDOMContentLoadedTiming` Int32,
    `FirstPaintTiming` Int32,
    `RedirectCount` Int8,
    `SocialSourceNetworkID` UInt8,
    `SocialSourcePage` String,
    `ParamPrice` Int64,
    `ParamOrderID` String,
    `ParamCurrency` FixedString(3),
    `ParamCurrencyID` UInt16,
    `GoalsReached` Array(UInt32),
    `OpenstatServiceName` String,
    `OpenstatCampaignID` String,
    `OpenstatAdID` String,
    `OpenstatSourceID` String,
    `UTMSource` String,
    `UTMMedium` String,
    `UTMCampaign` String,
    `UTMContent` String,
    `UTMTerm` String,
    `FromTag` String,
    `HasGCLID` UInt8,
    `RefererHash` UInt64,
    `URLHash` UInt64,
    `CLID` UInt32,
    `YCLID` UInt64,
    `ShareService` String,
    `ShareURL` String,
    `ShareTitle` String,
    `ParsedParams` Nested(
        Key1 String,
        Key2 String,
        Key3 String,
        Key4 String,
        Key5 String,
        ValueDouble Float64),
    `IslandID` FixedString(16),
    `RequestNum` UInt32,
    `RequestTry` UInt8
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate, intHash32(UserID))
SAMPLE BY intHash32(UserID)
SETTINGS index_granularity = 8192
CREATE TABLE tutorial.visits_v1
(
    `CounterID` UInt32,
    `StartDate` Date,
    `Sign` Int8,
    `IsNew` UInt8,
    `VisitID` UInt64,
    `UserID` UInt64,
    `StartTime` DateTime,
    `Duration` UInt32,
    `UTCStartTime` DateTime,
    `PageViews` Int32,
    `Hits` Int32,
    `IsBounce` UInt8,
    `Referer` String,
    `StartURL` String,
    `RefererDomain` String,
    `StartURLDomain` String,
    `EndURL` String,
    `LinkURL` String,
    `IsDownload` UInt8,
    `TraficSourceID` Int8,
    `SearchEngineID` UInt16,
    `SearchPhrase` String,
    `AdvEngineID` UInt8,
    `PlaceID` Int32,
    `RefererCategories` Array(UInt16),
    `URLCategories` Array(UInt16),
    `URLRegions` Array(UInt32),
    `RefererRegions` Array(UInt32),
    `IsYandex` UInt8,
    `GoalReachesDepth` Int32,
    `GoalReachesURL` Int32,
    `GoalReachesAny` Int32,
    `SocialSourceNetworkID` UInt8,
    `SocialSourcePage` String,
    `MobilePhoneModel` String,
    `ClientEventTime` DateTime,
    `RegionID` UInt32,
    `ClientIP` UInt32,
    `ClientIP6` FixedString(16),
    `RemoteIP` UInt32,
    `RemoteIP6` FixedString(16),
    `IPNetworkID` UInt32,
    `SilverlightVersion3` UInt32,
    `CodeVersion` UInt32,
    `ResolutionWidth` UInt16,
    `ResolutionHeight` UInt16,
    `UserAgentMajor` UInt16,
    `UserAgentMinor` UInt16,
    `WindowClientWidth` UInt16,
    `WindowClientHeight` UInt16,
    `SilverlightVersion2` UInt8,
    `SilverlightVersion4` UInt16,
    `FlashVersion3` UInt16,
    `FlashVersion4` UInt16,
    `ClientTimeZone` Int16,
    `OS` UInt8,
    `UserAgent` UInt8,
    `ResolutionDepth` UInt8,
    `FlashMajor` UInt8,
    `FlashMinor` UInt8,
    `NetMajor` UInt8,
    `NetMinor` UInt8,
    `MobilePhone` UInt8,
    `SilverlightVersion1` UInt8,
    `Age` UInt8,
    `Sex` UInt8,
    `Income` UInt8,
    `JavaEnable` UInt8,
    `CookieEnable` UInt8,
    `JavascriptEnable` UInt8,
    `IsMobile` UInt8,
    `BrowserLanguage` UInt16,
    `BrowserCountry` UInt16,
    `Interests` UInt16,
    `Robotness` UInt8,
    `GeneralInterests` Array(UInt16),
    `Params` Array(String),
    `Goals` Nested(
        ID UInt32,
        Serial UInt32,
        EventTime DateTime,
        Price Int64,
        OrderID String,
        CurrencyID UInt32),
    `WatchIDs` Array(UInt64),
    `ParamSumPrice` Int64,
    `ParamCurrency` FixedString(3),
    `ParamCurrencyID` UInt16,
    `ClickLogID` UInt64,
    `ClickEventID` Int32,
    `ClickGoodEvent` Int32,
    `ClickEventTime` DateTime,
    `ClickPriorityID` Int32,
    `ClickPhraseID` Int32,
    `ClickPageID` Int32,
    `ClickPlaceID` Int32,
    `ClickTypeID` Int32,
    `ClickResourceID` Int32,
    `ClickCost` UInt32,
    `ClickClientIP` UInt32,
    `ClickDomainID` UInt32,
    `ClickURL` String,
    `ClickAttempt` UInt8,
    `ClickOrderID` UInt32,
    `ClickBannerID` UInt32,
    `ClickMarketCategoryID` UInt32,
    `ClickMarketPP` UInt32,
    `ClickMarketCategoryName` String,
    `ClickMarketPPName` String,
    `ClickAWAPSCampaignName` String,
    `ClickPageName` String,
    `ClickTargetType` UInt16,
    `ClickTargetPhraseID` UInt64,
    `ClickContextType` UInt8,
    `ClickSelectType` Int8,
    `ClickOptions` String,
    `ClickGroupBannerID` Int32,
    `OpenstatServiceName` String,
    `OpenstatCampaignID` String,
    `OpenstatAdID` String,
    `OpenstatSourceID` String,
    `UTMSource` String,
    `UTMMedium` String,
    `UTMCampaign` String,
    `UTMContent` String,
    `UTMTerm` String,
    `FromTag` String,
    `HasGCLID` UInt8,
    `FirstVisit` DateTime,
    `PredLastVisit` Date,
    `LastVisit` Date,
    `TotalVisits` UInt32,
    `TraficSource` Nested(
        ID Int8,
        SearchEngineID UInt16,
        AdvEngineID UInt8,
        PlaceID UInt16,
        SocialSourceNetworkID UInt8,
        Domain String,
        SearchPhrase String,
        SocialSourcePage String),
    `Attendance` FixedString(16),
    `CLID` UInt32,
    `YCLID` UInt64,
    `NormalizedRefererHash` UInt64,
    `SearchPhraseHash` UInt64,
    `RefererDomainHash` UInt64,
    `NormalizedStartURLHash` UInt64,
    `StartURLDomainHash` UInt64,
    `NormalizedEndURLHash` UInt64,
    `TopLevelDomain` UInt64,
    `URLScheme` UInt64,
    `OpenstatServiceNameHash` UInt64,
    `OpenstatCampaignIDHash` UInt64,
    `OpenstatAdIDHash` UInt64,
    `OpenstatSourceIDHash` UInt64,
    `UTMSourceHash` UInt64,
    `UTMMediumHash` UInt64,
    `UTMCampaignHash` UInt64,
    `UTMContentHash` UInt64,
    `UTMTermHash` UInt64,
    `FromHash` UInt64,
    `WebVisorEnabled` UInt8,
    `WebVisorActivity` UInt32,
    `ParsedParams` Nested(
        Key1 String,
        Key2 String,
        Key3 String,
        Key4 String,
        Key5 String,
        ValueDouble Float64),
    `Market` Nested(
        Type UInt8,
        GoalID UInt32,
        OrderID String,
        OrderPrice Int64,
        PP UInt32,
        DirectPlaceID UInt32,
        DirectOrderID UInt32,
        DirectBannerID UInt32,
        GoodID String,
        GoodName String,
        GoodQuantity Int32,
        GoodPrice Int64),
    `IslandID` FixedString(16)
)
ENGINE = CollapsingMergeTree(Sign)
PARTITION BY toYYYYMM(StartDate)
ORDER BY (CounterID, StartDate, intHash32(UserID), VisitID)
SAMPLE BY intHash32(UserID)
SETTINGS index_granularity = 8192

Как мы можем видеть, hits_v1 использует базовый движок MergeTree, а visits_v1 использует вариант Collapsing.

Импорт данных

Импорт данных в ClickHouse осуществляется через запрос INSERT INTO, как и во многих других базах данных SQL. Однако данные обычно предоставляются в одном из поддерживаемых форматов вместо предложения VALUES (которое также поддерживается).

Файлы, которые мы скачали ранее, представлены в формате табуляций, поэтому вот как их импортировать через консольный клиент:

clickhouse-client --query "INSERT INTO tutorial.hits_v1 FORMAT TSV" --max_insert_block_size=100000 < hits_v1.tsv
clickhouse-client --query "INSERT INTO tutorial.visits_v1 FORMAT TSV" --max_insert_block_size=100000 < visits_v1.tsv

ClickHouse имеет множество настроек, и один из способов указать их в консольном клиенте использовать аргументы, например мы используем –max_insert_block_size. Самый простой способ выяснить, какие настройки доступны, что они означают и каковы значения по умолчанию, – это запросить таблицу system.settings:

SELECT name, value, changed, description
FROM system.settings
WHERE name LIKE '%max_insert_b%'
FORMAT TSV

При желании вы можете OPTIMIZE таблицы после импорта. Таблицы, настроенные с помощью механизма семейства MergeTree, всегда выполняют слияние частей данных в фоновом режиме, чтобы оптимизировать хранение данных (или, по крайней мере, проверить, имеет ли это смысл). Эти запросы просто заставляют движок таблиц выполнить оптимизацию хранилища прямо сейчас, а не через некоторое время:

clickhouse-client --query "OPTIMIZE TABLE tutorial.hits_v1 FINAL"
clickhouse-client --query "OPTIMIZE TABLE tutorial.visits_v1 FINAL"

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

Теперь мы можем проверить, что таблицы успешно импортированы:

clickhouse-client --query "SELECT COUNT(*) FROM tutorial.hits_v1"
clickhouse-client --query "SELECT COUNT(*) FROM tutorial.visits_v1"

Пример запроса

SELECT
    StartURL AS URL,
    AVG(Duration) AS AvgDuration
FROM tutorial.visits_v1
WHERE StartDate BETWEEN '2014-03-23' AND '2014-03-30'
GROUP BY URL
ORDER BY AvgDuration DESC
LIMIT 10
SELECT
    sum(Sign) AS visits,
    sumIf(Sign, has(Goals.ID, 1105530)) AS goal_visits,
    (100. * goal_visits) / visits AS goal_percent
FROM tutorial.visits_v1
WHERE (CounterID = 912887) AND (toYYYYMM(StartDate) = 201403) AND (domain(StartURL) = 'yandex.ru')

Интерфейсы ClickHouse

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

ClickHouse предоставляет два сетевых интерфейса (оба могут быть дополнительно обернуты в TLS для дополнительной безопасности):

  • HTTP, который задокументирован и прост для использования напрямую;
  • Native TCP, который имеет меньше накладных расходов.

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

Существует также широкий спектр сторонних библиотек для работы с ClickHouse:

На первом этапе наиболее часто вам скорее всего придется работать с HTTP-интерфейс

HTTP-интерфейс

HTTP интерфейс позволяет использовать ClickHouse на любой платформе, из любого языка программирования. У нас он используется для работы из Java и Perl, а также из shell-скриптов. В других отделах, HTTP интерфейс используется из Perl, Python и Go. HTTP интерфейс более ограничен по сравнению с родным интерфейсом, но является более совместимым.

По умолчанию, clickhouse-server слушает HTTP на порту 8123 (это можно изменить в конфиге). Если запросить GET / без параметров, то вернётся строка заданная с помощью настройки http_server_default_response. Значение по умолчанию “Ok.” (с переводом строки на конце).

$ curl 'http://localhost:8123/'
Ok.

В скриптах проверки доступности вы можете использовать GET /ping без параметров. Если сервер доступен всегда возвращается “Ok.” (с переводом строки на конце).

$ curl 'http://localhost:8123/ping'
Ok.

Запрос отправляется в виде URL параметра с именем query. Или как тело запроса при использовании метода POST. Или начало запроса в URL параметре query, а продолжение POST-ом (зачем это нужно, будет объяснено ниже). Размер URL ограничен 16KB, это следует учитывать при отправке больших запросов.

В случае успеха, вам вернётся код ответа 200 и результат обработки запроса в теле ответа. В случае ошибки, вам вернётся код ответа 500 и текст с описанием ошибки в теле ответа.

При использовании метода GET, выставляется настройка readonly. То есть, для запросов, модифицирующие данные, можно использовать только метод POST. Сам запрос при этом можно отправлять как в теле POST-а, так и в параметре URL.

По умолчанию, данные возвращаются в формате TabSeparated (подробнее смотри раздел “Форматы”). Можно попросить любой другой формат – с помощью секции FORMAT запроса.

http://localhost:8123/?query=SELECT 1 FORMAT Pretty

Более подробнее можно почитать на страницу документации

Заключение

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

Была ли вам полезна эта статья?
[25 / 4.6]

Spread the love
Подписаться
Уведомление о
guest
2 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Катя
4 лет назад

Спасибо за полезную статью, недавно узнала про ClickHouse.

Владимир
Владимир
6 месяцев назад

Появилось еще больше вопросов: там где рассказ про логику работы Clickhouse так и не понятно, так в чем фишка clickhouse, ну и что, записал он вновь полученные данные в конец в памяти (даже если в памяти эта пачка получилась упорядоченная) дальше то надо смержить с данными, которые на диске, а там они отсортированы (столбцы) и надо отсортированные столбцы из памяти вставить на диск. И надо опять всё перебирать…
Еще в примере структур таблиц и нет понятия почему в одной таблице выбрали CollapsingMergeTree а в другой MergeTree
не понятн что делает в структуре PARTITION BY, ORDER BY и что указывать в ORDER BY(), про гранулированность я даже не заикаюсь.