Перейти к содержимому
Последнее обновление

Сброс промежуточных результатов запроса на диск

Зачем нужен сброс на диск​

Вычислительный слой Doris основан на архитектуре MPP. Все вычислительные задачи выполняются в памяти BE, а обмен данными между узлами также использует память, поэтому управление памятью напрямую влияет на стабильность запросов. По мере того как все больше пользователей переносят в Doris обработку данных ETL, обработку материализованных представлений по нескольким таблицам и сложные запросы ad-hoc, памяти одного узла зачастую не хватает для хранения всего промежуточного состояния.

Сброс на диск (spill to disk) записывает на диск промежуточное состояние, например состояние агрегации и временные данные сортировки, позволяя продолжать выполнение запросов при нехватке памяти. Он дает три преимущества:

ПреимуществоОписание
МасштабируемостьПозволяет обрабатывать наборы данных, превышающие лимит памяти одного узла
СтабильностьСнижает число ошибок запросов и аварийных завершений процессов из-за нехватки памяти
ГибкостьПозволяет выполнять более сложные запросы без добавления оборудования

Сброс на диск сейчас поддерживают следующие операторы: Hash Join, Aggregation, Sort и CTE.

Примечание

Сброс на диск создает дополнительную нагрузку на дисковый ввод-вывод, поэтому время выполнения запросов может существенно увеличиться. Соответственно увеличьте значение переменной сеанса query_timeout и подключите отдельный диск или используйте SSD для каталога сброса, чтобы снизить влияние на обычную загрузку данных и выполнение запросов.

По умолчанию сброс промежуточных результатов запросов на диск отключен.

Как запускается сброс на диск​

Doris использует механизм резервирования памяти (reserve memory), чтобы управлять запуском сброса на диск. Он работает следующим образом:

  1. Во время выполнения Doris оценивает объем памяти, необходимый для обработки каждого Block, и запрашивает его у единого диспетчера памяти.
  2. Глобальный аллокатор памяти проверяет, не приведет ли выделение запрошенной памяти к превышению лимита памяти запроса, Workload Group или процесса.
  3. При превышении лимита память не выделяется. Doris приостанавливает текущий запрос и запускает сброс на диск для оператора, потребляющего больше всего памяти.
  4. После завершения сброса на диск выполнение запроса продолжается.

Иерархия управления памятью​

Управление памятью в Doris имеет три уровня: уровень процесса -> уровень Workload Group -> уровень запроса. Все три уровня ограничивают работу механизма сброса на диск.

Память на уровне процесса (BE)​

Параметр mem_limit в be.conf задает верхний предел использования памяти всем процессом BE. Когда потребление памяти превышает этот порог, Doris отменяет запрос, который в данный момент запрашивает память, а асинхронная фоновая задача завершает некоторые запросы или освобождает кеши.

Две распространенные проблемные ситуации:

  • Совместное развертывание: если BE размещается на одном хосте с FE, Kafka, HDFS или другими процессами, фактически доступный объем памяти может быть значительно меньше mem_limit. В этом случае механизм освобождения памяти не справляется и срабатывает OOM Killer операционной системы.
  • Развертывание в контейнерах: в средах K8s или Cgroup Doris автоматически определяет настройки памяти контейнера, поэтому ручная корректировка не требуется.

Память Workload Group​

ПараметрОписание
max_memory_percentМаксимальная доля памяти процесса, которую может использовать эта Workload Group. При ее превышении запускается сброс на диск или завершаются запросы.
min_memory_percentМинимальная доля памяти, гарантированная этой Workload Group. При нехватке памяти система распределяет ее с учетом этого значения, чтобы другим группам хватало памяти.
memory_low_watermarkНижний порог использования памяти. По умолчанию 50%.
memory_high_watermarkВерхний порог использования памяти. По умолчанию 80%. При превышении этого значения запросы на резервирование памяти отклоняются и запускается сброс на диск.

Ограничения: сумма значений min_memory_percent всех Workload Group не должна превышать 100%, а значение min_memory_percent отдельной группы не должно быть больше ее max_memory_percent.

Память на уровне запроса​

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

Значение exec_mem_limit задается с помощью переменной сеанса до запуска запроса и не может динамически изменяться во время его выполнения.

Примечание об обновлении

Значение exec_mem_limit по умолчанию составляло 2 GB до версии 3.1, а начиная с версии 3.1 было изменено на 100 GB; в этих версиях ограничение фактически применяется на стороне BE. Перед обновлением до версии 3.1 или более поздней явно задайте для этого параметра значение 100g, чтобы существующие запросы не отменялись из-за превышения лимита и не запускали неожиданный сброс на диск.

Динамическое выделение памяти на основе слотов​

При статическом выделении пользователям часто сложно точно оценить объем памяти, необходимый одному запросу, поэтому они задают слишком высокий лимит (например, половину памяти процесса). Это мешает точно управлять распределением памяти. Механизм слотов Workload Group решает эту проблему.

Принцип работы:

  • Если для Workload Group заданы max_memory_percent и max_concurrency, память BE логически делится на max_concurrency слотов, а объем памяти каждого слота = max_memory_percent x mem_limit / max_concurrency.
  • По умолчанию каждый запрос занимает 1 слот. Чтобы использовать больше памяти, измените переменную сеанса query_slot_count.
  • Когда запрос занимает больше слотов, число запросов, которые Workload Group может выполнять одновременно, автоматически уменьшается, а новые запросы попадают в очередь.

Возможные значения slot_memory_policy:

ЗначениеОписание
noneЗначение по умолчанию. Механизм не включен. Запрос использует максимально возможный объем памяти, а сброс на диск запускается при достижении лимита Workload Group.
fixedОбъем памяти на запрос = workload group mem_limit x query_slot_count / max_concurrency. Фиксированное выделение памяти в зависимости от допустимого числа одновременных запросов.
dynamicОбъем памяти на запрос = workload group mem_limit x query_slot_count / sum(running query slots). Память свободных слотов динамически перераспределяется в пользу выполняющихся крупных запросов.

Оба варианта, fixed и dynamic, задают жесткие лимиты. При их превышении запускается сброс на диск или завершается запрос. Они также переопределяют статически заданный лимит exec_mem_limit. При настройке slot_memory_policy внимательно подбирайте значение max_concurrency, иначе возможна нехватка памяти.

Включение сброса промежуточных результатов запросов на диск​

Шаг 1: Настройте пути сброса на диск для BE​

Добавьте следующую конфигурацию в be.conf. Чтобы изменение вступило в силу, BE необходимо перезапустить:

spill_storage_root_path=/mnt/disk1/spilltest/doris/be/storage;/mnt/disk2/doris-spill;/mnt/disk3/doris-spill
spill_storage_limit=100%
ПараметрОписание
spill_storage_root_pathПуть хранения файлов сброса на диск. По умолчанию совпадает со значением storage_root_path. Рекомендуется использовать путь на отдельном диске.
spill_storage_limitМаксимальный объем дискового пространства для файлов сброса. Поддерживается абсолютное значение (например, 100G или 1T) либо процент (по умолчанию 20%). При использовании отдельного диска можно задать 100%.

Шаг 2: Настройте переменные сеанса FE​

SET enable_spill = true;
SET exec_mem_limit = 10g;
SET query_timeout = 3600;
ПеременнаяОписание
enable_spillВключает или отключает сброс на диск. По умолчанию false. Если включен, сброс на диск запускается автоматически при нехватке памяти.
exec_mem_limitМаксимальный объем памяти, доступный одному запросу.
query_timeoutСброс на диск увеличивает время выполнения запроса, поэтому соответственно увеличьте время ожидания (в секундах).

Шаг 3: Настройте Workload Group (необязательно)​

Скорректируйте max_memory_percent, чтобы отдельная Workload Group не исчерпывала всю память процесса:

ALTER WORKLOAD GROUP normal PROPERTIES ('max_memory_percent'='90%');

Включите динамическое выделение памяти на основе слотов, чтобы сброс на диск запускался в первую очередь для крупных запросов:

ALTER WORKLOAD GROUP normal PROPERTIES ('slot_memory_policy'='dynamic');

Мониторинг сброса на диск​

Журнал аудита​

В FE Audit Log добавлены следующие поля для регистрации объемов чтения и записи при сбросе на диск:

SpillWriteBytesToLocalStorage=503412182|SpillReadBytesFromLocalStorage=503412182
ПолеОписание
SpillWriteBytesToLocalStorageОбщий объем данных, записанных на диск при сбросе (в байтах).
SpillReadBytesFromLocalStorageОбщий объем данных, прочитанных с диска при сбросе (в байтах).

Профиль запроса (Query Profile)​

После запуска сброса на диск для запроса в Profile появляются счетчики с префиксом Spill. Пример для HashJoin Build HashTable:

PARTITIONED_HASH_JOIN_SINK_OPERATOR  (id=4  ,  nereids_id=179):(ExecTime:  6sec351ms)
- Spilled: true
- CloseTime: 528ns
- ExecTime: 6sec351ms
- InitTime: 5.751us
- InputRows: 6.001215M (6001215)
- MemoryUsage: 0.00
- MemoryUsagePeak: 554.42 MB
- MemoryUsageReserved: 1024.00 KB
- OpenTime: 2.267ms
- PendingFinishDependency: 0ns
- SpillBuildTime: 2sec437ms
- SpillInMemRow: 0
- SpillMaxRowsOfPartition: 68.569K (68569)
- SpillMinRowsOfPartition: 67.455K (67455)
- SpillPartitionShuffleTime: 836.302ms
- SpillPartitionTime: 131.839ms
- SpillTotalTime: 5sec563ms
- SpillWriteBlockBytes: 714.13 MB
- SpillWriteBlockCount: 1.344K (1344)
- SpillWriteFileBytes: 244.40 MB
- SpillWriteFileTime: 350.754ms
- SpillWriteFileTotalCount: 32
- SpillWriteRows: 6.001215M (6001215)
- SpillWriteSerializeBlockTime: 4sec378ms
- SpillWriteTaskCount: 417
- SpillWriteTaskWaitInQueueCount: 0
- SpillWriteTaskWaitInQueueTime: 8.731ms
- SpillWriteTime: 5sec549ms

Spilled: true означает, что оператор запустил сброс на диск.

Системная таблица backend_active_tasks​

В information_schema.backend_active_tasks добавлены два столбца, позволяющие в реальном времени просматривать объемы сброса на диск для выполняющихся запросов:

СтолбецОписание
SPILL_WRITE_BYTES_TO_LOCAL_STORAGEОбъем данных, уже записанных на диск текущим запросом при сбросе (в байтах).
SPILL_READ_BYTES_FROM_LOCAL_STORAGEОбъем данных, уже прочитанных с диска текущим запросом при сбросе (в байтах).
SELECT * FROM information_schema.backend_active_tasks;

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

+-------+------------+-------------------+-----------------------------------+--------------+------------------+-----------+------------+----------------------+---------------------------+--------------------+-------------------+------------+------------------------------------+-------------------------------------+
| BE_ID | FE_HOST | WORKLOAD_GROUP_ID | QUERY_ID | TASK_TIME_MS | TASK_CPU_TIME_MS | SCAN_ROWS | SCAN_BYTES | BE_PEAK_MEMORY_BYTES | CURRENT_USED_MEMORY_BYTES | SHUFFLE_SEND_BYTES | SHUFFLE_SEND_ROWS | QUERY_TYPE | SPILL_WRITE_BYTES_TO_LOCAL_STORAGE | SPILL_READ_BYTES_FROM_LOCAL_STORAGE |
+-------+------------+-------------------+-----------------------------------+--------------+------------------+-----------+------------+----------------------+---------------------------+--------------------+-------------------+------------+------------------------------------+-------------------------------------+
| 10009 | 10.16.10.8 | 1 | 6f08c74afbd44fff-9af951270933842d | 13612 | 11025 | 12002430 | 1960955904 | 733243057 | 70113260 | 0 | 0 | SELECT | 508110119 | 26383070 |
| 10009 | 10.16.10.8 | 1 | 871d643b87bf447b-865eb799403bec96 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | SELECT | 0 | 0 |
+-------+------------+-------------------+-----------------------------------+--------------+------------------+-----------+------------+----------------------+---------------------------+--------------------+-------------------+------------+------------------------------------+-------------------------------------+

Показатели производительности (TPC-DS 10TB)​

Ниже приведены результаты теста с выполнением одного запроса за раз на серверах Alibaba Cloud. Он подтверждает, что сброс на диск позволяет завершить все 99 запросов TPC-DS даже в экстремальных условиях, когда отношение объема памяти к объему данных составляет примерно 1:52.

Тестовая среда:

  • 1 FE: 16 ядер vCPU, 32 GiB памяти (ecs.c6.4xlarge)
  • 3 BE: 16 ядер vCPU, 64 GiB памяти (ecs.g6.4xlarge)
  • Тестовые данные: TPC-DS 10TB, подключенные через Alibaba Cloud DLF Catalog

Общее время: 28,102.386 секунды

ЗапросВремя (мс)ЗапросВремя (мс)ЗапросВремя (мс)
query129092query3484055query673939554
query2130003query3569885query68183648
query396119query36148662query6911031
query41199097query3721598query70137901
query5212719query38164746query71166454
query662259query395874query722859001
query7209154query4051602query7392015
query862433query41563query74336694
query9579371query4293005query75838989
query1054260query4367769query76174235
query11560169query4479527query77174525
query1226084query4526575query781956786
query13228756query46134991query79162259
query141137097query47161873query80602088
query1527509query48153657query8116184
query1684806query49259387query8256292
query17288164query50141421query8326211
query1894770query51158056query8411906
query19124955query5291392query8557739
query2030970query5389497query8634350
query214333query54124118query87173631
query229890query5582584query88449003
query231757755query56152110query89113799
query24399553query5783417query9030825
query25291474query58259580query9112239
query2679832query59177125query9226695
query27175894query60161729query93275828
query28647497query61258058query9456464
query291299597query6239619query9564932
query3011434query6391258query9648102
query31106665query64234882query97597371
query3233481query65278610query98112399
query33146101query6690246query9964472

В дальнейшем поддержка сброса на диск будет добавлена для других операторов, например Window Function и Intersect, а его производительность продолжит улучшаться.