Я пытаюсь создать частичные индексы для большой (1,2 ТБ) статической таблицы в Postgres 9.4.
Мои данные полностью статичны, поэтому я могу вставить все данные, а затем создать все индексы.
В этой таблице размером 1,2 ТБ у меня есть столбец, run_id
который четко разделяет данные. Мы добились отличной производительности, создав индексы, которые охватывают диапазон run_id
s. Вот пример:
CREATE INDEX perception_run_frame_idx_run_266_thru_270
ON run.perception
(run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;
Эти частичные индексы дают нам желаемую скорость запроса. К сожалению, создание каждого частичного индекса занимает около 70 минут.
Похоже, мы ограничены процессором ( top
показывает 100% для процесса).
Что я могу сделать, чтобы ускорить создание наших частичных индексов?
Системные характеристики:
- 18 ядро Xeon
- ОЗУ 192 ГБ
- 12 SSD в RAID
- Автовакуум выключен
- maintenance_work_mem: 64 ГБ (слишком высоко?)
Таблица спецификаций:
- Размер: 1,26 ТБ
- Количество рядов: 10,537 млрд
- Типичный размер индекса: 3,2 ГБ (разница составляет ~. 5 ГБ)
Определение таблицы:
CREATE TABLE run.perception(
id bigint NOT NULL,
run_id bigint NOT NULL,
frame bigint NOT NULL,
by character varying(45) NOT NULL,
by_anyone bigint NOT NULL,
by_me bigint NOT NULL,
by_s_id integer,
owning_p_id bigint NOT NULL,
obj_type_set bigint,
seq integer,
subj_id bigint NOT NULL,
subj_state_frame bigint NOT NULL,
CONSTRAINT perception_pkey PRIMARY KEY (id))
(Не читайте слишком много имен столбцов - я их немного запутал.)
Справочная информация:
- У нас есть отдельная команда, которая использует эти данные, но на самом деле есть только один или два пользователя. (Все эти данные генерируются с помощью симуляции.) Пользователи начинают анализировать данные только после того, как вставки завершены и индексы полностью построены. Наша главная задача - сократить время, необходимое для генерации полезных данных, и сейчас узким местом является время создания индекса.
- Скорость запросов была полностью адекватной при использовании партиалов. На самом деле, я думаю, что мы могли бы увеличить количество прогонов, охватываемых каждым индексом, и при этом поддерживать достаточную производительность запросов.
- Я предполагаю, что нам придется разделить таблицу. Мы пытаемся исчерпать все другие варианты, прежде чем идти по этому маршруту.
run_id
? Равномерно распределены? Размер результирующего индекса на диске? Данные статичны, хорошо. Но ты единственный пользователь?completely static
, то что вы имеете в видуWe have a separate team onsite that consumes this data
? Вы просто индексируете диапазонrun_id >= 266 AND run_id <= 270
или всю таблицу? Какова продолжительность жизни каждого индекса / сколько запросов будет его использовать? Сколько разных значений дляrun_id
? Звучит как ~ 15 млн. строк заrun_id
, что сделает около 800 различных значений дляrun_id
? Почемуobj_type_set
,by_s_id
,seq
не определены как NOT NULL? Какой приблизительный процент значений NULL для каждого?Ответы:
BRIN index
Доступно с Postgres 9.5 и, вероятно, именно то, что вы ищете. Гораздо быстрее создание индекса, гораздо меньший индекс. Но запросы обычно не такие быстрые. Руководство:
Читайте дальше, это еще не все.
Депес провел предварительный тест.
Оптимальный для вашего случая: Если вы можете писать строки кластерных на
run_id
, ваш индекс становится очень малым и созданием гораздо дешевле.Вы можете даже просто проиндексировать всю таблицу .
Макет таблицы
Что бы вы ни делали, вы можете сохранить 8 байтов, потерянных для заполнения из-за требований выравнивания на строку, упорядочив столбцы следующим образом:
Уменьшает размер таблицы на 79 ГБ, если ни один из столбцов не имеет значений NULL. Подробности:
Кроме того, у вас есть только три столбца, которые могут быть NULL. Растровое изображение NULL занимает 8 байтов для 9 - 72 столбцов. Если только один целочисленный столбец имеет значение NULL, то для парадокса хранения есть угол: было бы дешевле использовать вместо этого фиктивное значение: 4 байта потрачено впустую, а 8 байтов сохранено, поскольку для строки не требуется битовая карта NULL. Подробнее здесь:
Частичные индексы
В зависимости от ваших реальных запросов может оказаться более эффективным иметь эти пять частичных индексов вместо приведенного выше:
Запустите одну транзакцию для каждой.
run_id
Таким образом, удаление в качестве столбца индекса экономит 8 байтов на каждую запись индекса - 32 вместо 40 байтов на строку. Каждый индекс также дешевле создавать, но создание пяти вместо одного занимает значительно больше времени для таблицы, которая слишком велика для того, чтобы оставаться в кэше (как прокомментировали @ Jürgen и @Chris). Так что это может или не может быть полезным для вас.Разметка
На основе наследования - единственный вариант до Postgres 9.5.
(Новое декларативное разбиение в Postgres 11 или, предпочтительно, 12 умнее.)
Руководство:
Жирный акцент мой. Следовательно, оценивая 1000 различных значений
run_id
, вы создадите разделы, охватывающие около 10 значений каждый.maintenance_work_mem
Я пропустил, что вы уже корректируете
maintenance_work_mem
в моем первом чтении. Я оставлю цитату и совет в своем ответе для справки. По документации:Я бы установил его настолько высоко, насколько это необходимо - что зависит от неизвестного (для нас) размера индекса. И только локально для выполнения сеанса. Как объясняет цитата, в противном случае слишком высокая общая настройка может привести к неэффективной работе сервера, поскольку автоочистка также может потребовать больше оперативной памяти. Кроме того, не устанавливайте его намного выше необходимого, даже во время выполнения сеанса свободная память может быть использована для кэширования данных.
Это может выглядеть так:
О себе
SET LOCAL
:Чтобы измерить размеры объекта:
Сервер обычно должен быть настроен разумно иначе, очевидно.
источник
Может быть, это просто чрезмерно спроектировано. Вы на самом деле пытались использовать один полный индекс? Частичные индексы, охватывающие всю таблицу вместе, не дают большого выигрыша, если таковые имеются, для поиска индекса, и из вашего текста я делаю вывод, что у вас есть индексы для всех run_ids? Могут быть некоторые преимущества при сканировании индекса с частичными индексами, но я бы в первую очередь проверил простое одноиндексное решение.
Для каждого создания индекса необходимо полное сканирование IO через таблицу. Таким образом, создание нескольких частичных индексов требует гораздо больше операций ввода-вывода при чтении таблицы, чем для одного индекса, хотя сортировка будет выполняться на диске для одного большого индекса. Если вы настаиваете на частичных индексах, вы можете попытаться построить все (или несколько) индексов одновременно (если позволяет память).
Для приблизительной оценки обслуживания_work_mem, необходимого для сортировки всех run_ids, представляющих собой 8-байтовые бигинты, в памяти вам потребуется 10,5 * 8 ГБ + некоторые накладные расходы.
источник
Вы также можете создать индексы в других табличных пространствах, отличных от стандартных. Эти табличные пространства могут указывать на диски, которые не являются избыточными (просто воссоздают индексы в случае их сбоя) или находятся на более быстрых массивах.
Вы также можете рассмотреть возможность разделения таблицы по тем же критериям, что и ваши частичные индексы. Это позволило бы при запросе использовать ту же скорость, что и индекс, без создания какого-либо индекса вообще.
источник