Асинхронные вставки
Классическая проблема при высокочастотной загрузке данных в ClickHouse — “too many parts”. Если клиент отправляет тысячи мелких INSERT в секунду, каждый INSERT создаёт отдельный part на диске. Фоновый merge не успевает объединять их — число parts растёт, производительность падает, и ClickHouse начинает отклонять вставки с ошибкой Too many parts.
Решение — async_insert=1: ClickHouse накапливает данные из нескольких клиентских INSERT в буфере и делает одну физическую запись на диск единым батчем.
Как работает async_insert
При async_insert=1 клиентский INSERT не записывает данные немедленно. Вместо этого:
- Данные попадают в in-memory буфер на стороне сервера
- Буфер накапливается до достижения порогового размера (
async_insert_max_data_size) или таймаута (async_insert_busy_timeout_max_ms) - Когда порог достигнут — весь буфер записывается как единый part
Ключевые настройки
Включение и подтверждение
-- Включить async insert для сессии
SET async_insert = 1;
-- Ждать подтверждения от сервера (durability mode)
SET wait_for_async_insert = 1;
-- Не ждать подтверждения (fire-and-forget, максимальный throughput)
SET wait_for_async_insert = 0;
Настройки можно задавать на уровне пользователя в конфиге сервера или передавать в каждом запросе.
Параметры буфера и таймаутов
<!-- /etc/clickhouse-server/users.d/async_insert.xml -->
<clickhouse>
<profiles>
<default>
<!-- Включить async insert по умолчанию (26.3 LTS: включён глобально) -->
<async_insert>1</async_insert>
<!-- Ждать подтверждения (рекомендуется для durability) -->
<wait_for_async_insert>1</wait_for_async_insert>
<!-- Максимальный размер буфера: 10 МБ -->
<async_insert_max_data_size>10485760</async_insert_max_data_size>
<!-- Адаптивный таймаут сброса буфера: до 1 с (26.3 LTS) -->
<async_insert_busy_timeout_max_ms>1000</async_insert_busy_timeout_max_ms>
</default>
</profiles>
</clickhouse>
Адаптивный таймаут (async_insert_busy_timeout_max_ms, введён в 26.3 LTS) — ClickHouse адаптивно увеличивает задержку сброса буфера при низкой нагрузке. При высокой нагрузке буфер сбрасывается быстро; при низкой — небольшая задержка позволяет собрать больше данных в один batch.
Дедупликация при retry
При network failure клиент не знает, был ли INSERT успешным. Повторная отправка может создать дубли. Для идемпотентных вставок используется insert_deduplication_token:
-- Каждая вставка получает уникальный токен
INSERT INTO events SETTINGS
async_insert = 1,
wait_for_async_insert = 1,
insert_deduplication_token = 'batch-2026-01-15-001'
VALUES (...);
-- При retry с тем же токеном — вставка пропускается (идемпотентность)
INSERT INTO events SETTINGS
async_insert = 1,
wait_for_async_insert = 1,
insert_deduplication_token = 'batch-2026-01-15-001'
VALUES (...); -- дубль пропущен
Токен хранится в Keeper в узле blocks/ таблицы. Если блок с таким токеном уже есть — ClickHouse возвращает успех без повторной записи.
ClickHouse 26.3 LTS: async_insert по умолчанию
Начиная с ClickHouse 26.3 LTS, async_insert = 1 — значение по умолчанию для пользовательского профиля default. Это значит: HTTP-интерфейс, native protocol и любые клиенты, которые явно не выставляли async_insert = 0, автоматически получают server-side batching без изменения клиентского кода.
Что значит “по умолчанию” в 26.3
В предыдущих LTS (24.3, 24.8) async_insert был opt-in — нужно было ставить SET async_insert = 1 или прописывать в profile XML. В 26.3 переключение симметрично: чтобы вернуть классическое поведение per-INSERT-part, теперь нужно явно SET async_insert = 0. Дефолт сместился.
Этот сдвиг даёт три следствия для миграции с 24.x → 26.3:
- High-frequency clients перестают получать “Too many parts” — без изменений в коде. Это первая и главная причина переключения дефолта.
wait_for_async_insert = 1остаётся дефолтом — сервер ждёт физического flush перед200 OK. Durability-семантика классического sync-INSERT сохранена.- Latency распределения per-INSERT увеличивается на величину busy-timeout (по умолчанию 200ms-1s в зависимости от нагрузки) — адаптивный таймаут стартует с малого значения и растёт при низкой частоте вставок.
Batching policy и flush triggers
В 26.3 буфер сбрасывается при выполнении любого из трёх условий (whichever comes first):
| Триггер | Параметр | Дефолт 26.3 |
|---|---|---|
| Размер накопленных данных | async_insert_max_data_size | 10 MiB |
| Количество накопленных запросов | async_insert_max_query_number | 450 |
| Таймаут с момента первой записи в буфер | async_insert_busy_timeout_max_ms | 1000 ms (адаптивно) |
Адаптивный таймаут (async_insert_busy_timeout_max_ms) — ключевая фича 26.3: ClickHouse начинает с малой задержки (~50ms) и адаптивно увеличивает её, если входящих запросов мало. При взрывной нагрузке таймаут падает до минимума, и flush происходит почти моментально по data-size триггеру.
Server-side dedup для async insert
Insert deduplication для async-вставок работает на уровне финального merged-блока (не отдельных клиентских INSERT). После сборки batch ClickHouse считает hash объединённых данных и сверяет его с deduplication-окном в Keeper. Это означает: два одинаковых клиентских INSERT, попавшие в один batch, будут дедуплицированы как часть batch hash, но если они попали в разные batch — они станут разными blocks, и для гарантии нужна явная insert_deduplication_token (см. секцию выше).
-- Включить дедупликацию для async вставок (включена по умолчанию в 26.3)
SET async_insert_deduplicate = 1;
-- Размер deduplication-окна на уровне Keeper (число последних блоков)
-- Применяется к target ReplicatedMergeTree, не к async-buffer:
ALTER TABLE events MODIFY SETTING
replicated_deduplication_window = 1000,
replicated_deduplication_window_seconds = 604800;
wait_for_async_insert=0 означает, что сервер подтверждает получение данных до их физической записи на диск. При сбое сервера после подтверждения, но до сброса буфера — данные будут потеряны. Используйте wait_for_async_insert=1 (это дефолт в 26.3) для сценариев, где потеря данных недопустима (финансовые транзакции, события безопасности).
Мониторинг async insert
-- Посмотреть текущее состояние async insert буферов
SELECT *
FROM system.asynchronous_insert_log
ORDER BY event_time DESC
LIMIT 10;
-- Количество успешных и неудачных async inserts
SELECT
status,
count() AS cnt,
sum(rows) AS total_rows
FROM system.asynchronous_insert_log
WHERE event_time > now() - INTERVAL 1 HOUR
GROUP BY status;
Ключевые выводы
async_insert=1решает проблему “too many parts” при высокочастотных вставках: данные буферизуются на сервере и записываются единым batch.wait_for_async_insert=1— durability mode: клиент ждёт подтверждения записи.=0— fire-and-forget с риском потери данных при сбое.insert_deduplication_tokenобеспечивает идемпотентность при retry-логике клиента.async_insert_busy_timeout_max_ms(26.3 LTS) — адаптивный таймаут: при высокой нагрузке буфер сбрасывается быстро, при низкой — небольшая задержка улучшает batching.- В ClickHouse 26.3 LTS
async_insert = 1— значение по умолчанию для профиляdefault. Чтобы вернуть классическое поведение, нужно явноSET async_insert = 0. Flush-триггеры: 10 MiB / 450 запросов / 1000ms адаптивного таймаута — whichever comes first. Insert deduplication работает на уровне merged-блока batch (не клиентских INSERT) — для строгой идемпотентности на уровне отдельной вставки используйтеinsert_deduplication_token.