Вставка больших объёмов: батчи, а не строки

Главное правило загрузки: одна большая вставка лучше тысячи маленьких.

Батч (batch) — это одна вставка сразу многих строк (тысячи и более) одним INSERT, вместо множества вставок по одной строке.

Почему мелкие вставки — беда

Вспомним из раздела о MergeTree: каждый INSERT создаёт отдельный кусок (part) на диске. Вставили строку — получили крошечный part. Вставили миллион строк по одной — получили миллион крошечных кусков. Фоновое слияние не успевает их укрупнять, метаданных становится слишком много, и сервер отвечает ошибкой:

DB::Exception: Too many parts (300). Merges are processing significantly slower than inserts.

Это самая частая ошибка новичка в ClickHouse.

Как правильно: батчи

Собирайте данные в пачки и вставляйте большими блоками — тысячи или десятки тысяч строк за раз:

INSERT INTO events (event_date, user_id, value) VALUES
  ('2024-01-01', 1, 10.0),
  ('2024-01-01', 2, 12.5),
  ('2024-01-01', 3, 9.0),
  -- ... ещё тысячи строк в одном INSERT ...
  ('2024-01-01', 9999, 7.7);

Один такой INSERT создаст один аккуратный part вместо тысяч крошечных.

Загрузка из файлов

Чаще всего данные грузят не руками, а из файлов через clickhouse-client с указанием формата:

# Залить CSV-файл одним батчем
clickhouse-client \
  --query="INSERT INTO events FORMAT CSV" \
  < data.csv

# Или через HTTP
cat data.csv | curl 'http://localhost:8123/?query=INSERT%20INTO%20events%20FORMAT%20CSV' \
  --data-binary @-

Сколько строк в батче

Ориентир — от нескольких тысяч до сотен тысяч строк на вставку, и не чаще одного-двух INSERT в секунду на таблицу. Если данные идут потоком (по событию), их нужно буферизовать: накапливать в приложении или использовать таблицу-буфер / интеграционные движки (Kafka), которые сами собирают батчи.

Иллюстрация: число кусков

Сравним «по строке» и «батчами» для миллиона строк (чистый Python):

rows = 1_000_000
batch_size = 50_000
parts_one_by_one = rows          # каждая строка -> свой part
parts_batched = rows // batch_size
print("По одной строке -> кусков:", parts_one_by_one)
print("Батчами по 50000 -> кусков:", parts_batched)

Вывод:

По одной строке -> кусков: 1000000
Батчами по 50000 -> кусков: 20

Как работает под капотом

Слияние кусков — фоновый процесс с ограниченной пропускной способностью. Когда вставки порождают куски быстрее, чем слияние их укрупняет, число кусков растёт, и срабатывает защита «too many parts». Батчи держат число кусков низким, и слияние спокойно справляется.

Частые ошибки

  • INSERT в цикле по строке. Классический способ положить таблицу ошибкой «too many parts».
  • Слишком частые вставки. Даже батчи, идущие сто раз в секунду, создают много кусков — буферизуйте.
  • Огромный единичный батч в память. Миллиарды строк одним INSERT могут не влезть; грузите крупными, но разумными блоками.

Итоги

  • Каждый INSERT — отдельный кусок; мелкие вставки порождают слишком много кусков.
  • Вставляйте батчами по тысячи/десятки тысяч строк, нечасто.
  • Поток данных буферизуйте (приложение, Buffer-таблица, Kafka).
  • «Too many parts» — сигнал, что вставки слишком мелкие/частые.
Проверьте себя
1. Почему вставка по одной строке в ClickHouse — плохая идея?
AЭто запрещено синтаксисом SQL
BКаждый INSERT создаёт отдельный кусок, и их становится слишком много (too many parts)
CClickHouse не умеет вставлять одну строку
DОдна строка занимает больше места, чем батч
2. Как правильно загружать поток событий (по одному событию)?
AДелать INSERT на каждое событие сразу
BБуферизовать события и вставлять батчами
CОтключить движок MergeTree
DВставлять только по выходным