Настройка рабочих нагрузок
Параллелизм (обработка многоядерных процессоров)
Влияние групп строк на параллелизм
DuckDB распараллеливает рабочую нагрузку на основе групп строк, т.е. групп строк, хранящихся вместе на уровне хранения. Группа строк в формате базы данных DuckDB состоит из максимум 122 880 строк. Параллелизм начинается на уровне групп строк, поэтому для выполнения запроса на k потоках необходимо просканировать как минимум k * 122 880 строк.
Слишком много потоков
Обратите внимание, что в некоторых случаях DuckDB может запускать слишком много потоков (например, из-за HyperThreading), что может привести к замедлению. В этих случаях стоит вручную ограничить количество потоков с помощью SET threads = X.
Рабочие нагрузки, превышающие объем памяти (обработка вне памяти)
Ключевой сильной стороной DuckDB является поддержка рабочих нагрузок, превышающих объем памяти, т.е. он может обрабатывать наборы данных, которые больше доступной оперативной памяти (также известной как обработка вне памяти). Он также может выполнять запросы, где промежуточные результаты не помещаются в память. В этом разделе объясняются предварительные условия, объем и известные ограничения обработки больших нагрузок в DuckDB.
Выгрузка на диск
Рабочие нагрузки, превышающие объем памяти, поддерживаются путем выгрузки на диск. При стандартной конфигурации DuckDB создает ⟨database_file_name⟩.tmp временную директорию (в персистентном режиме) или .tmp директорию (в режиме оперативной памяти). Данную директорию можно изменить с помощью опции конфигурации temp_directory, например:
SET temp_directory = '/path/to/temp_dir.tmp/';
Операторы блокирования
Некоторые операторы не могут вывести одну строку до тех пор, пока не будет прочитана последняя строка их входных данных. Они называются операторами блокирования, так как им требуется буферизация всех входных данных, и являются наиболее ресурсоемкими операторами в реляционных системах баз данных. Основные операторы блокирования следующие:
-
сортировка:
ORDER BY -
группировка:
GROUP BY -
оконные функции:
OVER ... (PARTITION BY ... ORDER BY ...) -
соединение:
JOIN
DuckDB поддерживает обработку больших нагрузок для всех этих операторов.
Ограничения
DuckDB стремится всегда завершать рабочие нагрузки, даже если они превышают объем памяти. Тем не менее, в настоящее время есть некоторые ограничения:
- Если в одном запросе присутствует несколько операторов блокирования, DuckDB может по-прежнему выбросить исключение «недостаточно памяти» из-за сложного взаимодействия этих операторов.
- Некоторые функции агрегирования, такие как
list()иstring_agg(), не поддерживают перенос на диск. - Функции агрегирования, использующие сортировку, являются целостными, т.е. им нужны все входные данные, прежде чем агрегирование может начаться. Поскольку DuckDB пока не может переносить некоторые сложные промежуточные состояния агрегирования на диск, эти функции могут вызывать исключение «недостаточно памяти» при выполнении на больших наборах данных.
- Операция
PIVOTвнутренне использует функциюlist(), поэтому она подвержена тому же ограничению.
Профилирование
Если ваши запросы не работают так хорошо, как ожидалось, стоит изучить их планы запросов:
- Используйте
EXPLAINдля вывода физического плана запроса без его выполнения. - Используйте
EXPLAIN ANALYZEдля выполнения и профилирования запроса. Это покажет время ЦП, которое занимает каждый шаг в запросе. Обратите внимание, что из-за многопоточности сумма индивидуальных времен будет больше, чем общее время обработки запроса.
Планы запросов могут указывать на корень проблем с производительностью. Несколько общих направлений:
- Избегайте соединений типа «вложенный цикл» в пользу соединений типа «хеш-соединение».
- Сканирование, не включающее фильтрацию для условия фильтрации, которое применяется позже, выполняет ненужный ввод-вывод. Попробуйте переписать запрос, чтобы применить фильтрацию.
- Следует избегать плохих порядков соединений, где кардинальность оператора увеличивается до миллиардов кортежей.
Предварительно скомпонованные операторы
Предварительно скомпонованные операторы могут повысить производительность при многократном выполнении одного и того же запроса, но с разными параметрами. Когда оператор подготавливается, он выполняет несколько начальных частей процесса выполнения запроса (парсинг, планирование и т. д.) и кэширует их вывод. При выполнении эти шаги можно пропустить, что повышает производительность. Это полезно в основном для многократного выполнения небольших запросов (с временем выполнения < 100 мс) с разными наборами параметров.
Обратите внимание, что основной целью DuckDB не является быстрое одновременное выполнение многих небольших запросов. Скорее, он оптимизирован для выполнения больших, менее частых запросов.
Запрос удаленных файлов
DuckDB использует синхронный ввод-вывод при чтении удаленных файлов. Это означает, что каждый поток DuckDB может выполнить не более одного запроса HTTP за раз. Если запрос должен выполнить много небольших запросов по сети, увеличение threads DuckDB до значения больше, чем общее количество ядер процессора (примерно в 2-5 раз больше, чем количество ядер процессора), может улучшить параллелизм и производительность.
Избегайте чтения ненужных данных
Основной узким местом в рабочих нагрузках, читающих удаленные файлы, вероятно, является ввод-вывод. Это означает, что минимизация необоснованно считываемых данных может быть очень полезной.
Некоторые базовые трюки SQL могут помочь с этим:
- Избегайте
SELECT *. Вместо этого выбирайте только столбцы, которые действительно используются. DuckDB будет пытаться загрузить только данные, которые ему действительно нужны. - Если это возможно, применяйте фильтры к удаленным файлам Parquet. DuckDB может использовать эти фильтры для сокращения объема сканируемых данных.
- Сортируйте или разбивайте данные по столбцам, которые регулярно используются для фильтров: это повышает эффективность фильтров в сокращении ввода-вывода.
Чтобы проверить, сколько удаленных данных передается для запроса, EXPLAIN ANALYZE можно использовать для вывода общего числа запросов и общего объема передаваемых данных для запросов к удаленным файлам.
Избегайте повторного чтения данных
DuckDB не кэширует данные из удаленных файлов автоматически. Это означает, что повторное выполнение запроса к удаленному файлу приведет к повторной загрузке необходимых данных. Поэтому, если к данным необходимо получить доступ несколько раз, хранение их локально может иметь смысл. Чтобы проиллюстрировать это, рассмотрим пример:
Рассмотрим следующие запросы:
SELECT col_a + col_b FROM 's3://bucket/file.parquet' WHERE col_a > 10; SELECT col_a * col_b FROM 's3://bucket/file.parquet' WHERE col_a > 10;
Эти запросы загружают столбцы col_a и col_b из s3://bucket/file.parquet дважды. Теперь рассмотрим следующие запросы:
CREATE TABLE local_copy_of_file AS
SELECT col_a, col_b FROM 's3://bucket/file.parquet' WHERE col_a > 10;
SELECT col_a + col_b FROM local_copy_of_file;
SELECT col_a * col_b FROM local_copy_of_file; Здесь DuckDB сначала скопирует col_a и col_b из s3://bucket/file.parquet в локальную таблицу, а затем дважды запросит локальные столбцы в оперативной памяти. Обратите также внимание, что фильтр WHERE col_a > 10 теперь применяется только один раз.
Здесь следует сделать важное замечание. Первые два запроса полностью потоковые, с небольшой занимаемой памятью, в то время как второй требует полного материализации столбцов col_a и col_b. Это означает, что в некоторых редких случаях (например, при высокоскоростном сетевом соединении, но с очень ограниченным объемом доступной памяти) повторная загрузка данных дважды может быть на самом деле выгодной.
Рекомендации по использованию подключений
DuckDB будет работать наилучшим образом при многократном повторном использовании одного и того же подключения к базе данных. Отключение и подключение при каждом запросе приведет к некоторой задержке, что может снизить производительность при выполнении множества небольших запросов. DuckDB также кэширует некоторые данные и метаданные в памяти, и этот кэш теряется при закрытии последнего открытого подключения. Часто одно подключение будет работать лучше всего, но также можно использовать пул подключений.
Использование нескольких подключений может распараллелить некоторые операции, хотя это обычно не требуется. DuckDB пытается максимально распараллелить операции в рамках каждого запроса, но это не всегда возможно. Создание нескольких подключений может обрабатывать больше операций параллельно. Это может быть полезнее, если DuckDB не ограничен процессором, а ограничен другим ресурсом, например, скоростью передачи по сети.
Опция preserve_insertion_order
При импорте или экспорте наборов данных (в/из форматов Parquet или CSV), которые намного больше доступной памяти, может возникнуть ошибка «недостаточно памяти»:
Error: Out of Memory Error: failed to allocate data of size ... (.../... used)
В этих случаях рассмотрите возможность установки опции конфигурации preserve_insertion_order в значение false:
SET preserve_insertion_order = false;
Это позволяет системам переупорядочивать любые результаты, не содержащие ORDER BY предложений, что потенциально уменьшает использование памяти.
Постоянные и временные таблицы
DuckDB поддерживает техники сжатия небольшого размера. В настоящее время они применяются только к постоянным (на диске) базам данных.
DuckDB не сжимает свои временные таблицы. Причина в том, что сжатие выполняется во время создания контрольных точек, а временные таблицы не создают контрольные точки.
В редких случаях это может привести к неинтуитивным результатам производительности, где запросы выполняются быстрее на таблицах на диске по сравнению с таблицами в памяти. Например, запрос Q1 из нагрузки TPC-H выполняется быстрее при работе на диске по сравнению с режимом в памяти:
INSTALL tpch; LOAD tpch; CALL dbgen(sf = 30); .timer on PRAGMA tpch(1);
| Настройка базы данных | Время выполнения |
|---|---|
| База данных в памяти | 4.80 с |
| Персистентная база данных | 0.57 с |
© Copyright 2018–2024 Stichting DuckDB Foundation
Licensed under the MIT License.
https://duckdb.org/docs/guides/performance/how_to_tune_workloads.html