CH позиционируется для очень больших хранилищ данных, для работы на HDD. Пока мы храним несколько десятков ТБ. В этом его сверх "крутость CH" что на блинах такая скорость работы )
Если по каким-то причинах реляционная СУБД не подходит (например, очень много данных) и требуется выполнять обновления записей, то при выполнении ряда условий:
Статус продаж в конечном итоге финализируется (т.е. переходит в состояние, когда информация о продаже более не меняется, например, продажа состоялась).
Количество продаж, у которых потенциально может измениться состояние, в сравнении с общим количеством продаж значительно меньше и эта информация может сохраняться во внешнее хранилище с быстрым доступом по ключу (по идентификатору продажи).
рекомендуется использовать движок https://clickhouse.yandex/reference_ru.html#CollapsingMergeTree, работа которого основана на двух состояниях записи { old_state, new_state }, и иметь возможность быстрого получения состояний продаж, которые потенциально могут быть изменены, например, во внешнем key-value хранилище, где ключом будет идентификатор продажи, а значением — информация о продаже, включая статус.
Например, будет следующая структура таблицы: ( sign, ( sale_uuid ), manager_id, price, status ). Тогда работа с данными будет выглядеть следующим образом.
Добавление продажи (запись в key-value хранилище отсутствует по заданному ключу: sale_uuid):
Key-value:
put( 'sale_uuid_0', { 867, 120.34, 'pending' } )
CH:
( 1, 'sale_uuid_0', 867, 120.34, 'pending' )
Обновление продажи (запись в key-value хранилище присутствует по заданному ключу: sale_uuid):
Готовых продуктов/дашбордов мы не знаем, частично поэтому мы реализовали свой GUI для редактора SQL. В котором планируется реализовать отрисовку графиков ( визуализацию данных) после создания запроса.
Думаю использовать Grafana для отрисовки графика на обычных движках CH, не очень правильный кейс.
Для Graphite, в CH предусмотрен отдельный сторедж, который хранит и “сворачивает” данные в виде Graphite схемы данных. GraphiteRollupSorted не документирован, и опыта у нас работы с ним пока нет.
Есть много способов решения задачи. Основная идея в том, что лучше хранить события в одной большой таблице и не использовать JOIN по большим данным. Или дублировать данные при вставке в несколько таблиц, в терминологии статьи это значит "писать в несколько TSV-файлов".
Также есть вариант, когда вы пишете RAW событий в одну большую таблицу (назовем ее "поток"), к которой прикреплены MaterializedView. Эти MaterializedView будут раскладывать RAW-поток на нужные составляющие.
Возможно, лучше изменить понятие uuid, и ввести понятие session_id. Сессия меняется, а uuid нет, если это возможно.
Если без uuid, то тогда можно сделать еще такие варианты:
Использовать две колонки uuid: например first_uuid и main_uuid. При изменении uuid вы пишете в разные поля, как изменилась сессия.
Использовать отдельное поле, массив сессий.
Использовать движок CollapsingMergeTree.
Ввести единый uuid и добавить признак типа enum|int — is_login.
Я бы посоветовал написать прототип по одному из вариантов или сразу несколько вариантов и начать делать запросы SELECT. Придумать несколько штук, которые самые популярные у вас. И исходя из запросов на чтение понять оптимальную структуру.
В статье мы привели самый простой пример, для упрощения. На самом деле мы используем практически все из перечисленных выше методов. И меняли структуру данных под CH.
Вопрос хороший, на самом деле в этом отличие СМИ2 от рекламных сетей, у нас высокое требование по доставки свежего контента пользователю – и вообще, и особенно если происходит что-то что чрезвычайно важное (как выборы в Америке сегодня).
Тогда об этом начинают писать все СМИ, и чем быстрее наша система заметит свежую и важную новость по теме, тем лучше. А новости могут устаревать даже спустя полчаса (если появился апдейт, сообщающий о новых подробностях или изменении суть происходящего)
Поэтому сейчас мы ранжируем за 5 секунд весь набор активных новостей, плюс-минус 30 тысяч, и пользователь получает каждую минуту свежий набор новостей. Безусловно, это не значит, что обновляются вообще ВСЕ новости – речь о том, что мы стремимся показывать актуальный набор на конкретный момент времени.
Желание изучить максимально предметную область — все “фичи” CH на самом низком уровне, чтобы реализовать нужный нам ф-ционал, асинхронная отправка с сжатием потока.
В нашем приложении под HHVM, использую только максимально легковесный код, в котором можно быть уверенным что все работает так как нужно.
Драйвер мы не используем под большой нагрузкой, т/к не представляю у нас такой кейс.
Есть альтернативный драйвер на Guzzle, но в нем не реализован функционал которые хотелось и выглядит он заброшенным.
HTTPlug показался неподходящим, т/к не нашел в нем реализации curl_multi_exec, (возможно плохо искал).
Реализация показалась тяжелой в Guzzle, хотя в нем отлично реализован GuzzleHttp\Handler\CurlMultiHandler
В общем случае добавление новых узлов в кластер требует либо тяжелой операции перешардирования (с равномерным перераспределением данных), либо использования механизма весов, когда данные пишутся более активно на новые сервера с более высоким весом, и когда объём данных на новых и старых узлах становится примерно равный, веса выравниваются.
У каждого из подходов есть плюсы и минусы:
Перешардирование даёт лучшее распределение данных в кластере и при выполнении запросов узлы кластера будут нагружены примерно одинаково. Минус перешардирования — кроме того, что это очень тяжелая операция, возможно, потребует остановки кластера. Если СУБД, к примеру, является подсистемой, то остановка кластера может быть недопустима.
Использование весов даёт неравномерное распределение, зато позволяет быстро добавлять узлы без создания дополнительной нагрузки на кластер.
Учитывая, что в нашем случае CH находится под нагрузкой, более правильным видится вариант с весами.
Самим средствами CH — проверить при вставке валидность uuid думаю не правильно, это просто строка, без типизации.
Можно отдельно отправить запрос и узнать сколько «плохих» строк, которые не соответствуют длине, не содержат ‘-’ и т/д.
Лучший кейс — это проверка при сохранении «события» в файл, т/е на стороне «писателя» данных.
Если «писатель» получил пустой uuid или не валидный — то можно проставить новый рандомный uuid, пометив что он искусственный, через отдельную колонку в таблице типа is_true_uuis = [ 0 | 1 ].
Теоретически, можно сделать еще так, через новое поле check_uuis DEFAULT (length(uuid)=61 ? 1 : 0 ), но думаю это тоже плохой кейс.
Под realtime мы подразумевали, что данные достаточно быстро доступны для чтения, по сравнению со стеком Hadoop.
В видеороликах продемонстрировано, насколько быстро ClickHouse анализирует исторические данные. Демонстировать запросы данных за последние 5-10 минут тяжело, т. к. их выполнение занимает сотые доли секунды.
Как пример, для чего мы используем запросы, близкие к realtime, могу привести ранжирование статей. Допустим, 1 минутут назад статью просмотрело 100 человек, в промежутке между 2-й и 3-й минутами от текущего времени — 120 человек. Эти данные используются нами для ранжирования статей. У нас порядка 25 тысяч статей. По каждой из них нужно получать состояние по каждой минуте и на основе этой информации ранжировать статьи.
Как только мы приготовились запускать Druid в продакшн, мы успели накопить в нём данные всего лишь за месяц. Druid показал хорошую производительность на данных с глубиной в несколько дней. При запросах большей глубины были заметные тормоза.
Как только мы увидели ClickHouse в открытом доступе, мы удалили с серверов Druid и поставили ClickHouse. В ClickHouse данные у нас храняться больше 3,5 месяцев.
Прямого сравнения у нас не было, поэтому в статье мы не приводим бенчмарки.
Druid много чего не поддерживает по сравнению с ClickHouse. Мы хотели/мечтали о базе с «SQL-подобными запросами». Когда мы анализировали Druid, он не понравился нам запросами в «своем формате». Писать SQL-запросы приятнее и проще, чем огромные «массивы» в postAggregation, hyperUniqueCardinality и т. д.
Решил сам проверить 3.0 в действии, тест простой но близкий к моей задаче, пишем случайный ключ и читаем по случайному ключу.
Тесты делал на php-fpm + nginx, три отдельных сервера — на одном сразу три монги запущенно.
Возможно где то ошибся и тест сильно " синтетический ", результаты: в два раза быстрее и компактнее…
Подробности: gist.github.com/isublimity/4974919e38c66367804d
Ошибка явно указывает на ограничения пользователя, в настройках вашей db.
Access denied to database linear for user
Для запроса: SHOW TABLES FORMAT JSON
https://clickhouse.tech/docs/ru/operations/access-rights/amp/
Как разработчик Tabix ( может слышали ), смотрите в сторону React, но только не angular))
Взял за основу pgsql + mssql "шаблоны" в AceJs и собрал под CH
Последняя Dev версия если интересно тут
Сообщество выпустило интеграцию с grafana: https://github.com/Vertamedia/clickhouse-grafana
О GraphiteRollupSorted лучше спросить в Telegram группе — там есть люди которые активно используют этот сторедж, мы не стали использовать его.
CH позиционируется для очень больших хранилищ данных, для работы на HDD. Пока мы храним несколько десятков ТБ. В этом его сверх "крутость CH" что на блинах такая скорость работы )
Кто то добрый взялся и перевел -> спасибо!
Да есть такое в issue и roadmap — появятся силы добавим мультиязычность https://github.com/smi2/clickhouse-frontend/issues/21
Если по каким-то причинах реляционная СУБД не подходит (например, очень много данных) и требуется выполнять обновления записей, то при выполнении ряда условий:
рекомендуется использовать движок https://clickhouse.yandex/reference_ru.html#CollapsingMergeTree, работа которого основана на двух состояниях записи
{ old_state, new_state }
, и иметь возможность быстрого получения состояний продаж, которые потенциально могут быть изменены, например, во внешнем key-value хранилище, где ключом будет идентификатор продажи, а значением — информация о продаже, включая статус.Например, будет следующая структура таблицы:
( sign, ( sale_uuid ), manager_id, price, status )
. Тогда работа с данными будет выглядеть следующим образом.Key-value:
CH:
Key-value:
CH:
На текущем этапе CH будет хранить 3 записи:
, которые потенциально могут быть схлопнуты по ключу в одну запись:
Аналогичным образом могут 'изменяться' любые поля для заданного ключа.
old_state = get( 'sale_uuid_0' )
new_state — не задан
Key-value:
remove( 'sale_uuid_0' )
CH:
На текущем этапе CH будет хранить 4 записи (если схлопывание движком еще не произошло):
Либо CH будет хранить 2 записи:
Во всех случаях (рано или поздно) записи будут схлопнуты и запись о продаже будет 'удалена'.
Готовых продуктов/дашбордов мы не знаем, частично поэтому мы реализовали свой GUI для редактора SQL. В котором планируется реализовать отрисовку графиков ( визуализацию данных) после создания запроса.
Думаю использовать Grafana для отрисовки графика на обычных движках CH, не очень правильный кейс.
Для Graphite, в CH предусмотрен отдельный сторедж, который хранит и “сворачивает” данные в виде Graphite схемы данных.
GraphiteRollupSorted
не документирован, и опыта у нас работы с ним пока нет.Есть много способов решения задачи. Основная идея в том, что лучше хранить события в одной большой таблице и не использовать JOIN по большим данным. Или дублировать данные при вставке в несколько таблиц, в терминологии статьи это значит "писать в несколько TSV-файлов".
Также есть вариант, когда вы пишете RAW событий в одну большую таблицу (назовем ее "поток"), к которой прикреплены MaterializedView. Эти MaterializedView будут раскладывать RAW-поток на нужные составляющие.
Возможно, лучше изменить понятие uuid, и ввести понятие session_id. Сессия меняется, а uuid нет, если это возможно.
Если без uuid, то тогда можно сделать еще такие варианты:
is_login
.Я бы посоветовал написать прототип по одному из вариантов или сразу несколько вариантов и начать делать запросы SELECT. Придумать несколько штук, которые самые популярные у вас. И исходя из запросов на чтение понять оптимальную структуру.
В статье мы привели самый простой пример, для упрощения. На самом деле мы используем практически все из перечисленных выше методов. И меняли структуру данных под CH.
Вопрос хороший, на самом деле в этом отличие СМИ2 от рекламных сетей, у нас высокое требование по доставки свежего контента пользователю – и вообще, и особенно если происходит что-то что чрезвычайно важное (как выборы в Америке сегодня).
Тогда об этом начинают писать все СМИ, и чем быстрее наша система заметит свежую и важную новость по теме, тем лучше. А новости могут устаревать даже спустя полчаса (если появился апдейт, сообщающий о новых подробностях или изменении суть происходящего)
Поэтому сейчас мы ранжируем за 5 секунд весь набор активных новостей, плюс-минус 30 тысяч, и пользователь получает каждую минуту свежий набор новостей. Безусловно, это не значит, что обновляются вообще ВСЕ новости – речь о том, что мы стремимся показывать актуальный набор на конкретный момент времени.
Я надеялся избежать этого вопроса )
Есть альтернативный драйвер на Guzzle, но в нем не реализован функционал которые хотелось и выглядит он заброшенным.
HTTPlug показался неподходящим, т/к не нашел в нем реализации curl_multi_exec, (возможно плохо искал).
Реализация показалась
тяжелойв Guzzle, хотя в нем отлично реализованGuzzleHttp\Handler\CurlMultiHandler
Примеры
странногоспецифичного кодаВ общем случае добавление новых узлов в кластер требует либо тяжелой операции перешардирования (с равномерным перераспределением данных), либо использования механизма весов, когда данные пишутся более активно на новые сервера с более высоким весом, и когда объём данных на новых и старых узлах становится примерно равный, веса выравниваются.
У каждого из подходов есть плюсы и минусы:
Перешардирование даёт лучшее распределение данных в кластере и при выполнении запросов узлы кластера будут нагружены примерно одинаково. Минус перешардирования — кроме того, что это очень тяжелая операция, возможно, потребует остановки кластера. Если СУБД, к примеру, является подсистемой, то остановка кластера может быть недопустима.
Использование весов даёт неравномерное распределение, зато позволяет быстро добавлять узлы без создания дополнительной нагрузки на кластер.
Учитывая, что в нашем случае CH находится под нагрузкой, более правильным видится вариант с весами.
Рекомендую:
Перешардирование
bug on groups
Самим средствами CH — проверить при вставке валидность uuid думаю не правильно, это просто строка, без типизации.
Можно отдельно отправить запрос и узнать сколько «плохих» строк, которые не соответствуют длине, не содержат ‘-’ и т/д.
Лучший кейс — это проверка при сохранении «события» в файл, т/е на стороне «писателя» данных.
Если «писатель» получил пустой uuid или не валидный — то можно проставить новый рандомный uuid, пометив что он искусственный, через отдельную колонку в таблице типа
is_true_uuis = [ 0 | 1 ]
.Теоретически, можно сделать еще так, через новое поле
check_uuis DEFAULT (length(uuid)=61 ? 1 : 0 )
, но думаю это тоже плохой кейс.Спасибо за позитивный комментарий.
Внутри драйвера мы чуда не сотворили.
Используется
curl_multi
.Функция
execLoopWait
ждет в цикле — через проверку состоянияcurl_multi_exec
.См. https://github.com/smi2/phpClickHouse/blob/master/src/Transport/CurlerRolling.php#L159
В видеороликах продемонстрировано, насколько быстро ClickHouse анализирует исторические данные. Демонстировать запросы данных за последние 5-10 минут тяжело, т. к. их выполнение занимает сотые доли секунды.
Как пример, для чего мы используем запросы, близкие к realtime, могу привести ранжирование статей. Допустим, 1 минутут назад статью просмотрело 100 человек, в промежутке между 2-й и 3-й минутами от текущего времени — 120 человек. Эти данные используются нами для ранжирования статей. У нас порядка 25 тысяч статей. По каждой из них нужно получать состояние по каждой минуте и на основе этой информации ранжировать статьи.
Как только мы увидели ClickHouse в открытом доступе, мы удалили с серверов Druid и поставили ClickHouse. В ClickHouse данные у нас храняться больше 3,5 месяцев.
Прямого сравнения у нас не было, поэтому в статье мы не приводим бенчмарки.
Druid много чего не поддерживает по сравнению с ClickHouse. Мы хотели/мечтали о базе с «SQL-подобными запросами». Когда мы анализировали Druid, он не понравился нам запросами в «своем формате». Писать SQL-запросы приятнее и проще, чем огромные «массивы» в postAggregation, hyperUniqueCardinality и т. д.
используем его в проде, для
алертов из приложений и nagios и получить состояние и
Тесты делал на php-fpm + nginx, три отдельных сервера — на одном сразу три монги запущенно.
Возможно где то ошибся и тест сильно " синтетический ", результаты: в два раза быстрее и компактнее…
Подробности: gist.github.com/isublimity/4974919e38c66367804d