Проблемы производительности Postgres с секционированными данными - PullRequest
0 голосов
/ 25 июня 2018

Я изменяю схему базы данных, чтобы улучшить производительность запросов.В новом дизайне есть 5 (3 используемые в примере ниже) таблицы, разделенные на разделы в месяц года (всего 860 таблиц в 172 разделах для тестового примера).Соответствующие поля индексируются с использованием соответствующего типа индекса и класса оператора.База данных заполнена симулированными данными, которые представляют собой разумные данные, которые могут встречаться в производственной среде.Данные почти никогда не будут обновляться, после их сохранения они будут только прочитаны.

В таблице измерений

  • 10M строк
  • 160M строк в таблице material_data
  • 40M строк в таблице process_data

Конфигурация аппаратного и программного обеспечения:

Windows 10 Professional 64bit
Intel Core i7-4790CPU 
1 TB SATA HDD
16 GB RAM
PostgreSQL 11beta 1

Конфигурация Postgres (postgresql.conf):

shared_buffers = 512MB
temp_buffers   = 32MB
work_mem       = 32MB
maintenance_work_mem = 1GB

max_worker_processes = 8
max_parallel_workers = 8
max_parallel_workers_per_gather = 2

enable_partition_pruning = on
enable_parallel_append = on
constraint_exclusion = partition
default_statistics_target = 500
effective_cache_size = 12GB

Схема базы данных:

table measurements (10M records total):
id serial
guid TEXT NOT NULL (index: btree, text_pattern_ops)
start TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL (index: btree)
stop TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL 
mount_point_id SMALLINT NOT NULL (index: btree)
name TEXT NOT NULL
comment TEXT NOT NULL
PARTITION BY RANGE (start)

table process_data (40M records total):
id serial
mount_point_id SMALLINT NOT NULL (index:btree)
measurement_id INTEGER NOT NULL (index: btree)
measurement_start TIMESTAMP WITHOUT TIME ZONE NOT NULL (index: btree)
item_id SMALLINT NOT NULL (index: btree( item_id, item_value) )
item_value REAL NOT NULL
PARTITION BY RANGE (measurement_start)

table material_data (160M records total):
id serial,
mount_point_id SMALLINT NOT NULL (index: btree)
measurement_id INTEGER NOT NULL  (index: btree)
measurement_start TIMESTAMP WITHOUT TIME ZONE NOT NULL (index: btree)
material_index SMALLINT NOT NULL (index: btree)
material_data TEXT NOT NULL (index: btree, text_pattern_ops)
PARTITION BY RANGE (measurement_start)

Table relations:
measurements 1 ---+--- 1..N process_data
                  +--- 1..N material_data
                  +--- 1..N ...

Это базовые таблицы, я предоставил информацию индекса для ясности.Фактически индексы применяются к отдельным таблицам разделов.

partition tables (data given for one partition):
partition_2018_06_measurements: 60K records
partition_2018_06_process_data: 240K record
partition_2018_06_material_data: 950K records

Обычные запросы:

  • выбор всех измерений за определенный интервал времени
  • выбор всех измерений с определенным uuid (или частью uuid)
  • выбрать все измерения с определенными элементами данных процесса
  • выбрать все измерения с определенными элементами material_data

Я провел некоторое тестирование с различным количеством записей измерений и целей статистики(в диапазоне от 10 000 до 10 000 записей в таблицах измерений и статистических целей 100 250 500 750 и 1000. Это всего 20 различных сценариев, и я получил сопоставимые результаты для каждого сценария, чуть лучшие результаты с более высокими целевыми показателями).

SQL-запрос, используемый для тестирования:

DROP VIEW IF EXISTS view_measurements;
DROP VIEW IF EXISTS view_material;
DROP VIEW IF EXISTS view_process;

CREATE TEMPORARY VIEW view_measurements AS
(
   SELECT * FROM 
      measurements m 
   WHERE
          m.start BETWEEN '2018-06-01 00:00:00' AND '2018-07-01 00:00:00'
      AND m.mount_point_id IN( 1,3,5,7,9,11,13,15,17,19 )
);

CREATE TEMPORARY VIEW view_material AS
(
   SELECT 
      md.measurement_id, 
      md.material_index, 
      md.material_data 
   FROM 
      material_data md 
   WHERE
      -- exclude as many rows as possible
          md.measurement_start BETWEEN '2018-06-01 00:00:00' AND '2018-07-01 00:00:00'
      AND md.mount_point_id IN( 1,3,5,7,9,11,13,15,17,19 )
      AND (md.material_data LIKE 'SHX%' OR md.material_data LIKE 'CU23%')
);

CREATE TEMPORARY VIEW view_process AS
(
   SELECT 
      pd.measurement_id, 
      pd.item_id, 
      pd.item_value 
   FROM 
      process_data pd
   WHERE
      -- exclude as many rows as possible
          pd.measurement_start BETWEEN '2018-06-01 00:00:00' AND '2018-07-01 00:00:00'
      AND pd.mount_point_id IN( 1,3,5,7,9,11,13,15,17,19 )
      AND pd.item_id IN ( 110, 111 )
);

--EXPLAIN ANALYZE VERBOSE
SELECT
   *
FROM
   view_measurements vm
WHERE
(
  (
    EXISTS( SELECT 1 FROM view_material md WHERE vm.id = md.measurement_id AND md.material_data LIKE 'SHX%' )  OR
    EXISTS( SELECT 1 FROM view_material md WHERE vm.id = md.measurement_id AND md.material_data LIKE 'CU23%' )
  )
  AND
  (
    EXISTS( SELECT 1 FROM view_process pd WHERE vm.id = pd.measurement_id AND pd.item_id = 110 AND pd.item_value > 1700 ) AND
    EXISTS( SELECT 1 FROM view_process pd WHERE vm.id = pd.measurement_id AND pd.item_id = 111 AND pd.item_value > 2.2 )
  )
);

Приведенный выше запрос выбирает все измерения с 01.06.2018 по 01.07.2018, где для строки измерения есть

- a material item starting with 'SHX' or there is an material_item starting with 'CU23' AND
- a process data item with id 110 and value > 1700 AND
- a process data item with id 110 and value > 2.2

.Запрос вернул 18 элементов.

Приведенный выше запрос иногда занимает до 1 минуты из неподготовленной базы данных.Это кажется слишком медленным, особенно когда все данные взяты из ровно 3 таблиц (интервал точно соответствует разделу 2018_06).Как только данные загружаются в кеш базы данных, запрос с аналогичными параметрами возвращается через несколько сотен миллисекунд.Я выполнил тот же запрос для больших разделов (кварталы против месяца), и первоначальный запрос занял еще больше времени (2 минуты вместо 1 минуты).Оптимизатор плана запросов показывает, что оценка строк в планировщике запросов в 200x / 400x меньше, чем фактический результат (пункты 10 и 11).

Я пытался использовать CTE вместо представлений, но времена были еще хуже.

  • Можно ли ускорить запрос к некэшированным данным?
  • Есть ли существенный недостаток в дизайне, который необходимо исправить?
  • Есть ли лучший дизайн схемы?Используя схему выше, представления могут быть созданы без объединения данных из другой таблицы, что должно быть значительно быстрее.

Заранее спасибо, Гвидо

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...