ClickHouse в 2026 году по‑прежнему отлично подходит для быстрых агрегатов по большим объёмам данных. Но в production почти всегда упираются в два ресурса: дисковую подсистему и оперативную память. И чаще всего это проявляется не как «медленный диск», а как внезапные провалы по SELECT, рост задержек на INSERT и ошибки вроде too many parts.
Ниже — практическая эксплуатация на VDS: как MergeTree работает на уровне parts, что делают background merges, почему «много мелких кусков» ломает и чтение, и вставки, как читать системные таблицы, и как не загнать сервер в OOM из‑за лимитов max_memory_usage, overcommit и кэшей (mark/uncompressed cache).
Ментальная модель: что делает SELECT в MergeTree
Чтобы тюнить SELECT, важно понимать простую вещь: MergeTree читает не «таблицу», а набор parts (частей). Каждый part — самостоятельный кусок данных со своими файлами столбцов, индексом (primary key), метками (marks) и статистикой.
В типовом запросе ClickHouse делает следующее:
- выбирает, какие parts нужно затронуть (партиции + индекс);
- для каждого part определяет диапазоны marks, которые надо читать;
- читает нужные столбцы, разжимает, применяет фильтры, агрегации/JOIN/сортировки;
- упирается в I/O (много мелких чтений по marks) и в память (хэши агрегаций, JOIN, сортировки).
Вывод для эксплуатации: даже идеально написанный запрос начнёт деградировать, если parts стало слишком много или merges не успевают «склеивать» их в более крупные части.
Почему too many parts — это не только про вставки
Симптом too many parts часто воспринимают как «проблема вставок»: частые маленькие INSERT создают множество частей, и система начинает тратить ресурсы на их обслуживание. Но это напрямую бьёт и по чтению.
- Планирование запроса: больше parts — больше работы на этапе подготовки, больше накладных расходов на диспетчеризацию.
- Чтение по marks: множество маленьких частей превращают чтение в большое число маленьких I/O (особенно на сетевых/загруженных дисках).
- Эффективность кэшей: рабочий набор размазывается по множеству файлов — hit-rate mark/uncompressed cache падает.
Если parts растут быстрее, чем merges их схлопывают, вы почти всегда увидите цепочку: сначала падает скорость вставок, затем «вдруг» начинают плавать задержки
SELECTдаже на старых запросах.

Если вы упёрлись в диск или память и merges стабильно «не успевают», чаще всего проще и дешевле решить проблему ресурсами: добавить CPU/RAM и перейти на более быстрые диски на уровне тарифа VDS.
Background merges: что делают и почему могут «не успевать»
Background merges — это фоновое слияние частей (parts) в более крупные. В норме merges уменьшают количество частей, улучшают локальность данных, снижают накладные расходы на индексы и делают чтение более предсказуемым.
Почему merges не успевают (частые причины на VDS):
- Слишком много мелких INSERT (например, каждые 1–2 секунды небольшая порция) без буферизации на стороне приложения/шины.
- Диск не тянет: merge — это чтение + запись, иногда с высокой конкуренцией за IOPS и пропускную способность.
- Конкуренция с SELECT: чтение и merges дерутся за I/O, latency начинает «пилить».
- Неудачная партиционизация: слишком мелкие партиции увеличивают число активных частей и уменьшают шанс на «крупные» merges.
- ORDER BY не соответствует фильтрам: merges не спасают, если запросы всё равно читают значительную долю данных.
Как быстро проверить состояние parts и merges
Начинайте с системных таблиц: они показывают реальную динамику и «узкое место», а не ощущения.
clickhouse-client -q "SELECT database, table, count() AS parts, sum(rows) AS rows, formatReadableSize(sum(bytes_on_disk)) AS size FROM system.parts WHERE active GROUP BY database, table ORDER BY parts DESC LIMIT 20"
clickhouse-client -q "SELECT database, table, elapsed, progress, num_parts, total_size_bytes_compressed FROM system.merges ORDER BY elapsed DESC LIMIT 20"
clickhouse-client -q "SELECT event_time, event_type, formatReadableSize(size) AS size, duration_ms, database, table, part_name FROM system.part_log WHERE event_type IN ('NewPart','MergeParts','MutatePart') ORDER BY event_time DESC LIMIT 50"
На что смотреть в первую очередь:
- Лидеры по parts в
system.parts: таблицы‑кандидаты на «генератор мелких частей». - Хвост merges в
system.merges: какие таблицы и какие объёмы сливаются долго и постоянно. - Баланс NewPart vs MergeParts в
system.part_log: если NewPart стабильно обгоняет MergeParts, деградация будет накапливаться.
INSERT performance: как не плодить мелкие parts
ClickHouse любит батчи. Если вставлять в production мелкими порциями, вы почти гарантированно придёте к тысячам/десяткам тысяч частей и деградации: merges станут постоянным фоном, а чтение начнёт «шуметь» задержками.
Практика, которая обычно даёт быстрый эффект:
- Укрупняйте батчи: группируйте вставки по размеру или по времени (в разумных пределах).
- Стабилизируйте частоту: ровная нагрузка почти всегда легче для merges, чем пики.
- Избегайте сверхмелких партиций: партиция «по часу» может быть нормальной, но при большом потоке превращается в генератор parts.
- Не размножайте INSERT по десяткам таблиц без нужды: parts и merges множатся линейно, а диск один.
Простейшая диагностика «вставки слишком мелкие»: в system.part_log много событий NewPart с маленьким размером, а merges не закрывают этот поток.
Память в SELECT: max_memory_usage, overcommit и «неочевидные» потребители
В ClickHouse «убийца» запросов чаще память, а не CPU. Риски по RAM повышают: агрегации с высокой кардинальностью, большие JOIN, сортировки, window‑функции, DISTINCT, чтение большого числа столбцов с распаковкой.
Удобно держать в голове три слоя потребления:
- Память запроса — то, что ограничивается
max_memory_usageи связанными лимитами. - Кэши — mark cache и uncompressed cache: ускоряют чтение, но «конкурируют» за RAM с запросами.
- Фон — merges, мутации, служебные структуры сервера и память ОС.
max_memory_usage: почему «поставили побольше» иногда хуже
max_memory_usage ограничивает RAM на один запрос. Ошибка эксплуатации — выкрутить лимит «чтобы не падало». Тогда один тяжёлый отчёт может съесть почти всю память, вытеснить кэши, создать давление на память и спровоцировать лавинообразную деградацию для всех остальных запросов.
Рабочая схема в production:
- задайте реалистичный лимит на запрос для приложения/обычных пользователей;
- для админских сессий/ночных задач используйте отдельный профиль с повышенными лимитами;
- учитывайте конкуренцию: два параллельных запроса по 50–60% RAM хуже, чем один на 80%.
Overcommit: как ClickHouse выбирает, кого «урезать»
Overcommit (в терминах настроек ClickHouse) нужен, чтобы при общем дефиците памяти сервер мог отменить один или несколько запросов и не довести систему до неконтролируемого OOM на уровне ОС.
Для production лучше контролируемо отменить один тяжёлый запрос, чем потерять весь инстанс и получить длинное восстановление.
Симптомы, которые часто указывают именно на память:
- скачки latency на
SELECTпри росте параллелизма; - ошибки превышения памяти на части запросов;
- заметное падение hit-rate кэшей после «тяжёлых отчётов»;
- появление активного swap (для ClickHouse это почти всегда сигнал «пересмотреть лимиты/запросы»).
Mark cache и uncompressed cache: когда ускоряют SELECT, а когда мешают
Два кэша, с которыми чаще всего «перебарщивают» на небольших серверах:
- mark cache — кэш меток (позиций) для чтения по первичному ключу и пропуска ненужных гранул. Обычно даёт пользу почти всегда, но размер должен быть разумным.
- uncompressed cache — кэш разжатых блоков. Может резко ускорять повторяющиеся запросы, но потребляет заметно больше памяти.
На VDS типичная ошибка — увеличить кэши «как в рекомендациях из интернета», не учитывая профиль запросов. Если RAM мало, кэши начинают конкурировать с рабочей памятью запросов и merges, что даёт нестабильность: кэш то вытесняется, то снова заполняется.
Быстрая проверка текущего потребления кэшей:
clickhouse-client -q "SELECT metric, value FROM system.metrics WHERE metric IN ('MarkCacheBytes','UncompressedCacheBytes')"
clickhouse-client -q "SELECT metric, value FROM system.asynchronous_metrics WHERE metric IN ('MarkCacheSize','UncompressedCacheSize')"
Если кэши постоянно упираются в лимит, а запросы преимущественно «холодные» (редкие отчёты за большие диапазоны), часто выгоднее отдать память под запросы и merges, а кэш держать умеренным.

Если ClickHouse у вас живёт рядом с веб‑частью/дашбордами и нагрузка растёт, на практике помогает разнести роли и держать веб‑проекты на виртуальном хостинге, а базу — на отдельном сервере.
Parts и SELECT: чеклист, что чинить первым
Когда в production «просел SELECT», не начинайте с тюнинга отдельных запросов. Сначала проверьте базовые контуры: parts, merges, диск, память.
-
Сколько активных parts? Десятки тысяч на таблицу — уже повод разбираться, сотни тысяч — почти всегда причина деградации.
clickhouse-client -q "SELECT table, count() AS parts FROM system.parts WHERE active AND database = currentDatabase() GROUP BY table ORDER BY parts DESC" -
Успевают ли merges? Если merges постоянно в работе, а parts продолжают расти — ищите проблему во вставках, диске или партиционизации.
-
Есть ли конкуренция за диск? Heavy SELECT + heavy merges на одном диске обычно дают «пилу» по latency. Решения: ограничение параллелизма, вынос тяжёлых отчётов по расписанию, иногда разнос нагрузок по узлам.
-
Хватает ли памяти запросам? Частые отмены по памяти и активный swap почти всегда означают, что лимиты/профили запросов и кэши настроены без запаса.
Мини-рукбук: что делать при инциденте «SELECT стал медленным»
Если нужно быстро восстановить качество сервиса, действуйте последовательно и фиксируйте наблюдения (время, таблицы, симптомы):
Проверьте, не идёт ли «шторм merges» и не растёт ли число parts у ключевых таблиц.
-
Посмотрите топ запросов по времени/памяти: это один «монстр» или системная деградация.
clickhouse-client -q "SELECT query_duration_ms, read_rows, read_bytes, memory_usage, query FROM system.query_log WHERE type='QueryFinish' AND event_time > now() - INTERVAL 15 MINUTE ORDER BY query_duration_ms DESC LIMIT 10" Если память на грани — временно снизьте параллелизм тяжёлых запросов и ограничьте ресурсы проблемного пользователя/приложения (лучше точечно, чем глобально).
Если причина в inserts/parts — приоритизируйте исправление пайплайна вставок (батчи, буферизация, пересмотр партиций). Иначе инцидент вернётся.
Что ещё полезно прокачать на VDS
Если ClickHouse работает рядом с веб‑частью (API/дашборды), полезно отдельно навести порядок с кэшированием на фронте/прокси, чтобы сгладить пики и снизить число тяжёлых запросов в базу. По смежной теме может пригодиться разбор про кэширование в PHP на Memcached и Redis.
Итоги
Производительность SELECT в ClickHouse на VDS почти всегда определяется тремя вещами: количеством parts (MergeTree), темпом и качеством background merges, и тем, как вы управляете памятью (лимиты max_memory_usage, механизмы overcommit и баланс кэшей mark/uncompressed cache). Держите эти контуры под контролем — и запросы станут предсказуемыми, а тюнинг отдельных SQL превратится в аккуратную настройку, а не борьбу с симптомами.


