Перейти к основному содержанию
ClickHouse Connect может декодировать результаты запросов и кодировать вставки с помощью скомпилированного кодека на Rust вместо реализации на Python и Cython, используемой по умолчанию. Кодек на Rust применяется только к управляемому клиентом трафику FORMAT Native: это query, query_np, query_df, их варианты с потоковой передачей блоков и строк, а также вставки, включая insert_df. Методы Arrow используют FORMAT Arrow и не затрагиваются, равно как и raw-запросы, raw-вставки и форматы, отличные от Native. Кодек является экспериментальным и включается явно. По умолчанию используется кодек на Python.

Установка

Скомпилированный кодек поставляется в виде отдельного wheel-пакета clickhouse-connect-core, содержащего модуль расширения _ch_core. Установите кодек вместе с интеграциями, которые использует ваше приложение:
Основные wheel-пакеты публикуются для CPython версий с 3.10 по 3.14, а также для CPython 3.14t со свободной многопоточностью (free-threaded) на Linux, macOS и Windows. В Python со свободной многопоточностью импорт основного расширения не приводит к повторному включению GIL. Для обычного вывода в NumPy и Pandas средствами Rust не требуется PyArrow или какой-либо другой пакет Arrow. Это относится к query_np, query_df, их потоковым вариантам, а также к query(..., use_numpy=True). Для запросов NumPy пакет Pandas не нужен. Чтобы использовать асинхронный клиент, добавьте дополнительный модуль async. PyArrow по-прежнему необходим для методов Arrow и хранилища Pandas на базе Arrow. Чтобы использовать эти возможности, установите дополнительный модуль arrow:
Вывод строк в расширенном режиме Pandas определяется параметром Pandas mode.string_storage. Если установлен PyArrow, строки преобразуются существующим механизмом Arrow с полной проверкой UTF-8 для любого бэкенда хранилища. Без PyArrow драйвер использует объектное преобразование на Rust, а хранилище Pandas по умолчанию использует строки Python. Явный запрос хранилища строк Arrow без установленного PyArrow вызывает ошибку зависимости. Некорректные данные UTF-8, как и прежде, отображаются в шестнадцатеричном виде при любом бэкенде хранилища. Кодек, его упаковка и набор зависимостей являются экспериментальными. Прямое преобразование буферов теперь поддерживает столбцы с типами non-nullable 8–64-битных целых чисел, Float32/64, Boolean, BFloat16, Interval, Date, Date32, DateTime и DateTime64. Скалярные столбцы Time и Time64, в том числе со значениями Nullable, также используют прямые буферы — как и Nullable BFloat16, а также вывод целых чисел, чисел с плавающей точкой и Interval с типом Nullable в расширенном режиме Pandas. Для BFloat16 сохраняются вывод в float32 и прежняя политика обработки отсутствующих значений Pandas Float32. Значения Interval по-прежнему представляются знаковыми счётчиками в объявленной единице измерения. При преобразовании временных типов сохраняются объявленные единицы и прежние правила обработки часовых поясов. Nullable-псевдонимы SimpleAggregateFunction для Time64 теперь возвращают корректные длительности и NaT для значений NULL. Скалярные столбцы Nullable DateTime64(9) также используют буферы, что позволяет сохранять наносекунды в выводе NumPy и Pandas. Для остальных дат и временных меток с типом Nullable сохраняются прежние пути объектного преобразования. Массивы Time/Time64, включая вложенные массивы и листовые элементы Nullable, а также LowCardinality(Time), в том числе со значениями Nullable, также используют прямые буферы. Вложенность, единицы длительности и представление NULL для них не меняются. LowCardinality(Time64) по-прежнему не поддерживается ядром на Rust. Для скалярных столбцов Nullable DateTime64(9) функции query_df и query_df_stream сохраняют все девять знаков дробной части, в том числе при использовании псевдонимов SimpleAggregateFunction. В Pandas 3 столбцы даты и времени в выводе по умолчанию теперь используют наносекунды вместо микросекунд. Прежний вывод типов с объектным dtype для блоков, состоящих только из NULL, не меняется. Если из-за именованного часового пояса значение выходит за пределы наносекундного диапазона Pandas, для этого столбца сохраняются прежнее объектное преобразование и прежнее поведение при выходе за диапазон. Для этих столбцов объектные поля в query_np и query_np_stream теперь содержат ячейки numpy.datetime64 вместо ячеек Python datetime, а для SQL NULL — None. Значения с именованным часовым поясом представляются тиками UTC без метаданных часового пояса — так же, как в кодеке на Python. Это позволяет сохранять наносекунды без Pandas. Если вам нужны временные метки с учётом часового пояса, используйте query_df. Типы ячеек NumPy для остальных временных столбцов с типом Nullable не меняются. Запросы NumPy и Pandas для скалярных DateTime64 с точностью, отличной от 0, 3, 6 и 9, завершаются ошибкой ProgrammingError — в том числе для столбцов с типом Nullable и псевдонимов SimpleAggregateFunction. Если выбран кодек на Rust, а скомпилированный модуль не установлен, при создании клиента возникает ошибка NotSupportedError с указанием этой команды установки.

Включение кодека

Выберите кодек с помощью клиентской опции native_codec:
Допустимые значения: Значение по умолчанию также можно задать через общую настройку native_codec или переменную окружения CLICKHOUSE_CONNECT_NATIVE_CODEC. Наивысший приоритет имеет именованный аргумент клиента, затем общая настройка и, наконец, переменная окружения. Для клиентов с interface="chdb" этот параметр игнорируется — они всегда используют кодек на Python.

Когда использовать кодек на Rust

Кодек на Rust наиболее полезен для больших результатов в виде DataFrame с текстовыми, контейнерными и составными типами, такими как String, LowCardinality, Map, Array, JSON, Decimal и UUID. Он также может ускорить работу с большими потоками блоков строк и столбцов, параллельными рабочими нагрузками из запросов и массовыми вставками. Для небольших результатов и запросов, ограниченных пропускной способностью сети, разница может быть незначительной. Простые числовые результаты уже обрабатываются эффективными пакетными путями в кодеке на Python, поэтому выигрыш для них тоже будет меньше. Прямые буферы позволяют обойтись без промежуточных массивов Arrow для поддерживаемых типов. При итоговой сборке результата NumPy и Pandas по-прежнему возможны выделение памяти и копирование данных, поэтому не каждый результат запроса становится zero-copy. Без PyArrow преобразование строк в Rust создаёт строковые объекты Python из каждого декодированного блока. При размерах блоков по умолчанию пиковое потребление памяти для больших результатов типа String сопоставимо с кодеком на Python. Очень большие блоки, например один блок с миллионом строк, удерживают в памяти весь декодированный блок, пока создаются его строки, и могут требовать больше пиковой памяти, чем кодек на Python. Для больших результатов Pandas StringDtype установите дополнительный модуль arrow и используйте хранение строк в Arrow. Pandas 3 по умолчанию выбирает хранение в Arrow, если установлен PyArrow. Для Pandas 2 требуется pd.options.mode.string_storage = "pyarrow". Буферизованные вызовы query() для очень широких или полностью числовых результатов сейчас могут работать медленнее и давать более высокое пиковое потребление памяти при использовании кодека на Rust. Для таких рабочих нагрузок предпочтительнее query_df, query_row_block_stream или query_column_block_stream. Для NumPy и Pandas снизить пиковое потребление памяти также помогают query_np_stream и query_df_stream. Обрабатывайте и освобождайте каждый блок, а не накапливайте весь поток. Потребление памяти по-прежнему зависит от размера каждого блока, поэтому стриминг одного большого блока не даёт такого же преимущества. Прежде чем переходить на этот кодек, протестируйте его на собственной рабочей нагрузке. При замерах используйте native_codec="rust_strict", чтобы неподдерживаемая опция или отсутствующая зависимость приводили к исключению, а не к незаметному перенаправлению запроса в Python.

Правила fallback

Решение о fallback принимается до того, как будут прочитаны или отправлены какие-либо байты, поэтому переключение кодека посреди потока невозможно. Для запросов выбор делается до чтения тела ответа. Для вставок кодировщик на Rust выбирается только в том случае, если поддерживаются все типы столбцов; иначе вся вставка выполняется на кодеке на Python. Когда активен режим naive_datetime_insert="server", режим rust направляет вставку, содержащую хотя бы один столбец типа DateTime или DateTime64, на кодек на Python, чтобы применялся объявленный timezone столбца или server timezone. rust_strict отклоняет такое сочетание. Режим по умолчанию naive_datetime_insert="local" по-прежнему использует кодировщик на Rust. Внутренние запросы драйвера к метаданным, включая команды reflection диалекта SQLAlchemy, всегда используют кодек на Python — незаметно для пользователя и во всех режимах. Некорректные полезные нагрузки Native, обнаруженные кодеком Rust, приводят к ошибке DataError.

Versioning

clickhouse-connect-core версионируется независимо от clickhouse-connect. Дополнительный модуль rust требует clickhouse-connect-core>=0.2.1,<0.3. При выборе кодека на Rust драйвер проверяет как версию binding API, так и поддержку буфера столбцов. В опубликованном wheel-пакете core 0.2.0 поддержка буфера столбцов отсутствует. Если установленный wheel-пакет несовместим, при создании клиента как для rust, так и для rust_strict возникает NotSupportedError со следующей командой обновления:
Исправления и повышение производительности скомпилированного кодека выходят в составе релизов clickhouse-connect-core, и их можно получить, обновив wheel-пакет core до совместимой версии. Изменения в интеграции драйвера с Rust требуют обновления clickhouse-connect.

Известные различия в поведении

Кодек на Rust нацелен на побитовое совпадение с кодеком на Python вплоть до каждой ячейки. Ниже перечислены известные различия.
  • Результаты query_np и query_df для столбцов Variant содержат обычные объекты Python, а не скалярные значения numpy. Сами значения совпадают, различаются типы ячеек.
  • Значения Dynamic, содержащие Time64, материализуются как datetime.timedelta в результатах query_np и query_df кодека на Rust. При scale 0, 3, 6 и 9 значения совпадают с ячейками numpy.timedelta64 кодека на Python, но типы ячеек различаются. При других значениях scale кодек на Rust возвращает datetime.timedelta, тогда как кодек на Python выбрасывает ProgrammingError, поскольку в NumPy нет подходящей единицы измерения. Метаданные членов Dynamic после декодирования на Rust драйверу не передаются, поэтому используйте native_codec="python", если нужны типы ячеек NumPy или проверка неподдерживаемых значений scale.
  • Для query_df кодек на Python может преобразовывать в строки составные значения, хранящиеся в общих данных JSON. Кодек на Rust возвращает декодированные объекты, что соответствует результатам query_np обоих кодеков.
  • Альтернатива LowCardinality внутри контейнера, который материализуется поячеечно, например Array(Variant(...)), даёт равные по значению ячейки, которые не разделяют идентичность объекта на слот словаря, характерную для кодека на Python.
  • Столбцы Nullable(Tuple(...)) с одним или несколькими элементами кодек на Rust декодирует корректно. Кодек на Python читает такую структуру неверно, поэтому эталонным считается результат Rust. Оба кодека поддерживают Nullable(Tuple()).
  • rust_strict отклоняет параметры запроса, не реализованные в Rust-пути, например пользовательские query_formats для отдельного запроса, вместо того чтобы незаметно менять поведение.
  • Ошибки преобразования и валидации при вставке в Rust могут приводить к DataError там, где кодек на Python выбрасывает ValueError, при этом текст сообщения может отличаться. Например: недопустимые значения Time и Time64, IPv6-адреса, размерности QBit, длины FixedString, строки Float64, а также попытки вставить элементы в столбец Tuple().
  • Кодировщик на Rust отклоняет b"" для FixedString(N) и числовые строки вида "2" для целочисленных столбцов. Кодек на Python дополняет пустое значение FixedString нулями и приводит числовые строки к нужному типу.
  • Кодек на Rust декодирует некоторые значения общих вариантов Dynamic в соответствующие типы Python, тогда как кодек на Python оставляет значение в виде сырых байтов. Например, сохранённое значение Decimal из Rust вернётся как decimal.Decimal, а из Python — как двоичное значение.
  • Для значений Date в общем хранилище Dynamic методы query_np и query_df возвращают ячейки datetime.date при использовании кодека на Rust и ячейки numpy.datetime64 при использовании кодека на Python. Из query оба кодека возвращают datetime.date.