Наступил сентябрь, и вышел ClickHouse 26.9 с очередной насыщенной подборкой новых функций.
Релиз ClickHouse 26.9 содержит 56 новых функций 🍁, 135 оптимизаций производительности 🍎 и 464 исправления ошибок 🐿️.
В этот релиз вошли условные границы для LIMIT, инкрементальные обновления для материализованных представлений только для добавления (append-only) и сброс на диск для запросов DISTINCT с высокой кардинальностью.
Мы также рассмотрим токены доступа с ограниченным сроком действия, более быстрые запросы min, max и count, расширенную поддержку PromQL и несколько улучшений для удобства использования.
Особое приветствие всем новым участникам в версии 26.9! Рост сообщества ClickHouse не может не восхищать, и мы всегда благодарны за вклад, который делает ClickHouse таким популярным.
Ниже приведены имена новых участников:
Aaron Harlap, Actuele AI, Alex Francoeur, Alex Prabhat Bara, AlexF, Anand Kumar Shaw, Anton Kovalenko, Aparajita Pandey, Brandon Pereira, Claude, Denys Stetsenko, Dmitrii Bezrukov, Evandro Leopoldino Gonçalves, Friedrich ten Hagen, George Viamontes, Gülçin Yıldırım Jelinek, Hamza Wasim, Hank Hoffmeier, Héctor Pablos, Itamar Tempelhof, Ivan N. Taranov, Ivan Tkachev, Ivan Tkatchev, Jithin Zachariah, Jordan Bertasso, Jords, Joshua, Juanjo, Kelly Toole, Lucas, Luis Neves, Luís Lizardo, Marat Dulin, Mike Shi, Navneet Kumar, Pablo Francisco Pérez Hidalgo, Paul Annesley, Philip Li, Pratham Nayak, Pratheesh, SamWolfberg, Sankalp Thakur, Sebastian Vercruyssse, Serhiy Bzhezytskyy, Takayuki Enomoto, Thien Phan, Vadim Ilves, XanderYoon, Yongqiang Tian, alexprabhat99, anand-tradesea, aparajita, bakhtiiartashbolotov, cuishuang, jithinzac, kasimtj, key-arg, kyungryun, linsen, maederm, mariahlynnenagy, miao tang, mosya415, ngagejason, sakshichitnis27, sleepingeight, statxc, t, tars, zhanglangning
Подсказка: если вам интересно, как мы формируем этот список… here.
Вы также можете посмотреть слайды презентации.
Начиная с ClickHouse 26.9, вы можете использовать значения времени (Time) в качестве смещений при сложении или вычитании со значениями даты и времени (DateTime). Результат сохраняет часовой пояс значения DateTime.
Давайте рассмотрим несколько простых примеров:
Если вычисление выходит за диапазон, поддерживаемый типом результата, параметр date_time_overflow_behavior определяет, будет ли ClickHouse генерировать исключение, округлять результат до ближайшей границы или оставлять переполнение без проверки.
Максимальное значение, которое мы можем сохранить в DateTime, — это 2106-02-07 06:28:15. Давайте посмотрим, что произойдет, если мы прибавим к этому времени одну секунду:
Произошло переполнение обратно до минимального значения DateTime, что вполне ожидаемо, так как значением по умолчанию для date_time_overflow_behavior является ignore. Но, возможно, мы предпочтем получить исключение при переполнении значения:
Или мы можем использовать saturate, и в этом случае будет возвращено максимальное значение для этого типа:
Наконец, давайте выведем часовой пояс исходных и вычисленных значений:
ClickHouse 26.9 расширяет поддержку PromQL, добавляя больше функций, дополнительные конечные точки HTTP API Prometheus и прямые SELECT-запросы к таблицам TimeSeries.
PromQL и движок таблиц TimeSeries теперь доступны в закрытом превью на ClickHouse Cloud, что позволяет хранить метрики в ClickHouse и запрашивать их из ClickStack, Grafana, clickhouse-client или с помощью SQL.
Вы можете узнать больше из публикации в блоге «Представляем новый движок TimeSeries в ClickHouse».
В ClickHouse 26.9 представлена команда CREATE TOKEN, которая позволяет пользователю создавать учетные данные с ограниченным сроком действия для приложений, скриптов, CI-задач и агентов без раскрытия или замены основного пароля.
Токен можно ограничить подмножеством существующих привилегий пользователя, что снижает ущерб в случае утечки учетных данных.
Давайте посмотрим, как это работает. Сначала создадим небольшую таблицу:
Затем создадим пользователя по имени alexey:
У alexey есть права SELECT и INSERT на эту таблицу, а также он может создавать для себя токен.
Далее мы подключимся от имени alexey и создадим токен со сроком действия 30 дней, который может выполнять только SELECT-запросы к таблице ourTable:
Оператор возвращает сгенерированный токен и срок его действия:
ClickHouse отображает токен только один после создания, поэтому обязательно скопируйте его.
Затем мы можем подключиться с помощью токена и выполнить SELECT-запрос:
Все работает отлично, как мы и ожидали. Но что, если мы попытаемся выполнить вставку в таблицу с помощью нашего токена?
Это не работает, так как у alexey есть только доступ SELECT при аутентификации с использованием токена.
Токен никогда не предоставляет больше привилегий, чем уже есть у пользователя, и перестает работать по истечении срока действия или при удалении пользователя. Если вы не укажете VALID UNTIL или VALID FOR, время жизни по умолчанию составит 30 минут.
ClickHouse 26.9 расширяет оператор LIMIT граничными условиями, которые запускают и останавливают вывод на основе значений в упорядоченном потоке результатов — возможность, которой, насколько нам известно, в настоящее время нет ни в одной другой базе данных.
- AFTER включает строку, удовлетворяющую его условию.
- UNTIL останавливается перед строкой, удовлетворяющей его условию.
- Вы можете добавить ALL, чтобы применять границу каждый раз при совпадении с условием.
Попробуйте различные комбинации ниже, чтобы увидеть, какие строки возвращает каждый запрос.
Эта функциональность особенно полезна для анализа логов, поэтому давайте рассмотрим набор данных Nginx, использовавшийся в публикации в блоге «Сжатие логов nginx в 170 раз с помощью колоночного хранения».
Сначала создадим таблицу:
Затем загрузим данные:
Теперь получим краткий обзори данных.
Следующий запрос начинается с первого ответа `5xx` и возвращает пять запросов. AFTER является включительным, поэтому запрос, удовлетворяющий условию, включается.
UNTIL является исключительным. Этот запрос начинается с первой ошибки сервера и останавливается перед первым ответом со статусом ниже 500:
Граница AFTER применяется только один раз, если только мы не используем ALL, что приводит к ее повторному применению при каждом совпадении со следующей строкой. Этот запрос возвращает каждую ошибку сервера и следующие два запроса после нее:
В этот период есть три отдельных инцидента в строках 2, 7 и 12. Внутри каждого всплеска каждые 500 ответов заново применяют трехстрочную границу.
Таким образом, последовательные сбои продлевают активное окно до тех пор, пока после финального сбоя не появятся два подряд не-5xx запроса. В этом примере оба являются успешными ответами 200.
ALL также может повторно применять начальную границу, в то время как UNTIL завершает каждый диапазон:
В отличие от предыдущего запроса, этот вывод исключает успешные запросы после каждого всплеска сбоев. UNTIL закрывает диапазон на первом не-5xx ответе, в то время как ALL продолжает сканирование в поисках следующего всплеска.
ClickHouse 26.9 добавляет APPEND INCREMENTAL в обновляемые материализованные представления. Вместо сканирования всей исходной таблицы при каждом обновлении ClickHouse обрабатывает только те строки, которые были зафиксированы с момента предыдущего обновления.
Это можно использовать для инкрементального копирования данных типа append-only в другую таблицу ClickHouse или репликации потока событий из таблицы MergeTree в озеро данных Iceberg.
Давайте посмотрим, как использовать эту функцию с Iceberg.
Мы создадим поток событий заказов только для добавления в ClickHouse и периодически будем копировать их в таблицу Iceberg, демонстрируя, что каждое обновление обрабатывает только вновь зафиксированные строки.
Сначала мы создадим нашу таблицу ClickHouse:
Столбцы block-number и block-offset предоставляют курсор, который ClickHouse использует для идентификации строк, зафиксированных после предыдущего обновления.
Затем мы включим вставку в Iceberg и создадим таблицу Iceberg в моей локальной файловой системе:
Директория lake_order_events содержит метаданные Iceberg, манифесты и файлы данных Parquet.
Наконец, мы создадим материализованное представление, которое будет копировать вновь зафиксированные события в Iceberg каждый час:
Время загрузить немного данных!
Эти события будут скопированы в таблицу Iceberg при срабатывании материализованного представления каждый час, но чтобы ускорить процесс, мы вызовем обновление принудительно:
И если мы сделаем запрос к таблице Iceberg:
Все записи перенесены. Теперь давайте добавим еще несколько строк, чтобы имитировать изменения в этих заказах:
Мы снова вручную обновим материализованное представление, а затем снова сделаем запрос к таблице Iceberg:
Мы видим, что три новых события на месте.
Для целевой таблицы Iceberg ClickHouse сохраняет инкрементальный курсор в сводке снимка (snapshot summary). Таким образом, курсор и вновь добавленные данные фиксируются как часть одного и того же снимка Iceberg. Мы можем запросить system.iceberg_history, чтобы убедиться в этом:
Первое обновление добавило четыре записи, в то время как второе добавило только три новых события, увеличив общее количество с четырех до семи. Мы также видим, что оба снимка содержат курсор обновления.
ClickHouse сохраняет этот курсор в снимке Iceberg вместе с вновь добавленными данными. Если ClickHouse перезапустится, следующее обновление возобновится с этой позиции, а не станет воспроизводить те же события заново. Курсор продвигается только после успешного добавления, поэтому он всегда остается синхронизированным с данными.
Этот подход лучше всего работает с данными, предназначенными только для добавления (append-only), такими как события, логи, аудиторские записи и другие неизменяемые факты.
ClickHouse сохраняет минимальные и максимальные значения для столбцов с числоподобными типами в каждой части данных. Начиная с версии 26.9, она может использовать эту статистику для ответов на запросы min, max и count без чтения данных базового столбца.
Давайте попробуем это на таблице логов Nginx, которую мы использовали ранее. Мы будем использовать response_bytes вместо timestamp. Поскольку timestamp является первым столбцом в ключе сортировки таблицы, ClickHouse уже может ответить на этот запрос по существующим метаданным.
Добавим префикс EXPLAIN к нашему запросу, чтобы увидеть план выполнения:
Если мы посмотрим на последнюю строку, то увидим, что вместо чтения столбца response_bytes ClickHouse формирует результат на основе статистики его столбцов.
Мы можем отключить эту оптимизацию с помощью параметра use_statistics_for_min_max_aggregation, и теперь ClickHouse вынужден сканировать столбец response_bytes для вычисления результата, как показано в следующем запросе:
В ClickHouse 26.9 появилась новая системная таблица system.session_query_ids, которая отслеживает все идентификаторы запросов в текущей сессии в порядке их выполнения.
Мы можем сделать запрос к этой таблице следующим образом:
Таблица system.query_log содержит query_id для каждой записи, а это значит, что теперь мы можем проверить, какие запросы мы только что выполнили, без необходимости вручную указывать идентификаторы запросов при обращении к этой таблице:
ClickHouse 26.9 позволяет устанавливать ограничения на максимальный размер отдельной таблицы, а также на количество таблиц, которые могут быть созданы в базе данных.
Это полезно в многоарендных (multi-tenant), временных и демо-окружениях, где мы не хотим, чтобы одна рабочая нагрузка потребляла все ресурсы.
Начнем с создания таблицы, которая может содержать максимум три строки:
Вставим три строки:
Если мы вставим еще одну строку, операция все равно завершится успешно:
Лимит проверяется по текущему размеру таблицы в момент начала операции INSERT. Это означает, что INSERT, который увеличивает количество строк в таблице с трех до четырех, может завершиться. Следующий INSERT видит, что таблица уже превысила лимит, и отклоняется:
Мы также можем ограничить таблицу по ее сжатому или несжатому размеру с помощью параметров max_table_size_bytes_compressed и max_table_size_bytes_uncompressed.
Мы можем установить ограничение и на количество таблиц в базе данных. Давайте создадим базу данных, которая может содержать две таблицы:
Мы можем создать первые две таблицы как обычно:
Но если мы попытаемся создать третью таблицу, ClickHouse отклонит ее:
Концептуально оператору DISTINCT нужно отслеживать уже увиденные значения. В ClickHouse есть несколько оптимизаций, снижающих объем этой работы, но запрос с высокой кардинальностью все равно может потребовать поддержания большого набора в памяти.
В ClickHouse 26.9 этот хэш-набор можно вытеснять на диск (spill to disk) — точно так же, как GROUP BY и ORDER BY, вместо того чтобы позволять ему расти до тех пор, пока в запросе не закончится память.
Давайте посмотрим, как это выглядит, вернув все уникальные значения в последовательности из 100 миллионов чисел и используя max_memory_usage для ограничения памяти запроса до 150 МБ:
Запрос не может быть обработан, так как памяти недостаточно. Мы можем разрешить DISTINCT сбрасывать промежуточное состояние на диск с помощью параметра max_bytes_before_external_distinct:
На этот раз ClickHouse начинает записывать данные DISTINCT во временные файлы при достижении примерно 25 МБ. Весь запрос может использовать до 150 МБ, оставляя достаточно памяти для чтения и слияния этих файлов в конце.
Есть еще один важный момент, который следует учитывать при использовании этой функции. Сброс на диск снижает потребление памяти, но не устраняет потребность в ней полностью. ClickHouse по-прежнему нужна память для буферов, конвейера запросов и слияния временных файлов, вытесненных на диск.
Давайте посмотрим, что произойдет, если мы уменьшим общий лимит памяти для запроса до 100 МБ, сохранив порог сброса на диск на уровне 25 МБ:
Данные были успешно вытеснены, но у ClickHouse закончилась память при чтении временных файлов обратно. Судя по BufferingFromFileSource, ошибка произошла во время обработки файлов, сброшенных на диск, а не при создании исходного набора в памяти.
Чтобы исправить это, нам нужно вернуть max_memory_usage к значению 150 МБ.
ClickHouse автоматически включает внешний DISTINCT (external DISTINCT), когда параметру max_bytes_ratio_before_external_distinct задано значение 0.5. Это означает, что DISTINCT начинает вытеснение данных на диск при достижении половины доступной памяти.
ClickHouse автоматически включает внешний DISTINCT (external DISTINCT), когда параметру max_bytes_ratio_before_external_distinct задано значение 0.5. Это означает, что DISTINCT начинает вытеснение данных на диск при достижении половины доступной памяти.
В ClickHouse 26.9 добавлений синтаксис квадратных скобок для доступа к путям в значении JSON. Это упрощает написание вложенных путей и работу с ключами, содержащими такие символы, как точки или пробелы.
Давайте посмотрим, как это работает, на примере в памяти:












