Перейти к содержанию

ClickHouse: подключение

hare работает с ClickHouse — колоночной базой для аналитики — через любой из двух интерфейсов сервера; у каждого своя библиотека:

engine / схема адреса базы Драйвер Установка
clickhouse+clickhouse-connect асинхронный клиент clickhouse-connect, HTTP-интерфейс (порт 8123) pip install "hare-orm[clickhouse-connect]"
clickhouse+clickhouse-driver clickhouse-driver, родной протокол поверх TCP (порт 9000) pip install "hare-orm[clickhouse-driver]"
await Hare.init(
    config={
        "connections": {"analytics": "clickhouse+clickhouse-connect://default:secret@clickhouse.local:8123/analytics"},
        "apps": {"analytics": {"models": ["library.analytics"], "default_connection": "analytics"}},
    }
)

Для родного протокола меняются только схема и порт — модели, запросы и SQL остаются теми же:

config = {"connections": {"analytics": "clickhouse+clickhouse-driver://default:secret@clickhouse.local:9000/analytics"}}

Диалект — один из встроенных диалектов hare, построенный только на хуках диалектов и Features, как был бы построен диалект стороннего пакета; как и другие встроенные, он импортируется, только когда его называет соединение или поиск по имени. Он работает с ClickHouse 25.8 и новее.

Настройки

Путь URL называет базу данных; порт — порт интерфейса драйвера: по умолчанию 8123 для clickhouse+clickhouse-connect и 9000 для clickhouse+clickhouse-driver.

Настройка Что задаёт
secure TLS: HTTPS вместо HTTP, защищённый порт родного протокола (на сервере с настройками по умолчанию — 9440, его нужно указать в URL). По умолчанию false.
compress Сжатие данных: ответов HTTP, блоков родного протокола (LZ4). По умолчанию true.
connect_timeout Секунды ожидания соединения, больше 0 и не больше 3600. По умолчанию 10.
send_receive_timeout Сколько секунд может выполняться команда, больше 0 и не больше 86400. По умолчанию 300. Более долгую команду останавливает сервер (max_execution_time, OperationalError с кодом 159) на любом драйвере — HTTP-клиент ждёт его ответа на секунду дольше; собственный settings(max_execution_time=...) команды главнее.
native_port Порт родного протокола, на котором hare dbshell запускает clickhouse-client, от 1 до 65535. По умолчанию: для clickhouse+clickhouse-connect — порт самого клиента (9000, 9440 с secure), для clickhouse+clickhouse-driver — порт самого подключения.
max_size Только clickhouse+clickhouse-driver: наибольшее число соединений клиента — столько команд он выполняет одновременно, от 1 до 1024. По умолчанию 16.
cluster Имя кластера, в который входит сервер: каждая команда схемы выполняется ON CLUSTER на нём, а журнал миграций реплицируется — см. Кластер. По умолчанию нет.
async_insert Сервер собирает вставки многих команд и пишет их вместе (async_insert=1) — для множества мелких вставок. По умолчанию false.
wait_for_async_insert С async_insert: вставка возвращается, когда её строки записаны, а ошибка поднята (wait_for_async_insert=1); false возвращается сразу — до записи строк и без ошибки. По умолчанию true.
transactions atomic() выполняет транзакцию ClickHouse — на сервере с ClickHouse Keeper и allow_experimental_transactions; проверяется при открытии соединения. См. Транзакции и блокировки. По умолчанию false.
keeper_hosts Серверы ClickHouse Keeper, в которых hare берёт блокировку миграций migrate, — host[:port] через запятую (порт по умолчанию 9181). Сессия открывается при открытии соединения — ConfigurationError, если ни один сервер её не открыл. По умолчанию нет.

С чем выполняется каждая команда

Каждая команда выполняется с такими настройками:

Настройка Зачем
join_use_nulls=1 LEFT JOIN без подходящей строки читает NULL, как в SQL, а не значения колонок по умолчанию.
mutations_sync=2, lightweight_deletes_sync=2 UPDATE или DELETE завершён, когда команда вернулась.
session_timezone='UTC' Моменты читаются и записываются в UTC.
max_query_size=1 ГиБ, max_ast_elements и max_expanded_ast_elements=100 000 000 Почти каждое значение команды вписывается в её текст (длинный список __in при чтении уходит внешней таблицей — см. Длинные списки __in); ограничения по умолчанию (256 КиБ текста) не пропускают длинный список __in при записи или UPDATE, записывающий большой JSON-документ.
allow_simdjson=0 Текст JSON читает разборщик, который принимает целое больше 64 бит за число, — разборщик по умолчанию не читает ни одного пути документа с таким числом.
input_format_json_read_bools_as_numbers=0 Логическое значение JSON остаётся логическим — массив чисел и логических значений хранил бы true как 1.
input_format_try_infer_dates=0, input_format_try_infer_datetimes=0 Строка JSON, похожая на дату или момент, остаётся строкой — колонка JSON хранила бы её как дату, потеряв смещение.
cast_string_to_dynamic_use_inference=0, cast_string_to_variant_use_inference=0 Текст, приводимый к значению Dynamic или Variant, остаётся текстом — сервер прочитал бы '5' как число.
json_type_escape_dots_in_keys=1 Точка в ключе значения JSON — часть ключа, а не вложенный путь.
allow_suspicious_types_in_order_by=1 По значению Dynamic (пути JSON) можно сортировать.
allow_suspicious_types_in_group_by=1 Группировка может взять такую сортировку — values("data__n").annotate(...).order_by("data__n").
calculate_text_stack_trace=0 Текст ошибки — сообщение сервера без его стека вызовов, который присылает собственный протокол.

Неверный пароль или пользователь и отсутствующая база подключения дают ConfigurationError на обоих драйверах, как в PostgreSQL: повтор этого не исправит; сервер, который отказывает в соединении или разрывает его, даёт DBConnectionError.

Значения и строки

Дата и момент времени хранятся с 1900-01-01 по 2299-12-31 (Date32, DateTime64); любое другое значение сервер без ошибки заменяет ближайшим из них. hare вместо этого отклоняет такое значение — и при записи, и в фильтре — с ValidationError. ClickhouseDatetimeField(precision=9) считает наносекунды в 64 битах и хранит моменты до 2262-04-11 23:47:16.854775 UTC — более поздний отклоняется так же. Диапазон задан в UTC: момент в пределах суток от его краёв, прочитанный в поясе, где местное время выходит за диапазон (1900-01-01 00:00 UTC в America/New_York), сервер переносит в Extract(), Trunc() и фильтре по части даты в этом поясе — держите такие моменты на сутки внутри диапазона.

bulk_create() загружает партию одной двоичной вставкой (Features.copies_bulk_inserts) — см. Массовая загрузка, — а строки моделей и values() читаются из кортежей, которые отдаёт библиотека, без словаря на каждую строку (Features.supports_positional_rows).

HTTP-интерфейс: clickhouse-connect

Каждая команда — это HTTP-запрос; clickhouse-connect сам держит HTTP-соединения и выполняет команды параллельных задач одновременно. Свои соединения он никому не показывает: get_pool_status() даёт UnSupportedError (Features.supports_pool_status выключен), а метрики пула это подключение не учитывают.

Родной протокол: clickhouse-driver

clickhouse-driver — синхронная библиотека, а соединение родного протокола выполняет одну команду за раз. hare выполняет каждую команду в рабочем потоке, а цикл событий ждёт её, не блокируясь; у каждого рабочего потока своё соединение, которое открывает его первая команда. max_size — число потоков: столько команд выполняется одновременно, остальные ждут очереди.

  • Состояние пула. get_pool_status() показывает открытые, занятые и ожидаемые соединения, их же считают метрики пула (Features.supports_pool_status).
  • Отменённая команда. Отмена задачи снимает её команду, если та ещё не началась. Уже отправленная команда выполняется на сервере до конца — её результат отбрасывается.
  • Закрытие. close() ждёт выполняющиеся команды до 10 секунд. После этого не начатые команды завершаются ошибкой DBConnectionError, а ещё выполняющаяся доходит до конца сама — тогда её поток закрывает своё соединение.
  • Команда с ошибкой. Библиотека закрывает соединение команды, завершившейся ошибкой; следующая команда этого потока открывает новое.
  • Пинг. Перед каждой командой библиотека спрашивает сервер, живо ли соединение, — это ещё один обмен с сервером на каждую команду. hare отправляет этот пинг только по соединению, которое простояло больше 10 секунд: занятое соединение начинает команду сразу, а закрытое сервером во время простоя незаметно открывается заново.
  • Моменты времени. Двоичная вставка записывает каждый момент целым числом тиков колонки сервера (DateTime64 любой точности, DateTime), и DateTime64 читается из тиков так же — точно, и до 1970 года, и до 2299-го. Сама библиотека переводит момент через число секунд с плавающей точкой, которое после 2242 года ошибается на микросекунду, а на Windows её перевод любого момента до 1970 года не работает.
  • compress нужны пакеты lz4 и clickhouse-cityhash — их ставит hare-orm[clickhouse-driver]. Для сервера на той же машине compress=false избавляет от работы по сжатию.

Выбор драйвера

Команда занимает на обоих драйверах примерно одинаковое время — его определяют работа сервера и разбор ответа самой библиотекой, а не транспорт (см. бенчмарк). Родной протокол открывает по соединению на рабочий поток и сообщает состояние пула; HTTP-интерфейс проходит через прокси и балансировщики, которые понимают только HTTP.