Влияние vacuum/analyze/bloat на производительность СУБД

Задачи эксперимента

  1. Оценить степень влияния регулярного выполнения vacuum/analyze на производительность СУБД.

  2. Оценить степень влияния распухания таблицы на производительность СУБД.

Реализация эксперимента

Виртуальная машина

CPU = 1

vendor_id       : GenuineIntel
cpu family      : 6
model           : 85
model name      : Intel Xeon Processor (Skylake, IBRS, no TSX)

RAM

Mem:          718Mi

Редакция/версия СУБД

Postgres Pro (enterprise certified) 15.8.1 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 11.4.1 20230605 (Red Soft 11.4.0-1), 64-bit  

Конфигурационные параметры СУБД

Инициализация тестовых таблиц

Создать и заполнить таблицы данными

recreate.sql
DROP TABLE IF EXISTS  table_parent ;
CREATE TABLE table_parent
(
id SERIAL ,
name text ,
curr_value numeric
);

DROP TABLE IF EXISTS  table_child1  ;
CREATE TABLE table_child1
(
id SERIAL ,
parent_key integer ,
name text ,
curr_value numeric
);

DROP TABLE IF EXISTS table_child2 ;
CREATE TABLE table_child2
(
id integer ,
parent_key integer ,
name text ,
curr_value numeric
);


INSERT INTO table_parent ( name , curr_value )
SELECT i::text , i::numeric
FROM generate_series(1, 1000000) AS t(i) ;

INSERT INTO table_child1 ( parent_key , name , curr_value )
SELECT i::numeric , i::text , i::numeric
FROM generate_series(300, 900000) AS t(i) ;

INSERT INTO table_child2 ( id , parent_key , name , curr_value )
SELECT i::numeric , i::numeric , i::text , i::numeric
FROM generate_series(20000, 800000) AS t(i) ;

Индексировать и собрать статистику по таблицам

index_analyze.sql
CREATE INDEX table_parent_idx ON table_parent ( id );
CREATE INDEX table_child1_idx ON table_child1 ( parent_key );
CREATE INDEX table_child2_idx ON table_child2 ( parent_key );

ANALYZE table_parent ;
ANALYZE table_child1 ;
ANALYZE table_child2 ;

Итерация теста

generator3.sql
CREATE OR REPLACE FUNCTION generator3() RETURNS integer AS $$
DECLARE
result_rec record ;
BEGIN
SELECT child1.name , child2.name
INTO      result_rec
FROM   table_parent parent
      join table_child1 child1 ON ( parent.id = child1.parent_key )
      join table_child2 child2 ON ( parent.id = child2.parent_key )                     
WHERE
      ( parent.id > 500000 AND parent.id < 500100 )   and ( parent.id % 2 = 0 );

INSERT INTO table_child2 ( id , parent_key , name , curr_value )
SELECT i::numeric , i::numeric , i::text , i::numeric
FROM generate_series(900000, 910000) AS t(i) ;

UPDATE table_child2
SET name = name || 'test'
WHERE id between 900000 AND 910000 ;
               
DELETE FROM table_child2
WHERE id between 900000 AND 910000 ;

return 0 ;
END
$$ LANGUAGE plpgsql;

Тест

generator3.4.sh
#!/bin/sh
#generator3.4.sh
#version.3.4

script=$(readlink -f $0)
current_path=`dirname $script`


LOG_FILE=$current_path'/generator.log'

echo 'TIMESTAMP : '$(date "+%d-%m-%Y %H:%M:%S") ' : OK : START '
echo 'TIMESTAMP : '$(date "+%d-%m-%Y %H:%M:%S") ' : OK : START '> $LOG_FILE

/opt/pgpro/ent-15/bin/psql -d test_db -c "select pg_stat_reset()" >> $LOG_FILE
if [ $? -ne 0 ]
then
  ##################################################################### 
  #ОШИБКА СУБД  
  echo 'ERROR : PostgreSQL ERROR : pg_stat_reset TERMINATED WITH ERROR '
  echo 'ERROR : PostgreSQL ERROR : pg_stat_statements_reset TERMINATED WITH ERROR ' >> $LOG_FILE
  exit 1000
  #####################################################################
fi


/opt/pgpro/ent-15/bin/psql -d test_db -c "select pg_stat_statements_reset()" >> $LOG_FILE
if [ $? -ne 0 ]
then
  ##################################################################### 
  #ОШИБКА СУБД  
  echo 'ERROR : PostgreSQL ERROR : pg_stat_statements_reset TERMINATED WITH ERROR '
  echo 'ERROR : PostgreSQL ERROR : pg_stat_statements_reset TERMINATED WITH ERROR ' >> $LOG_FILE
  exit 1000
  #####################################################################
fi


for ((i=1; i <= 1000; i++))
do
	echo 'PASS = '$i
	echo 'PASS = '$i>> $LOG_FILE
	
	/opt/pgpro/ent-15/bin/psql -d test_db -c "select generator3()" 
	if [ $? -ne 0 ]
	then
		##################################################################### 
		#ОШИБКА СУБД  
		echo 'ERROR : PostgreSQL ERROR : generator TERMINATED WITH ERROR '
		echo 'ERROR : PostgreSQL ERROR : generator TERMINATED WITH ERROR ' >> $LOG_FILE
		exit 1000
		#####################################################################
	fi
	

done

echo 'TIMESTAMP : '$(date "+%d-%m-%Y %H:%M:%S") ' : OK : FINISHED '
echo 'TIMESTAMP : '$(date "+%d-%m-%Y %H:%M:%S") ' : OK : FINISHED '>> $LOG_FILE

exit 0 

Оцениваемые результаты

  1. Общее время выполнения (total_exec_time представления pg_stat_statements)

  2. Количество запросов (calls представления pg_stat_statements)

  3. Количество результирующих строк (rows представления pg_stat_statements)

  4. Общее число прочитанных разделяемых блоков (shared_blks_read представления pg_stat_statements)

  5. Общее число «загрязнённых» разделяемых блоков (shared_blk_dirtied представления pg_stat_statements)

  6. Общее число записанных разделяемых блоков (shared_blk_written представления pg_stat_statements)

  7. Количество транзакций (xact_commit представления pg_stat_database)

  8. Метрика производительности СУБД (CPI)

Результаты эксперимента

Эталонный тест: vacuum + analyze после каждой итерации

Рис.1. Базовая нагрузка
Рис.1. Базовая нагрузка

Тест без выполнения vacuum + analyze после каждой итерации

Рис.2. Тест без vacuum+analyze
Рис.2. Тест без vacuum+analyze

Сравнение результатов с «Эталонный тест: vacuum + analyze после каждой итерации»

Фрагментация 11%

Рис.3. Фрагментация 11%
Рис.3. Фрагментация 11%

Сравнение результатов с «Тест без выполнения vacuum + analyze после каждой итерации»

Фрагментация 100%

Рис.4. Фрагментация 100%
Рис.4. Фрагментация 100%

Сравнение результатов с «Тест без выполнения vacuum + analyze после каждой итерации»

Итоги

Выполнение vacuum+analyze после массовых изменений данных существенно увеличивает производительность СУБД, хотя общее время выполнения и несколько возрастает.

Даже относительно небольшая фрагментация оказывает существенное влияние на производительность СУБД.

Дальнейшее увеличение фрагментации не оказывает заметного влияния на производительность СУБД.

Мониторить и оптимизировать надо не фрагментацию БД в целом, а фрагментацию наиболее часто используемых таблиц.

@rinace
23.09.2024 20:41 UTC
Первоисточник

Комментарии

@baldr
23.09.2024 15:49 UTC
+1

Идея провести тесты хорошая. Но выводы капитанские. Особенно когда в статье не приведены ни параметры базы, ни параметры сервера (может у вас там Windows-виртуалка в облаке с 1Гб памяти) , ни схема таблиц и данных, ни тесты....

Поставил плюс статье, но могло быть и лучше.

@rinace
23.09.2024 15:58 UTC
0

Если вам , действительно интересны детали, ок , принято добавлю чуть больше деталей, попозже. Эксперименты будут продолжены, очень интересно, как там с ожиданиями и корреляциями обстоит дело .Не знаю как вам , но мне, показалось очень интересным и не так уж и очевидным - несущественная разница между уровнем фрагментации 11% и 100%.Ожидал , что то типа логарифмической зависимости .

@rinace
28.09.2024 10:14 UTC
0

в статье не приведены ни параметры базы, ни параметры сервера (может у вас там Windows-виртуалка в облаке с 1Гб памяти) , ни схема таблиц и данных, ни тесты

Добавлено

@Hamletghost
23.09.2024 17:38 UTC
+1

Это все конечно здорово, но совершенно оторвано от реальности.

К сожалению Autocacuum постгреса, точнее алгоритм его запуска реализован слишком топорно.

Пример из жизни: кластер с одной синхронной репликой (которая используется для r/o аналитических запросов ). В какой-то момент складывается такая ситуация: в одной из крупных таблиц происходит много изменений, тригерится autocacuum и… он ничего не может собрать потому что, ну вот так совпало, что на реплике запустился какой-то тяжелый долгий запрос из-за чего на мастере xid остались заморожены до окончания запроса. Но условие срабатывания автовакума все еще валидно и вот он запускается снова и снова, сканирует это огромную таблицу, высаживая io и сжигая cpu.

Да и в принципе этот механизм так устроен, что он будет запускаться именно в часы наибольшей нагрузки (по очевидным причинам) - иногда добивая загрузку свободных ядер до 100%

Я знаю про все его настройки - баланс там найти сложно. Мечтаю, что когда-нибудь он научится адаптироваться к нагрузке и делать больше пауз, если бд и так перегружена

@rinace
23.09.2024 17:43 UTC
0

А пробовали настраивать вакуум не для кластера в целом , для таблиц отдельно ?

23.09.2024 18:47 UTC
+2

На большой бд только так и можно жить.

Дефолтные настройки для мелких таблиц

Все крупные таблицы имеют свои трешолды в зависимости от интенсивности их изменений

А на самых больших автовакуум вообще приходится выключать и запускать вакуум по старинке из планировщика (pg_cron) в часы наименьшей нагрузки