Learning Platform
Глоссарий Troubleshooting
Урок 13.05 · 30 мин
Продвинутый
memorymax_memory_usageexternal aggregationexternal sortMemoryTrackermax_bytes_before_external_group_bymax_bytes_before_external_sort

Настройка памяти и MemoryTracker

RAM — главный ресурс для выполнения аналитических запросов в ClickHouse. GROUP BY на больших таблицах, сортировки по большим результирующим наборам, сложные JOIN — всё это требует оперативной памяти. Ошибка MEMORY_LIMIT_EXCEEDED — одна из самых распространённых в production-системах. ClickHouse предоставляет гибкий набор настроек для управления потреблением памяти: от жёстких лимитов до механизма spill-to-disk, который позволяет завершить тяжёлый запрос без нехватки RAM.


max_memory_usage: лимит памяти на запрос

max_memory_usage ограничивает потребление оперативной памяти для одного запроса. При превышении лимита ClickHouse немедленно прерывает запрос с исключением MEMORY_LIMIT_EXCEEDED.

-- Установить лимит 10 GB для текущего запроса
SET max_memory_usage = 10000000000;

-- Запрос будет прерван при превышении 10 GB
SELECT
    user_id,
    count() AS events,
    sum(revenue) AS total_revenue
FROM large_events_table
GROUP BY user_id;

Значение 0 означает отсутствие лимита (поведение по умолчанию). Лимит задаётся в байтах.

Для ограничения на уровне пользователя (не отдельного запроса) используйте max_memory_usage_for_user:

-- Ограничить суммарное потребление памяти пользователем analyst
-- (применяется ко всем его одновременным запросам)
SET max_memory_usage_for_user = 8000000000;  -- 8 GB

Внешняя агрегация: max_bytes_before_external_group_by

По умолчанию GROUP BY операции выполняются в памяти. Если таблица слишком велика, GROUP BY завершится ошибкой. Настройка max_bytes_before_external_group_by включает механизм spill-to-disk: при превышении порога промежуточные данные агрегации выгружаются на диск, и запрос завершается без нехватки RAM.

-- Разрешить spill на диск при накоплении 10 GB промежуточных данных
SET max_bytes_before_external_group_by = 10000000000;

-- Теперь GROUP BY на большой таблице завершится, а не упадёт с OOM
SELECT
    user_id,
    count() AS session_count,
    sum(duration_sec) AS total_duration
FROM user_sessions_50gb
GROUP BY user_id;

Spill-to-disk делает запрос медленнее (операции с диском), но позволяет выполнить его при ограниченной RAM.

WARNING

Если max_bytes_before_external_group_by установлен, рекомендуется задать max_memory_usage как минимум в два раза больше этого значения. ClickHouse может использовать до 2x указанного порога для финальной агрегации прочитанных из диска данных. Например: если max_bytes_before_external_group_by = 10G, то max_memory_usage должен быть не менее 20G.


Внешняя сортировка: max_bytes_before_external_sort

Аналогичный механизм существует для операций ORDER BY. По умолчанию значение 0 (отключено). При включении промежуточные данные сортировки выгружаются на диск.

-- Разрешить spill на диск для ORDER BY при превышении 10 GB
SET max_bytes_before_external_sort = 10000000000;

-- ORDER BY на большом результирующем наборе не упадёт с OOM
SELECT *
FROM large_log_table
WHERE date >= today() - 30
ORDER BY event_time DESC;
WARNING

max_bytes_before_external_sort по умолчанию равен 0 — внешняя сортировка отключена. В отличие от внешней агрегации, она не включается автоматически. Запрос с большим ORDER BY упадёт с MEMORY_LIMIT_EXCEEDED, если явно не установить этот параметр.


MemoryTracker: мониторинг потребления памяти

ClickHouse имеет встроенный MemoryTracker, отслеживающий потребление памяти на уровне запросов, пользователей и всего сервера. Данные доступны через системные таблицы.

Текущее суммарное потребление памяти

-- Текущее суммарное потребление памяти сервером
SELECT metric, value
FROM system.metrics
WHERE metric = 'MemoryTracking';

Развёрнутая статистика через asynchronous_metrics

-- Все метрики памяти с human-readable значениями
SELECT
    metric,
    formatReadableSize(value) AS readable_value
FROM system.asynchronous_metrics
WHERE metric LIKE '%Mem%'
ORDER BY value DESC;

Пример вывода:

metric                        | readable_value
------------------------------|---------------
MemoryCode                    | 256.00 MiB
MemoryDataAndStack            | 14.32 GiB
OSMemoryTotal                 | 64.00 GiB
OSMemoryAvailable             | 38.45 GiB
OSMemoryFreePlusCached        | 41.20 GiB

Функция formatReadableSize() переводит байты в удобочитаемый формат (MiB, GiB).

Потребление памяти по завершённым запросам

-- Топ-10 запросов по потреблению памяти за последний час
SELECT
    initial_query_id,
    formatReadableSize(memory_usage) AS memory,
    query_duration_ms,
    substring(query, 1, 80) AS query_preview
FROM system.query_log
WHERE type = 'QueryFinish'
  AND event_time >= now() - INTERVAL 1 HOUR
ORDER BY memory_usage DESC
LIMIT 10;

Сводная таблица настроек

НастройкаНазначениеПо умолчаниюПоведение при превышении
max_memory_usageЛимит RAM на один запрос0 (нет лимита)MEMORY_LIMIT_EXCEEDED
max_memory_usage_for_userСуммарный лимит RAM на пользователя0 (нет лимита)MEMORY_LIMIT_EXCEEDED
max_bytes_before_external_group_byПорог spill для GROUP BY0 (отключено)Spill промежуточных данных на диск
max_bytes_before_external_sortПорог spill для ORDER BY0 (отключено)Spill промежуточных данных на диск

Практические рекомендации

Для многопользовательских аналитических систем рекомендуется следующая стартовая конфигурация:

-- Пример для сервера с 64 GB RAM, 8 concurrent пользователей:

-- Лимит на одного пользователя: 80% RAM / 8 пользователей = 6.4 GB
-- Используем консервативное значение 6 GB
SET max_memory_usage_for_user = 6000000000;

-- Лимит на один тяжёлый запрос: половина пользовательского лимита
SET max_memory_usage = 3000000000;

-- Порог external GROUP BY: max_memory_usage / 2 как стартовое значение
SET max_bytes_before_external_group_by = 1500000000;

Формула для расчёта max_memory_usage_for_user:

max_memory_usage_for_user = (общий RAM * 0.8) / ожидаемое_число_concurrent_пользователей

Ключевые выводы

  1. max_memory_usage ограничивает RAM на уровне запроса — при превышении запрос прерывается с MEMORY_LIMIT_EXCEEDED. Значение 0 означает отсутствие лимита.
  2. max_bytes_before_external_group_by включает spill-to-disk для GROUP BY. Запрос выполнится медленнее, но не упадёт с OOM. Установите max_memory_usage в 2x от этого значения.
  3. max_bytes_before_external_sort — аналогичный механизм для ORDER BY. По умолчанию отключён (значение 0).
  4. MemoryTracker доступен через system.metrics (текущее состояние) и system.asynchronous_metrics (расширенная статистика). Функция formatReadableSize() переводит байты в читаемый формат.
  5. Стартовое значение max_memory_usage_for_user: (80% RAM) / число_concurrent_пользователей.
Spark memory management: unified memory model, spill и OOM Trino spill-to-disk: JOIN и GROUP BY при недостатке памяти

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 3. Запрос `SELECT user_id, count() FROM user_sessions_50gb GROUP BY user_id` падает с ошибкой MEMORY_LIMIT_EXCEEDED. Какая настройка позволит выполнить агрегацию с меньшим потреблением оперативной памяти?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 10