Я изменяю схему базы данных, чтобы улучшить производительность запросов.В новом дизайне есть 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 вместо представлений, но времена были еще хуже.
- Можно ли ускорить запрос к некэшированным данным?
- Есть ли существенный недостаток в дизайне, который необходимо исправить?
- Есть ли лучший дизайн схемы?Используя схему выше, представления могут быть созданы без объединения данных из другой таблицы, что должно быть значительно быстрее.
Заранее спасибо, Гвидо