Перейти к основному содержимому

psql_diagpack. Дополнительные инструменты мониторинга

В исходном дистрибутиве установлено по умолчанию: нет.

Связанные компоненты: отсутствуют.

Схема размещения: ext.

Описание

Расширение psql_diagpack создает представления, которые отображают в удобном для восприятия пользователем виде информацию из системных представлений. Список представлений:

ПредставлениеОтображаемая информация
dba_query_cpu_and_waits_timeСписок запросов, отсортированных по убыванию времени, проведенного на CPU или событиях ожидания, отличных от IO
dba_query_io_timeСписок запросов, отсортированных по убыванию времени, проведенного на IO
dba_query_run_timeСписок запросов, отсортированных по убыванию общего времени выполнения
dba_locksДерево блокировок
dba_standby_checkОтчет по статусу репликации
dba_top_tablesСписок самых больших таблиц (полный размер) в БД
dba_top_objectsСписок самых больших отношений в БД
dba_activityРасширенный отчет по активности процессов
dba_activity_vacuumОтчет о текущей активности процессов очистки (AUTOVACUUM+VACUUM+VACUUM FULL)
dba_waitsОтчет о распределении процессов по событиям ожидания
dba_bloat_wastedbytesСписок таблиц, отсортированных по объему пространства, не занятого для хранения актуальных версий записей
dba_bloat_tbloatСписок таблиц, отсортированных по доле пространства, не занятого для хранения актуальных версий записей
dba_unused_indexesСписок неиспользуемых индексов
dba_duplicated_indexesСписок индексов, перекрываемых другими индексами

Настройка

Проверка установленного расширения pg_stat_statements

Поскольку часть представлений из поставки данного расширения требуют наличия установленного расширения pg_stat_statements, необходимо проследить, чтобы данное расширение было установлено.

  1. С помощью метакоманды \dx в psql проверьте, что расширение pg_stat_statements присутствует в списке:

    \dx

    Вывод:

            Name        | Version |   Schema   |                              Description
    --------------------+---------+------------+------------------------------------------------------------------------
    ...
    pg_stat_statements | 1.8 | ext | track planning and execution statistics of all SQL statements executed
    ...
  2. Проверьте, чтобы схема, в которой установлено расширение pg_stat_statements, присутствовала в search_path:

    SHOW search_path

    Пример вывода:

     search_path
    -------------
    ext, public

    Если схема отсутствует в search_path, ее можно добавить для заданного пользователя (например, одной из приведенных ниже команд), чтобы не выполнять команду SET SEARCH_PATH TO ... каждый раз вручную:

    ALTER ROLE TEST_ROLE SET search_path TO EXTENSION_SCHEMA,...';
    ALTER DATABASE TESTDB SET search_path TO EXTENSION_SCHEMA,...';
  3. Если производится установка на оригинальную версию PostgreSQL, необходимо установить данное расширение вручную. Для этого в postgresql.conf добавьте значение 'pg_stat_statements' в параметр shared_preload_libraries:

    shared_preload_libraries = 'pg_stat_statements'

    После этого, создайте расширение pg_stat_statements в заданной БД:

    CREATE EXTENSION pg_stat_statements;

Выдача необходимых привилегий

По умолчанию, пользователь может видеть только тексты запросов для процессов, которые были запущены под той же самой ролью. Если требуется видеть тексты запросов для всех процессов, необходимо предоставить пользователю привилегию pg_read_all_stats. Выдать привилегию пользователю можно командой:

GRANT pg_read_all_stats TO USER;

Установка расширения psql_diagpack

После настройки, можно перейти непосредственно к активации расширения psql_diagpack:

CREATE EXTENSION psql_diagpack;

Проверка корректности установки

Следует проверить, что расширение установлено корректно.

Расширения pg_stat_statements и psql_diagpack должны фигурировать в списке установленных расширений:

\dx

Вывод:

        Name        | Version |   Schema   |                              Description
--------------------+---------+------------+------------------------------------------------------------------------
...
pg_stat_statements | 1.8 | ext | track planning and execution statistics of all SQL statements executed
psql_diagpack | 1.0 | ext | Administrative views for PostgreSQL monitoring
...

Следующие два запроса должны возвращать данные:

TABLE dba_query_cpu_time LIMIT 2;

time_percent | iotime_percent | cputime_percent | total_exec_time | avg_time | avg_cpu_time | avg_io_time | calls | calls_percent | rows | row_percent | query
--------------+----------------+-----------------+-----------------+----------+--------------+-------------+-------+---------------+------+-------------+------------------------------------------------
68.77 | 0.00 | 68.77 | 43.90 | 43904.56 | 43.90 | 0.00 | 1 | 6.67 | 0 | 0.00 | create extension psql_diagpack schema ext
23.15 | 0.00 | 23.15 | 14.78 | 7391.40 | 7.39 | 0.00 | 2 | 13.33 | 0 | 0.00 | create extension pg_stat_statements schema ext
(2 rows)
TABLE dba_bloat_wastedbytes LIMIT 2;

nspname | tabname | relname | reltype | tsize | tplcnt | dtplcnt | wspaceprc | wspaceb
------------+------------------+-----------------------------------+---------+---------+--------+---------+-----------+------------
pg_catalog | pg_proc | pg_proc | table | 1112 kB | 3295 | 5 | 13.1 | 145 kB
pg_catalog | pg_depend | pg_depend_reference_index | index | 392 kB | 8597 | 0 | 35.2 | 138 kB
(2 rows)

Управление

Расширение psql_diagpack не предоставляет никаких параметров для изменения поведения расширения или иных средств управления.

Устранение неполадок

Если при просмотре представлений dba_locks и dba_activity вместо текстов запросов отображается строка Insufficient privilege, то необходимо предоставить пользователю привилегию pg_read_all_stats, чтобы наблюдать тексты запросов для всех процессов.

Безопасность

Поскольку psql_diagpack предполагает необходимость только Read Only доступа к БД, риски его использования относительно невелики. Тем не менее возможность наблюдать тексты запросов в представлениях pg_stat_activity и pg_stat_statements дает возможность злоумышленнику-инсайдеру обнаружить, к примеру, запросы, которые запускаются без использования переменных привязки. Из текста подобных запросов можно получить элементы персональных данных, либо рассмотреть возможность использования подобных запросов для проведения атаки типа «SQL-инъекция».

Описание объектов

Примечание:

Все указанные в разделе примеры вывода не содержат действительные данные (например, id).

Представления

dba_query_cpu_and_waits_time: Получение списка запросов, отсортированных по убыванию времени, проведенного на CPU или событиях ожидания, отличных от IO

Название поляТипОписание поля
time_pcntnumeric(5,2)Доля времени, проведенного на выполнении данного запроса, в общем времени, проведенном на выполнении запросов для данной БД
io_pcntnumeric(5,2)Доля времени, проведенного на IO при выполнении данного запроса, в общем времени, проведенном на IO при выполнении запросов для данной БД
cpu_and_waits_pcntnumeric(5,2)Доля времени, проведенного на CPU при выполнении данного запроса, в общем времени, проведенном на CPU или событиях ожидания, отличных от IO, при выполнении запросов для данной БД
calls_pcntnumeric(5,2)Доля от общего количества выполнений запросов в данной БД
rows_pcntnumeric(5,2)Доля от общего количества обработанных записей в данной БД
total_time_msbigintВремя, потраченное на все выполнения данного запроса (в миллисекундах)
total_io_msbigintВремя, проведенное на IO в рамках всех выполнений данного запроса (в миллисекундах)
shr_blksnumericКоличество страниц (из буферного кеша и из файловой системы), прочитанных в рамках всех выполнений данного запроса
callsnumericЧисло выполнений запроса
rowsnumericЧисло обработанных записей
avg_time_usbigintСреднее время выполнения запроса (в микросекундах)
avg_io_usbigintСреднее время проведенное на IO в рамках выполнения запроса (в микросекундах)
avg_shr_blksbigintСреднее количество страниц (из буферного кеша и из файловой системы), прочитанных в рамках выполнения данного запроса
querytextТекст запроса

DDL:

First_db=# \d+ dba_query_cpu_and_waits_time
View "public.dba_query_cpu_and_waits_time"
Column | Type | Collation | Nullable | Default | Storage | Description
--------------------+--------------+-----------+----------+---------+----------+-------------
time_pcnt | numeric(5,2) | | | | main |
io_pcnt | numeric(5,2) | | | | main |
cpu_and_waits_pcnt | numeric(5,2) | | | | main |
calls_pcnt | numeric(5,2) | | | | main |
rows_pcnt | numeric(5,2) | | | | main |
total_time_ms | bigint | | | | plain |
total_io_ms | bigint | | | | plain |
shr_blks | numeric | | | | main |
calls | numeric | | | | main |
rows | numeric | | | | main |
avg_time_us | bigint | | | | plain |
avg_io_us | bigint | | | | plain |
avg_shr_blks | bigint | | | | plain |
query | text | | | | extended |
View definition:
WITH summ AS (
SELECT GREATEST(0.001::double precision, sum(pg_stat_statements.total_exec_time)) AS sum_total_exec_time,
GREATEST(0.001::double precision, sum(pg_stat_statements.blk_read_time + pg_stat_statements.blk_write_time)) AS sum_io_time,
GREATEST(0.001::double precision, sum(pg_stat_statements.total_exec_time - pg_stat_statements.blk_read_time - pg_stat_statements.blk_write_time)) AS sum_cpu_and_waits_time,
sum(pg_stat_statements.calls) AS sum_calls,
sum(pg_stat_statements.rows) AS sum_rows
FROM pg_stat_statements
WHERE pg_stat_statements.dbid = (( SELECT pg_database.oid
FROM pg_database
WHERE pg_database.datname = current_database()))
), _pg_stat_statements AS (
SELECT pg_stat_statements.query,
sum(pg_stat_statements.total_exec_time) AS total_exec_time,
sum(pg_stat_statements.blk_read_time + pg_stat_statements.blk_write_time) AS io_time,
sum(pg_stat_statements.total_exec_time - pg_stat_statements.blk_read_time - pg_stat_statements.blk_write_time) AS cpu_and_waits_time,
sum(pg_stat_statements.calls) AS calls,
sum(pg_stat_statements.rows) AS rows,
sum(pg_stat_statements.shared_blks_read + pg_stat_statements.shared_blks_hit) AS shr_blks
FROM pg_stat_statements
WHERE pg_stat_statements.dbid = (( SELECT pg_database.oid
FROM pg_database
WHERE pg_database.datname = current_database()))
GROUP BY pg_stat_statements.query
)
SELECT (100::double precision * _pg_stat_statements.total_exec_time / (( SELECT summ.sum_total_exec_time
FROM summ)))::numeric(5,2) AS time_pcnt,
(100::double precision * _pg_stat_statements.io_time / (( SELECT summ.sum_io_time
FROM summ)))::numeric(5,2) AS io_pcnt,
(100::double precision * _pg_stat_statements.cpu_and_waits_time / (( SELECT summ.sum_cpu_and_waits_time
FROM summ)))::numeric(5,2) AS cpu_and_waits_pcnt,
(100::numeric * _pg_stat_statements.calls / (( SELECT summ.sum_calls
FROM summ)))::numeric(5,2) AS calls_pcnt,
(100::numeric * _pg_stat_statements.rows / (( SELECT summ.sum_rows
FROM summ)))::numeric(5,2) AS rows_pcnt,
_pg_stat_statements.total_exec_time::bigint AS total_time_ms,
_pg_stat_statements.io_time::bigint AS total_io_ms,
_pg_stat_statements.shr_blks,
_pg_stat_statements.calls,
_pg_stat_statements.rows,
(_pg_stat_statements.total_exec_time * 1000::double precision / _pg_stat_statements.calls::double precision)::bigint AS avg_time_us,
(_pg_stat_statements.io_time * 1000::double precision / _pg_stat_statements.calls::double precision)::bigint AS avg_io_us,
(_pg_stat_statements.shr_blks / _pg_stat_statements.calls)::bigint AS avg_shr_blks,
_pg_stat_statements.query
FROM _pg_stat_statements
WHERE (_pg_stat_statements.cpu_and_waits_time / (( SELECT summ.sum_cpu_and_waits_time
FROM summ))) >= 0.02::double precision
UNION ALL
SELECT (100::double precision * sum(_pg_stat_statements.total_exec_time) / (( SELECT summ.sum_total_exec_time
FROM summ)))::numeric(5,2) AS time_pcnt,
(100::double precision * sum(_pg_stat_statements.io_time) / (( SELECT summ.sum_io_time
FROM summ)))::numeric(5,2) AS io_pcnt,
(100::double precision * sum(_pg_stat_statements.cpu_and_waits_time) / (( SELECT summ.sum_cpu_and_waits_time
FROM summ)))::numeric(5,2) AS cpu_and_waits_pcnt,
(100::numeric * sum(_pg_stat_statements.calls) / (( SELECT summ.sum_calls
FROM summ)))::numeric(5,2) AS calls_pcnt,
(100::numeric * sum(_pg_stat_statements.rows) / (( SELECT summ.sum_rows
FROM summ)))::numeric(5,2) AS rows_pcnt,
sum(_pg_stat_statements.total_exec_time)::bigint AS total_time_ms,
sum(_pg_stat_statements.io_time)::bigint AS total_io_ms,
sum(_pg_stat_statements.shr_blks) AS shr_blks,
sum(_pg_stat_statements.calls) AS calls,
sum(_pg_stat_statements.rows) AS rows,
(sum(_pg_stat_statements.total_exec_time) * 1000::double precision / sum(_pg_stat_statements.calls)::double precision)::bigint AS avg_time_us,
(sum(_pg_stat_statements.io_time) * 1000::double precision / sum(_pg_stat_statements.calls)::double precision)::bigint AS avg_io_us,
(sum(_pg_stat_statements.shr_blks) / sum(_pg_stat_statements.calls))::bigint AS avg_shr_blks,
'other'::text AS query
FROM _pg_stat_statements
WHERE (_pg_stat_statements.cpu_and_waits_time / (( SELECT summ.sum_cpu_and_waits_time
FROM summ))) < 0.02::double precision
ORDER BY 3 DESC;
Пример эксплуатации

Подайте нагрузку на БД с помощью утилиты pg_bench:

pgbench -i -s 10 --foreign-keys First_db
pgbench -c 5 -j 5 -T 30 -P 10 First_db

Пример вывода списка тяжелых запросов:

psql -d First_db -c 'table dba_query_cpu_and_waits_time'

time_pcnt | io_pcnt | cpu_and_waits_pcnt | calls_pcnt | rows_pcnt | total_time_ms | total_io_ms | shr_blks | calls | rows | avg_time_us | avg_io_us | avg_shr_blks | query
-----------+---------+--------------------+------------+-----------+---------------+-------------+----------+--------+---------+-------------+-----------+--------------+------------------------------------------------------------------------------------------------------
56.89 | 0.00 | 57.51 | 14.26 | 2.90 | 17074 | 0 | 141083 | 33940 | 33940 | 503 | 0 | 4 | UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2
9.58 | 0.00 | 9.68 | 14.26 | 2.90 | 2875 | 0 | 106137 | 33940 | 33940 | 85 | 0 | 3 | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
9.06 | 0.75 | 9.15 | 0.00 | 0.00 | 2720 | 2 | 50693 | 1 | 0 | 2720126 | 2395 | 50693 | CREATE EXTENSION "pg_profile" WITH SCHEMA "pgse_profile"
9.01 | 0.01 | 9.10 | 14.26 | 2.90 | 2703 | 0 | 456267 | 33940 | 33940 | 80 | 0 | 13 | INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
4.45 | 11.42 | 4.38 | 14.26 | 2.90 | 1336 | 37 | 292065 | 33940 | 33940 | 39 | 1 | 9 | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
4.12 | 39.08 | 3.74 | 42.95 | 2.92 | 1237 | 125 | 229422 | 102216 | 34102 | 12 | 1 | 2 | other
3.65 | 15.65 | 3.52 | 0.00 | 85.48 | 1096 | 50 | 6 | 1 | 1000000 | 1095872 | 50228 | 6 | copy pgbench_accounts from stdin
3.23 | 33.09 | 2.90 | 0.00 | 0.00 | 968 | 106 | 49305 | 1 | 0 | 968289 | 106225 | 49305 | vacuum analyze pgbench_accounts
(8 rows)

dba_query_io_time: Получение списка запросов, отсортированных по убыванию времени, проведенного на IO

Название поляТипОписание поля
time_pcntnumeric(5,2)Доля времени, проведенного на выполнении данного запроса, в общем времени, проведенном на выполнении запросов для данной БД
io_pcntnumeric(5,2)Доля времени, проведенного на IO при выполнении данного запроса, в общем времени, проведенном на IO при выполнении запросов для данной БД
cpu_and_waits_pcntnumeric(5,2)Доля времени, проведенного на CPU при выполнении данного запроса, в общем времени, проведенном на CPU или событиях ожидания, отличных от IO, при выполнении запросов для данной БД
calls_pcntnumeric(5,2)Доля от общего количества выполнений запросов в данной БД
rows_pcntnumeric(5,2)Доля от общего количества обработанных записей в данной БД
total_time_msbigintВремя, потраченное на все выполнения данного запроса (в миллисекундах)
total_io_msbigintВремя, проведенное на IO в рамках всех выполнений данного запроса (в миллисекундах)
shr_blksnumericКоличество страниц (из буферного кеша и из файловой системы), прочитанных в рамках всех выполнений данного запроса
callsnumericЧисло выполнений запроса
rowsnumericЧисло обработанных записей
avg_time_usbigintСреднее время выполнения запроса (в микросекундах)
avg_io_usbigintСреднее время проведенное на IO в рамках выполнения запроса (в микросекундах)
avg_shr_blksbigintСреднее количество страниц (из буферного кеша и из файловой системы), прочитанных в рамках выполнения данного запроса
querytextТекст запроса

DDL:

First_db=# \d+ dba_query_io_time
View "public.dba_query_io_time"
Column | Type | Collation | Nullable | Default | Storage | Description
--------------------+--------------+-----------+----------+---------+----------+-------------
time_pcnt | numeric(5,2) | | | | main |
io_pcnt | numeric(5,2) | | | | main |
cpu_and_waits_pcnt | numeric(5,2) | | | | main |
calls_pcnt | numeric(5,2) | | | | main |
rows_pcnt | numeric(5,2) | | | | main |
total_time_ms | bigint | | | | plain |
total_io_ms | bigint | | | | plain |
shr_blks | numeric | | | | main |
calls | numeric | | | | main |
rows | numeric | | | | main |
avg_time_us | bigint | | | | plain |
avg_io_us | bigint | | | | plain |
avg_shr_blks | bigint | | | | plain |
query | text | | | | extended |
View definition:
WITH summ AS (
SELECT GREATEST(0.001::double precision, sum(pg_stat_statements.total_exec_time)) AS sum_total_exec_time,
GREATEST(0.001::double precision, sum(pg_stat_statements.blk_read_time + pg_stat_statements.blk_write_time)) AS sum_io_time,
GREATEST(0.001::double precision, sum(pg_stat_statements.total_exec_time - pg_stat_statements.blk_read_time - pg_stat_statements.blk_write_time)) AS sum_cpu_and_waits_time,
sum(pg_stat_statements.calls) AS sum_calls,
sum(pg_stat_statements.rows) AS sum_rows
FROM pg_stat_statements
WHERE pg_stat_statements.dbid = (( SELECT pg_database.oid
FROM pg_database
WHERE pg_database.datname = current_database()))
), _pg_stat_statements AS (
SELECT pg_stat_statements.query,
sum(pg_stat_statements.total_exec_time) AS total_exec_time,
sum(pg_stat_statements.blk_read_time + pg_stat_statements.blk_write_time) AS io_time,
sum(pg_stat_statements.total_exec_time - pg_stat_statements.blk_read_time - pg_stat_statements.blk_write_time) AS cpu_and_waits_time,
sum(pg_stat_statements.calls) AS calls,
sum(pg_stat_statements.rows) AS rows,
sum(pg_stat_statements.shared_blks_read + pg_stat_statements.shared_blks_hit) AS shr_blks
FROM pg_stat_statements
WHERE pg_stat_statements.dbid = (( SELECT pg_database.oid
FROM pg_database
WHERE pg_database.datname = current_database()))
GROUP BY pg_stat_statements.query
)
SELECT (100::double precision * _pg_stat_statements.total_exec_time / (( SELECT summ.sum_total_exec_time
FROM summ)))::numeric(5,2) AS time_pcnt,
(100::double precision * _pg_stat_statements.io_time / (( SELECT summ.sum_io_time
FROM summ)))::numeric(5,2) AS io_pcnt,
(100::double precision * _pg_stat_statements.cpu_and_waits_time / (( SELECT summ.sum_cpu_and_waits_time
FROM summ)))::numeric(5,2) AS cpu_and_waits_pcnt,
(100::numeric * _pg_stat_statements.calls / (( SELECT summ.sum_calls
FROM summ)))::numeric(5,2) AS calls_pcnt,
(100::numeric * _pg_stat_statements.rows / (( SELECT summ.sum_rows
FROM summ)))::numeric(5,2) AS rows_pcnt,
_pg_stat_statements.total_exec_time::bigint AS total_time_ms,
_pg_stat_statements.io_time::bigint AS total_io_ms,
_pg_stat_statements.shr_blks,
_pg_stat_statements.calls,
_pg_stat_statements.rows,
(_pg_stat_statements.total_exec_time * 1000::double precision / _pg_stat_statements.calls::double precision)::bigint AS avg_time_us,
(_pg_stat_statements.io_time * 1000::double precision / _pg_stat_statements.calls::double precision)::bigint AS avg_io_us,
(_pg_stat_statements.shr_blks / _pg_stat_statements.calls)::bigint AS avg_shr_blks,
_pg_stat_statements.query
FROM _pg_stat_statements
WHERE (_pg_stat_statements.io_time / (( SELECT summ.sum_io_time
FROM summ))) >= 0.02::double precision
UNION ALL
SELECT (100::double precision * sum(_pg_stat_statements.total_exec_time) / (( SELECT summ.sum_total_exec_time
FROM summ)))::numeric(5,2) AS time_pcnt,
(100::double precision * sum(_pg_stat_statements.io_time) / (( SELECT summ.sum_io_time
FROM summ)))::numeric(5,2) AS io_pcnt,
(100::double precision * sum(_pg_stat_statements.cpu_and_waits_time) / (( SELECT summ.sum_cpu_and_waits_time
FROM summ)))::numeric(5,2) AS cpu_and_waits_pcnt,
(100::numeric * sum(_pg_stat_statements.calls) / (( SELECT summ.sum_calls
FROM summ)))::numeric(5,2) AS calls_pcnt,
(100::numeric * sum(_pg_stat_statements.rows) / (( SELECT summ.sum_rows
FROM summ)))::numeric(5,2) AS rows_pcnt,
sum(_pg_stat_statements.total_exec_time)::bigint AS total_time_ms,
sum(_pg_stat_statements.io_time)::bigint AS total_io_ms,
sum(_pg_stat_statements.shr_blks) AS shr_blks,
sum(_pg_stat_statements.calls) AS calls,
sum(_pg_stat_statements.rows) AS rows,
(sum(_pg_stat_statements.total_exec_time) * 1000::double precision / sum(_pg_stat_statements.calls)::double precision)::bigint AS avg_time_us,
(sum(_pg_stat_statements.io_time) * 1000::double precision / sum(_pg_stat_statements.calls)::double precision)::bigint AS avg_io_us,
(sum(_pg_stat_statements.shr_blks) / sum(_pg_stat_statements.calls))::bigint AS avg_shr_blks,
'other'::text AS query
FROM _pg_stat_statements
WHERE (_pg_stat_statements.io_time / (( SELECT summ.sum_io_time
FROM summ))) < 0.02::double precision
ORDER BY 2 DESC;
Пример эксплуатации

Подайте нагрузку на БД с помощью утилиты pg_bench:

pgbench -i -s 10 --foreign-keys First_db
pgbench -c 5 -j 5 -T 30 -P 10 First_db

Пример вывода списка тяжелых запросов:

psql -d First_db -c 'table dba_query_io_time'
time_pcnt | io_pcnt | cpu_and_waits_pcnt | calls_pcnt | rows_pcnt | total_time_ms | total_io_ms | shr_blks | calls | rows | avg_time_us | avg_io_us | avg_shr_blks | query
-----------+---------+--------------------+------------+-----------+---------------+-------------+----------+--------+-------+-------------+-----------+--------------+---------------------------------------------------------------------
4.98 | 76.21 | 4.48 | 14.28 | 19.90 | 747 | 80 | 106453 | 17532 | 17532 | 43 | 5 | 6 | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
3.02 | 22.91 | 2.88 | 0.00 | 0.00 | 452 | 24 | 31655 | 1 | 1 | 452237 | 24130 | 31655 | SELECT pgse_profile.take_sample()
92.00 | 0.88 | 92.65 | 85.72 | 80.10 | 13789 | 1 | 559617 | 105211 | 70568 | 131 | 0 | 5 | other
(3 rows)

dba_query_run_time: Получение списка запросов, отсортированных по убыванию общего времени выполнения

Название поляТипОписание поля
time_pcntnumeric(5,2)Доля времени, проведенного на выполнении данного запроса, в общем времени, проведенном на выполнении запросов для данной БД
io_pcntnumeric(5,2)Доля времени, проведенного на IO при выполнении данного запроса, в общем времени, проведенном на IO при выполнении запросов для данной БД
cpu_and_waits_pcntnumeric(5,2)Доля времени, проведенного на CPU при выполнении данного запроса, в общем времени, проведенном на CPU или событиях ожидания, отличных от IO, при выполнении запросов для данной БД
calls_pcntnumeric(5,2)Доля от общего количества выполнений запросов в данной БД
rows_pcntnumeric(5,2)Доля от общего количества обработанных записей в данной БД
total_time_msbigintВремя, потраченное на все выполнения данного запроса (в миллисекундах)
total_io_msbigintВремя, проведенное на IO в рамках всех выполнений данного запроса (в миллисекундах)
shr_blksnumericКоличество страниц (из буферного кеша и из файловой системы), прочитанных в рамках всех выполнений данного запроса
callsnumericЧисло выполнений запроса
rowsnumericЧисло обработанных записей
avg_time_usbigintСреднее время выполнения запроса (в микросекундах)
avg_io_usbigintСреднее время проведенное на IO в рамках выполнения запроса (в микросекундах)
avg_shr_blksbigintСреднее количество страниц (из буферного кеша и из файловой системы), прочитанных в рамках выполнения данного запроса
querytextТекст запроса

DDL:

First_db=# \d+ dba_query_run_time
View "public.dba_query_run_time"
Column | Type | Collation | Nullable | Default | Storage | Description
--------------------+--------------+-----------+----------+---------+----------+-------------
time_pcnt | numeric(5,2) | | | | main |
io_pcnt | numeric(5,2) | | | | main |
cpu_and_waits_pcnt | numeric(5,2) | | | | main |
calls_pcnt | numeric(5,2) | | | | main |
rows_pcnt | numeric(5,2) | | | | main |
total_time_ms | bigint | | | | plain |
total_io_ms | bigint | | | | plain |
shr_blks | numeric | | | | main |
calls | numeric | | | | main |
rows | numeric | | | | main |
avg_time_us | bigint | | | | plain |
avg_io_us | bigint | | | | plain |
avg_shr_blks | bigint | | | | plain |
query | text | | | | extended |
View definition:
WITH summ AS (
SELECT GREATEST(0.001::double precision, sum(pg_stat_statements.total_exec_time)) AS sum_total_exec_time,
GREATEST(0.001::double precision, sum(pg_stat_statements.blk_read_time + pg_stat_statements.blk_write_time)) AS sum_io_time,
GREATEST(0.001::double precision, sum(pg_stat_statements.total_exec_time - pg_stat_statements.blk_read_time - pg_stat_statements.blk_write_time)) AS sum_cpu_and_waits_time,
sum(pg_stat_statements.calls) AS sum_calls,
sum(pg_stat_statements.rows) AS sum_rows
FROM pg_stat_statements
WHERE pg_stat_statements.dbid = (( SELECT pg_database.oid
FROM pg_database
WHERE pg_database.datname = current_database()))
), _pg_stat_statements AS (
SELECT pg_stat_statements.query,
sum(pg_stat_statements.total_exec_time) AS total_exec_time,
sum(pg_stat_statements.blk_read_time + pg_stat_statements.blk_write_time) AS io_time,
sum(pg_stat_statements.total_exec_time - pg_stat_statements.blk_read_time - pg_stat_statements.blk_write_time) AS cpu_and_waits_time,
sum(pg_stat_statements.calls) AS calls,
sum(pg_stat_statements.rows) AS rows,
sum(pg_stat_statements.shared_blks_read + pg_stat_statements.shared_blks_hit) AS shr_blks
FROM pg_stat_statements
WHERE pg_stat_statements.dbid = (( SELECT pg_database.oid
FROM pg_database
WHERE pg_database.datname = current_database()))
GROUP BY pg_stat_statements.query
)
SELECT (100::double precision * _pg_stat_statements.total_exec_time / (( SELECT summ.sum_total_exec_time
FROM summ)))::numeric(5,2) AS time_pcnt,
(100::double precision * _pg_stat_statements.io_time / (( SELECT summ.sum_io_time
FROM summ)))::numeric(5,2) AS io_pcnt,
(100::double precision * _pg_stat_statements.cpu_and_waits_time / (( SELECT summ.sum_cpu_and_waits_time
FROM summ)))::numeric(5,2) AS cpu_and_waits_pcnt,
(100::numeric * _pg_stat_statements.calls / (( SELECT summ.sum_calls
FROM summ)))::numeric(5,2) AS calls_pcnt,
(100::numeric * _pg_stat_statements.rows / (( SELECT summ.sum_rows
FROM summ)))::numeric(5,2) AS rows_pcnt,
_pg_stat_statements.total_exec_time::bigint AS total_time_ms,
_pg_stat_statements.io_time::bigint AS total_io_ms,
_pg_stat_statements.shr_blks,
_pg_stat_statements.calls,
_pg_stat_statements.rows,
(_pg_stat_statements.total_exec_time * 1000::double precision / _pg_stat_statements.calls::double precision)::bigint AS avg_time_us,
(_pg_stat_statements.io_time * 1000::double precision / _pg_stat_statements.calls::double precision)::bigint AS avg_io_us,
(_pg_stat_statements.shr_blks / _pg_stat_statements.calls)::bigint AS avg_shr_blks,
_pg_stat_statements.query
FROM _pg_stat_statements
WHERE (_pg_stat_statements.total_exec_time / (( SELECT summ.sum_total_exec_time
FROM summ))) >= 0.02::double precision
UNION ALL
SELECT (100::double precision * sum(_pg_stat_statements.total_exec_time) / (( SELECT summ.sum_total_exec_time
FROM summ)))::numeric(5,2) AS time_pcnt,
(100::double precision * sum(_pg_stat_statements.io_time) / (( SELECT summ.sum_io_time
FROM summ)))::numeric(5,2) AS io_pcnt,
(100::double precision * sum(_pg_stat_statements.cpu_and_waits_time) / (( SELECT summ.sum_cpu_and_waits_time
FROM summ)))::numeric(5,2) AS cpu_and_waits_pcnt,
(100::numeric * sum(_pg_stat_statements.calls) / (( SELECT summ.sum_calls
FROM summ)))::numeric(5,2) AS calls_pcnt,
(100::numeric * sum(_pg_stat_statements.rows) / (( SELECT summ.sum_rows
FROM summ)))::numeric(5,2) AS rows_pcnt,
sum(_pg_stat_statements.total_exec_time)::bigint AS total_time_ms,
sum(_pg_stat_statements.io_time)::bigint AS total_io_ms,
sum(_pg_stat_statements.shr_blks) AS shr_blks,
sum(_pg_stat_statements.calls) AS calls,
sum(_pg_stat_statements.rows) AS rows,
(sum(_pg_stat_statements.total_exec_time) * 1000::double precision / sum(_pg_stat_statements.calls)::double precision)::bigint AS avg_time_us,
(sum(_pg_stat_statements.io_time) * 1000::double precision / sum(_pg_stat_statements.calls)::double precision)::bigint AS avg_io_us,
(sum(_pg_stat_statements.shr_blks) / sum(_pg_stat_statements.calls))::bigint AS avg_shr_blks,
'other'::text AS query
FROM _pg_stat_statements
WHERE (_pg_stat_statements.total_exec_time / (( SELECT summ.sum_total_exec_time
FROM summ))) < 0.02::double precision
ORDER BY 1 DESC;
Пример эксплуатации

Подайте нагрузку на БД с помощью утилиты pg_bench:

pgbench -i -s 10 --foreign-keys First_db
pgbench -c 5 -j 5 -T 30 -P 10 First_db

Пример вывода списка тяжелых запросов:

psql -d First_db -c 'table dba_query_run_time'
time_pcnt | io_pcnt | cpu_and_waits_pcnt | calls_pcnt | rows_pcnt | total_time_ms | total_io_ms | shr_blks | calls | rows | avg_time_us | avg_io_us | avg_shr_blks | query
-----------+---------+--------------------+------------+-----------+---------------+-------------+----------+-------+-------+-------------+-----------+--------------+------------------------------------------------------------------------------------------------------
67.95 | 0.00 | 67.95 | 14.28 | 19.94 | 16443 | 0 | 122563 | 29646 | 29646 | 555 | 0 | 4 | UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2
11.67 | 0.00 | 11.67 | 14.28 | 19.94 | 2824 | 0 | 92859 | 29646 | 29646 | 95 | 0 | 3 | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
10.85 | 3.93 | 10.85 | 14.28 | 19.94 | 2626 | 0 | 387742 | 29646 | 29646 | 89 | 0 | 13 | INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
5.80 | 0.00 | 5.80 | 14.28 | 19.94 | 1405 | 0 | 189541 | 29646 | 29646 | 47 | 0 | 6 | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
3.73 | 1.45 | 3.73 | 42.86 | 20.25 | 903 | 0 | 160965 | 88958 | 30106 | 10 | 0 | 2 | other
(5 rows)

dba_locks: Построение дерева блокировок

Название поляТипОписание поля
pid_stringtextЦепочка блокировок, которая привела к ожиданию получения блокировки данным процессом
root_pidintegerКорневой узел цепочки блокировок, которая привела к ожиданию получения блокировки данным процессом
tidtextИдентификатор транзакции
lock_timeintervalВремя ожидания блокировки
statetextСостояние / текущее событие ожидания
datnamenameИмя БД
lock_mode_type_infotextДополнительная информация о блокировке, которую ожидает данный процесс
query_texttextТекст запроса
usenamenameИмя роли, под которой установлено соединение
client_addrinetIP-адрес, с которого установлено соединение
application_nametextИмя приложения, из которого установлено соединение

DDL:

First_db=# \d+ dba_locks
View "public.dba_locks"
Column | Type | Collation | Nullable | Default | Storage | Description
---------------------+----------+-----------+----------+---------+----------+-------------
pid_string | text | | | | extended |
tid | text | | | | extended |
lock_time | interval | | | | plain |
state | text | | | | extended |
datname | name | | | | plain |
lock_mode_type_info | text | C | | | extended |
query_text | text | | | | extended |
usename | name | | | | plain |
client_addr | inet | | | | main |
application_name | text | | | | extended |
root_pid | integer | | | | plain |
View definition:
with recursive tree (lev,pid,root_pid,pid_string,waitstart) as (
select
1 as lev,
all_blocking_pids.pid,
all_blocking_pids.pid as root_pid,
all_blocking_pids.pid::text as pid_string,
null::timestamptz as waitstart
from
(
select distinct unnest(pg_blocking_pids(pid)) as pid
from pg_locks
where not granted
) all_blocking_pids
where not exists (
select 1
from pg_locks blocking_pids_locks
where blocking_pids_locks.pid = all_blocking_pids.pid
and not granted
)
or all_blocking_pids.pid = 0
union all
select
tree.lev + 1,
locks_nl.pid,
tree.root_pid,
tree.pid_string || '>' || locks_nl.pid::text,
locks_nl.waitstart
from
pg_locks locks_nl,
unnest(pg_blocking_pids(locks_nl.pid)) blocking_pids_nl (pid),
tree
where not locks_nl.granted
and tree.pid = blocking_pids_nl.pid
and tree.lev <= 1000
)
select
rn_tree.pid_string,
(
case when rn_tree.pid != 0
then (
select transactionid::text
from pg_locks tid_locks
where tid_locks.pid = rn_tree.pid
and tid_locks.locktype = 'transactionid'
and tid_locks.mode = 'ExclusiveLock'
and tid_locks.granted = true
)
else 'prep.trans.'
end
) as tid,
date_trunc('second',clock_timestamp() - rn_tree.waitstart) as lock_time,
case when rn_tree.lev > 1 then 'blocked' else pgsa.state || ' / ' || pgsa.wait_event end as state,
pgsa.datname,
(
select string_agg(
locks.mode
|| ' / ' || locks.locktype
|| ' ('
|| case
when locks.locktype = 'transactionid'
then 'tid = ' || locks.transactionid
|| coalesce(
(
select ', global XID = ' || gid
from pg_prepared_xacts prep_trans
where prep_trans.transaction=locks.transactionid
),
''
)
when locks.locktype = 'relation'
then locks.full_relation_name
when locks.locktype = 'tuple'
then locks.full_relation_name || ', ctid=(' || locks.page || ',' || locks.tuple || ')'
else ''
end
|| ')'
, ' / '
)
from (
select
*,
(
select
'['
|| (case rels.relkind
when 'r' then 'ORD.TABLE'
when 'i' then 'INDEX'
when 'S' then 'SEQUENCE'
when 't' then 'TOAST.TABLE'
when 'v' then 'VIEW'
when 'm' then 'MAT.VIEW'
when 'c' then 'COMP.TYPE'
when 'f' then 'FOREIGN.TABLE'
when 'p' then 'PART.TABLE'
when 'I' then 'PART.INDEX'
end
)
|| '] '
|| (select nspname from pg_namespace where oid = rels.relnamespace)
|| '.'
|| rels.relname
|| ', oid = ' || pgl.relation
from pg_class rels
where rels.oid = pgl.relation
) as full_relation_name
from pg_locks pgl
) locks
where locks.pid = rn_tree.pid
and locks.granted = false
) as lock_mode_type_info,
regexp_replace(pgsa.query, E'[\\n\\r]+', ' ', 'g') as query_text,
pgsa.usename,
pgsa.client_addr,
pgsa.application_name,
rn_tree.root_pid -- adding it here to make the aggregates like root_pid,count(*) possible.
from (
select
lt.*,
row_number() over(partition by pid order by lev,pid_string) as rn
from tree lt
) rn_tree
left outer join pg_stat_activity pgsa on (pgsa.pid = rn_tree.pid)
where rn_tree.rn = 1
order by pid_string;
Пример эксплуатации

«Создайте» дерево блокировок. В рамках нескольких подключений выполните следующие команды:

a => \set AUTOCOMMIT 'off'
a => create table ttt_test(id bigint, name text);
a => insert into ttt_test(id,name)
values (1,'test 1'),(2,'test 2'),(3,'test 3');
a => commit;
a => update ttt_test set name = 'test 1 - mod' where id = 1;

b => \set AUTOCOMMIT 'off'
b => update ttt_test set name = 'test 2 - mod' where id = 2;
b => update ttt_test set name = 'test 1 - mod' where id = 1;

c => update ttt_test set name = 'test 2 - mod' where id = 2;

d => update ttt_test set name = 'test 1 - mod' where id = 1;

e => update ttt_test set name = 'test 2 - mod' where id = 2;

Пример вывода получившегося дерева блокировок посредством представления dba_locks:

psql -d First_db -c 'table dba_locks'

pid_string | tid | lock_time | state | datname | lock_mode_type_info | query_text | usename | client_addr | application_name | root_pid
---------------------+-------+------------+----------------------------------+----------+---------------------------------------------------------------------------+---------------------------------------------------------+----------+--------------+-------------------------------------------+----------
7800 | 96222 | 00:02:10 | idle in transaction / ClientRead | First_db | | SHOW search_path | postgres | {IP-адрес} | DBeaver 22.0.1 - SQLEditor <Script.sql> | 7800
7800>7879 | 96223 | 00:01:35 | blocked | First_db | ShareLock / transactionid (tid = 96222) | update ttt_test set name = 'test 1 - mod' where id = 1 | postgres | {IP-адрес} | DBeaver 22.0.1 - SQLEditor <Script-3.sql> | 7800
7800>7879>7911 | 96224 | 00:01:19 | blocked | First_db | ShareLock / transactionid (tid = 96223) | update ttt_test set name = 'test 2 - mod' where id = 2 | postgres | {IP-адрес} | DBeaver 22.0.1 - SQLEditor <Script-4.sql> | 7800
7800>7879>7911>7953 | 96226 | 00:00:54 | blocked | First_db | ExclusiveLock / tuple ([ORD.TABLE] ext.ttt_test, oid = 17865, ctid=(0,2)) | update ttt_test set name = 'test 2 - mod' where id = 2 | postgres | {IP-адрес} | DBeaver 22.0.1 - SQLEditor <Script-6.sql> | 7800
7800>7879>7925 | 96225 | 00:01:04 | blocked | First_db | ExclusiveLock / tuple ([ORD.TABLE] ext.ttt_test, oid = 17865, ctid=(0,1)) | update ttt_test set name = 'test 1 - mod' where id = 1 | postgres | {IP-адрес} | DBeaver 22.0.1 - SQLEditor <Script-5.sql> | 7800
(5 rows)

dba_standby_check: Построение отчета по статусу репликации

Название поляТипОписание поля
pidintegerИдентификатор процесса WAL sender, отвечающего за отсылку данных данному подписчику
clientinetIP-адрес подписчика
usernameИмя роли, используемой для репликации
statetextПоле-флаг, отображающее информацию о синхронности репликации
application_nametextИмя приложения, подписанного на репликацию
sending_lagnumericВеличина отставания (в байтах) между позицией WAL, записанной в локальный журнал, и позицией WAL, отосланной данному подписчику
receiving_lagnumericВеличина отставания (в байтах) между позицией WAL, отосланной данному подписчику, и позицией WAL, записанной в журнал подписчика
replaying_lagnumericВеличина отставания (в байтах) между позицией WAL, записанной в журнал подписчика, и позицией WAL, примененной к файлам данных подписчика
total_lagnumericВеличина отставания (в байтах) между позицией WAL, записанной в локальный журнал, и позицией WAL, примененной к файлам данных подписчика

DDL:

First_db=# \d+ dba_standby_check
View "public.dba_standby_check"
Column | Type | Collation | Nullable | Default | Storage | Description
------------------+---------+-----------+----------+---------+----------+-------------
pid | integer | | | | plain |
client | inet | | | | main |
user | name | | | | plain |
state | text | | | | extended |
application_name | text | | | | extended |
sending_lag | numeric | | | | main |
receiving_lag | numeric | | | | main |
replaying_lag | numeric | | | | main |
total_lag | numeric | | | | main |
View definition:
SELECT pg_stat_replication.pid,
pg_stat_replication.client_addr AS client,
pg_stat_replication.usename AS "user",
pg_stat_replication.sync_state AS state,
pg_stat_replication.application_name,
pg_wal_lsn_diff(pg_current_wal_lsn(), pg_stat_replication.sent_lsn) AS sending_lag,
pg_wal_lsn_diff(pg_stat_replication.sent_lsn, pg_stat_replication.flush_lsn) AS receiving_lag,
pg_wal_lsn_diff(pg_stat_replication.flush_lsn, pg_stat_replication.replay_lsn) AS replaying_lag,
pg_wal_lsn_diff(pg_current_wal_lsn(), pg_stat_replication.replay_lsn) AS total_lag
FROM pg_stat_replication;
Пример эксплуатации

Создайте сервер для реплики.

Добавьте запись для репликации в pg_hba.conf лидер-сервера:

host replication postgres {IP-адрес}/32 trust

Создайте, физическую резервную копию, предварительно настроенную для потоковой репликации:

pg_basebackup --pgdata=/pgdata/6.5.0-replica/data --write-recovery-conf --tablespace-mapping=/pgdata/6.5.0/tablespaces/Tbl_t=/pgdata/6.5.0-replica/tablespaces/Tbl_t --verbose

Поменяйте в postgresql.conf резервной копии следующие параметры:

authentication_port = '15544'
port='15433'
archive_command = '... -B /pgarclogs/6.5.0-replica...'
log_directory = '/pgerrorlogs/6.5.0-replica'
hba_file = '/pgdata/6.5.0-replica/data/pg_hba.conf'
ident_file = '/pgdata/6.5.0-replica/data/pg_ident.conf'

Запустите сервер-реплику:

pg_ctl -D /pgdata/6.5.0-replica/data start

Проверьте статус репликации на лидер-сервере:

psql -d First_db -c 'table dba_standby_check'

pid | client | user | state | application_name | sending_lag | receiving_lag | replaying_lag | total_lag
------+------------+----------+-------+------------------+-------------+---------------+---------------+-----------
4739 | {IP-адрес} | postgres | async | walreceiver | 0 | 0 | 0 | 0

Подайте на лидер-сервер нагрузку в рамках одного подключения:

psql -d First_db <<EOF
drop table ttt_test;
create table ttt_test(id bigint, name text);

insert into ttt_test(id,name)
select a.t, lpad('',1000,'test')
from generate_series(1,1000000) a(t);
EOF

Проверьте статус репликации в рамках другого подключения:

psql -d First_db -c 'select * from dba_standby_check'
pid | client | user | state | application_name | sending_lag | receiving_lag | replaying_lag | total_lag
------+------------+----------+-------+------------------+-------------+---------------+---------------+-----------
4739 | {IP-адрес} | postgres | async | walreceiver | 0 | 0 | 0 | 0
(1 row)

psql -d First_db -c 'select * from dba_standby_check'
pid | client | user | state | application_name | sending_lag | receiving_lag | replaying_lag | total_lag
------+------------+----------+-------+------------------+-------------+---------------+---------------+-----------
4739 | {IP-адрес} | postgres | async | walreceiver | 64110592 | 10223616 | 16777552 | 91111760
(1 row)

psql -d First_db -c 'select * from dba_standby_check'
pid | client | user | state | application_name | sending_lag | receiving_lag | replaying_lag | total_lag
------+------------+----------+-------+------------------+-------------+---------------+---------------+-----------
4739 | {IP-адрес} | postgres | async | walreceiver | 0 | 0 | 400 | 400
(1 row)

psql -d First_db -c 'select * from dba_standby_check'
pid | client | user | state | application_name | sending_lag | receiving_lag | replaying_lag | total_lag
------+------------+----------+-------+------------------+-------------+---------------+---------------+-----------
4739 | {IP-адрес} | postgres | async | walreceiver | 36904960 | 10166272 | 16777616 | 63848848

...

psql -d First_db -c 'select * from dba_standby_check'
pid | client | user | state | application_name | sending_lag | receiving_lag | replaying_lag | total_lag
------+------------+----------+-------+------------------+-------------+---------------+---------------+-----------
4739 | {IP-адрес} | postgres | async | walreceiver | 77119488 | 23535616 | 16778200 | 117433304
(1 row)

psql -d First_db -c 'select * from dba_standby_check'
pid | client | user | state | application_name | sending_lag | receiving_lag | replaying_lag | total_lag
------+------------+----------+-------+------------------+-------------+---------------+---------------+-----------
4739 | {IP-адрес} | postgres | async | walreceiver | 68041512 | 22618112 | 16778072 | 107437696
(1 row)

psql -d First_db -c 'select * from dba_standby_check'
pid | client | user | state | application_name | sending_lag | receiving_lag | replaying_lag | total_lag
------+------------+----------+-------+------------------+-------------+---------------+---------------+-----------
4739 | {IP-адрес} | postgres | async | walreceiver | 0 | 0 | 6774384 | 6774384
(1 row)

psql -d First_db -c 'select * from dba_standby_check'
pid | client | user | state | application_name | sending_lag | receiving_lag | replaying_lag | total_lag
------+------------+----------+-------+------------------+-------------+---------------+---------------+-----------
4739 | {IP-адрес} | postgres | async | walreceiver | 0 | 0 | 0 | 0
(1 row)

dba_top_tables: Построение списка самых больших таблиц (полный размер) в БД

Название поляТипОписание поля
schema_namenameИмя схемы
table_namenameИмя таблицы
persistencetextТип хранения таблицы (permanent / temporary / unlogged)
tab_sizetextРазмер таблицы
toast_sizetextРазмер TOAST сегмента
idx_sizetextРазмер всех индексов по данной таблице
total_sizetextПолный размер таблицы

DDL:

First_db=# \d+ dba_top_tables
View "public.dba_top_tables"
Column | Type | Collation | Nullable | Default | Storage | Description
-------------+------+-----------+----------+---------+----------+-------------
schema_name | name | | | | plain |
table_name | name | | | | plain |
persistence | text | | | | extended |
tab_size | text | | | | extended |
toast_size | text | | | | extended |
idx_size | text | | | | extended |
total_size | text | | | | extended |
View definition:
SELECT n.nspname AS schema_name,
c.relname AS table_name,
case relpersistence
when 'p' then 'permanent'
when 't' then 'temporary'
when 'u' then 'unlogged'
end as persistence,
pg_size_pretty(pg_relation_size(c.oid::regclass)) AS tab_size,
pg_size_pretty(pg_table_size(c.oid::regclass) - pg_relation_size(c.oid::regclass)) AS toast_size,
pg_size_pretty(pg_indexes_size(c.oid::regclass)) AS idx_size,
pg_size_pretty(pg_total_relation_size(c.oid::regclass)) AS total_size
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE (n.nspname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND n.nspname !~ '^pg_toast'::text AND (c.relkind = ANY (ARRAY['r'::"char", 'm'::"char"]))
ORDER BY (pg_total_relation_size(c.oid::regclass)) DESC
LIMIT 30;
Пример эксплуатации

Пример вывода списка самых больших таблиц:

psql -d First_db -c 'table dba_top_tables'

schema_name | table_name | tab_size | toast_size | idx_size | total_size
--------------+------------------------------+------------+------------+----------+------------
public | pgbench_accounts | 130 MB | 64 kB | 21 MB | 152 MB
public | pgbench_history | 1648 kB | 32 kB | 0 bytes | 1680 kB
pgse_profile | last_stat_tables | 328 kB | 32 kB | 88 kB | 448 kB
pgse_profile | last_stat_indexes | 224 kB | 32 kB | 96 kB | 352 kB
public | pgbench_tellers | 216 kB | 32 kB | 16 kB | 264 kB
...

dba_top_objects: Построение списка самых больших отношений в БД

Название поляТипОписание поля
schema_namenameИмя схемы
table_namenameИмя таблицы, к которой относится данное отношение (в случае таблицы и мат. представления - имя самой таблицы; в случае индекса, TOAST сегмента или индекса по TOAST сегменту - имя таблицы, к которой они относятся)
object_namenameИмя отношения
object_typetextТип отношения
persistencetextТип хранения таблицы (permanent / temporary / unlogged)
sizetextРазмер отношения

DDL:

First_db=# \d+ dba_top_objects
View "public.dba_top_objects"
Column | Type | Collation | Nullable | Default | Storage | Description
-------------+------+-----------+----------+---------+----------+-------------
schema_name | name | | | | plain |
table_name | name | | | | plain |
object_name | name | | | | plain |
object_type | text | | | | extended |
persistence | text | | | | extended |
size | text | | | | extended |
View definition:
SELECT n.nspname AS schema_name,
CASE
WHEN c.relkind = 't'::"char" THEN ( SELECT c2.relname
FROM pg_class c2
WHERE c2.reltoastrelid = c.oid)
WHEN c.relkind = 'i'::"char" AND n.nspname ~ '^pg_toast'::text THEN ( SELECT c4.relname
FROM pg_index i1,
pg_class c3,
pg_class c4
WHERE i1.indexrelid = c.oid AND c3.oid = i1.indrelid AND c4.reltoastrelid = c3.oid)
WHEN c.relkind = 'r'::"char" THEN c.relname
WHEN c.relkind = 'i'::"char" THEN ( SELECT c5.relname
FROM pg_index i2,
pg_class c5
WHERE i2.indexrelid = c.oid AND c5.oid = i2.indrelid)
WHEN c.relkind = 'm'::"char" THEN c.relname
ELSE NULL::name
END AS table_name,
c.relname AS object_name,
CASE
WHEN c.relkind = 't'::"char" THEN 'toast'::text
WHEN c.relkind = 'i'::"char" AND n.nspname ~ '^pg_toast'::text THEN 'toast index'::text
WHEN c.relkind = 'r'::"char" THEN 'table'::text
WHEN c.relkind = 'i'::"char" THEN 'index'::text
WHEN c.relkind = 'm'::"char" THEN 'mat.view'::text
ELSE NULL::text
END AS object_type,
case c.relpersistence
when 'p' then 'permanent'
when 't' then 'temporary'
when 'u' then 'unlogged'
end as persistence,
pg_size_pretty(pg_relation_size(c.oid::regclass)) AS size
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])
ORDER BY (pg_relation_size(c.oid::regclass)) DESC
LIMIT 30;
Пример эксплуатации

Пример вывода списка самых больших отношений в БД:

psql -d First_db -c 'table dba_top_objects'

schema_name | table_name | object_name | object_type | size
--------------+--------------------------+---------------------------+-------------+---------
public | pgbench_accounts | pgbench_accounts | table | 130 MB
public | pgbench_accounts | pgbench_accounts_pkey | index | 21 MB
public | pgbench_history | pgbench_history | table | 1648 kB
pg_toast | pg_rewrite | pg_toast_2618 | toast | 672 kB
pg_toast | pg_proc | pg_toast_1255 | toast | 344 kB
...

dba_activity: Построение расширенного отчета по активности процессов

Название поляТипОписание поля
pidintegerИдентификатор процесса
statetextСтатус процесса
xact_ageintervalВремя, прошедшее с момента открытия транзакции
query_ageintervalВремя, прошедшее с момента начала обработки запроса
change_ageintervalВремя, прошедшее с момента последнего изменения статуса процесса
wait_event_typetextТип события ожидания
wait_eventtextСобытие ожидания
datnamenameИмя БД
querytextТекст обрабатываемого запроса
usenamenameИмя роли, под которой установлено соединение
client_addrinetIP машины, с которой было установлено соединение
client_portintegerTCP-порт, который был использован для установки соединения
backend_typetextТип процесса
backend_xmin_ageintegerВозраст снимка (snapshot age) для процесса

DDL:

First_db=# \d+ dba_activity
View "public.dba_activity"
Column | Type | Collation | Nullable | Default | Storage | Description
------------------+----------+-----------+----------+---------+----------+-------------
pid | integer | | | | plain |
state | text | | | | extended |
xact_age | interval | | | | plain |
query_age | interval | | | | plain |
change_age | interval | | | | plain |
wait_event_type | text | | | | extended |
wait_event | text | | | | extended |
datname | name | | | | plain |
query | text | | | | extended |
usename | name | | | | plain |
client_addr | inet | | | | main |
client_port | integer | | | | plain |
backend_type | text | | | | extended |
backend_xmin_age | integer | | | | plain |
View definition:
SELECT pgsa.pid,
pgsa.state,
date_trunc('second'::text, clock_timestamp() - pgsa.xact_start) AS xact_age,
date_trunc('second'::text, clock_timestamp() - pgsa.query_start) AS query_age,
date_trunc('second'::text, clock_timestamp() - pgsa.state_change) AS change_age,
pgsa.wait_event_type,
pgsa.wait_event,
pgsa.datname,
pgsa.query,
pgsa.usename,
pgsa.client_addr,
pgsa.client_port,
pgsa.backend_type,
age(pgsa.backend_xmin) AS backend_xmin_age
FROM pg_stat_activity pgsa
WHERE ((clock_timestamp() - pgsa.xact_start) > '00:00:00.1'::interval OR (clock_timestamp() - pgsa.query_start) > '00:00:00.1'::interval AND pgsa.state = 'idle in transaction (aborted)'::text) AND pgsa.pid <> pg_backend_pid()
ORDER BY (COALESCE(pgsa.xact_start, pgsa.query_start));
Пример эксплуатации

В данном примере наблюдается картина, использованная для демонстрации представления dba_locks (мониторинг дерева блокировок):

psql -d First_db -c 'table dba_activity'

pid | state | xact_age | query_age | change_age | wait_event_type | wait_event | datname | query | usename | client_addr | client_port | backend_type | backend_xmin_age
------+---------------------+----------+-----------+------------+-----------------+---------------+----------+--------------------------------------------------------+----------+--------------+-------------+----------------+------------------
7800 | idle in transaction | 00:11:14 | 00:11:08 | 00:11:08 | Client | ClientRead | First_db | SHOW search_path | postgres | {IP-адрес} | {Порт} | client backend |
7879 | active | 00:10:33 | 00:10:33 | 00:10:33 | Lock | transactionid | First_db | \r +| postgres | {IP-адрес} | {Порт} | client backend | 5
| | | | | | | | update ttt_test set name = 'test 1 - mod' where id = 1 | | | | |
7911 | active | 00:10:18 | 00:10:18 | 00:10:18 | Lock | transactionid | First_db | update ttt_test set name = 'test 2 - mod' where id = 2 | postgres | {IP-адрес} | {Порт} | client backend | 5
7925 | active | 00:10:03 | 00:10:03 | 00:10:03 | Lock | tuple | First_db | update ttt_test set name = 'test 1 - mod' where id = 1 | postgres | {IP-адрес} | {Порт} | client backend | 5
7953 | active | 00:09:52 | 00:09:52 | 00:09:52 | Lock | tuple | First_db | update ttt_test set name = 'test 2 - mod' where id = 2 | postgres | {IP-адрес} | {Порт} | client backend | 5
(5 rows)

dba_activity_vacuum: Построение отчета о текущей активности процессов очистки (AUTOVACUUM+VACUUM+VACUUM FULL)

Название поляТипОписание поля
relnamenameИмя таблицы, обрабатываемой процессом очистки
ageintegerВозраст самой старой незамороженной транзакции для данной таблицы
pidintegerИдентификатор (PID) обслуживающего процесса
datidoidOID базы данных, к которой подключен этот обслуживающий процесс
datnamenameИмя базы данных, к которой подключен этот обслуживающий процесс
relidoidOID очищаемой таблицы
commandtext'VACUUM' для процессов автоматической и ручной очистки; 'VACUUM FULL' для операций ручной полной очистки; 'CLUSTER' для операции кластеризации таблиц
phasetextТекущая фаза очистки
heap_blks_totalbigintОбщее число блоков кучи в таблице на момент начала процесса очистки
heap_blks_scannedbigintЧисло просканированных блоков кучи. Так как для оптимизации сканирования применяется карта видимости, некоторые блоки могут пропускаться без осмотра; пропущенные блоки входят в это общее число, так что по завершении очистки это число станет равно heap_blks_total. Этот счетчик увеличивается только в фазе scanning heap.
heap_blks_vacuumedbigintЧисло очищенных блоков кучи. Если в таблице нет индексов, этот счетчик увеличивается только в фазе vacuuming heap (очистка кучи). Блоки, не содержащие «мертвых» кортежей, при этом пропускаются, так что этот счетчик иногда может увеличиваться резкими рывками. В случае операций VACUUM FULL / CLUSTER - NULL.
index_vacuum_countbigintКоличество завершенных циклов очистки индекса. В случае операций VACUUM FULL / CLUSTER - NULL.
max_dead_tuplesbigintЧисло «мертвых» кортежей, которое возможно сохранить, прежде чем потребуется выполнить цикл очистки индекса, в зависимости от maintenance_work_mem. В случае операций VACUUM FULL/CLUSTER - NULL.
num_dead_tuplesbigintЧисло «мертвых» кортежей, собранных со времени последнего цикла очистки индекса. В случае операций VACUUM FULL / CLUSTER - NULL.

DDL:

First_db=# \d+ dba_activity_vacuum
View "public.dba_activity_vacuum"
Column | Type | Collation | Nullable | Default | Storage | Description
--------------------+---------+-----------+----------+---------+----------+-------------
relname | name | | | | plain |
age | integer | | | | plain |
pid | integer | | | | plain |
datid | oid | | | | plain |
datname | name | | | | plain |
relid | oid | | | | plain |
command | text | | | | extended |
phase | text | | | | extended |
heap_blks_total | bigint | | | | plain |
heap_blks_scanned | bigint | | | | plain |
heap_blks_vacuumed | bigint | | | | plain |
index_vacuum_count | bigint | | | | plain |
max_dead_tuples | bigint | | | | plain |
num_dead_tuples | bigint | | | | plain |
View definition:
SELECT c1.relname,
age(c1.relfrozenxid) AS age,
v1.pid,
v1.datid,
v1.datname,
v1.relid,
'VACUUM'::text AS command,
v1.phase,
v1.heap_blks_total,
v1.heap_blks_scanned,
v1.heap_blks_vacuumed,
v1.index_vacuum_count,
v1.max_dead_tuples,
v1.num_dead_tuples
FROM pg_stat_progress_vacuum v1,
pg_class c1
WHERE v1.relid = c1.oid
UNION ALL
SELECT c2.relname,
age(c2.relfrozenxid) AS age,
v2.pid,
v2.datid,
v2.datname,
v2.relid,
v2.command,
v2.phase,
v2.heap_blks_total,
v2.heap_blks_scanned,
NULL::bigint AS heap_blks_vacuumed,
NULL::bigint AS index_vacuum_count,
NULL::bigint AS max_dead_tuples,
NULL::bigint AS num_dead_tuples
FROM pg_stat_progress_cluster v2,
pg_class c2
WHERE v2.relid = c2.oid;
Пример эксплуатации

Создайте таблицу, заполните ее данными, обновите достаточно большую долю данных, чтобы сработал AUTOVACUUM:

psql -d First_db <<EOF
drop table ttt_test;
create table ttt_test(id bigint, name text);

insert into ttt_test(id,name)
select a.t, lpad('',1000,'test')
from generate_series(1,3000000) a(t);

update ttt_test
set name = lpad('',1000,'TEST')
where mod(id,4) = 0;
EOF

Раз в несколько секунд проверяйте, не начал ли работать AUTOVACUUM:

psql -d First_db -c 'table dba_activity_vacuum'

relname | age | pid | datid | datname | relid | command | phase | heap_blks_total | heap_blks_scanned | heap_blks_vacuumed | index_vacuum_count | max_dead_tuples | num_dead_tuples
----------+-----+------+-------+----------+-------+---------+---------------+-----------------+-------------------+--------------------+--------------------+-----------------+-----------------
ttt_test | 2 | 9118 | 16800 | First_db | 17882 | VACUUM | scanning heap | 493434 | 48657 | 0 | 0 | 291 | 0
(1 row)

Запустите VACUUM FULL вручную в одном сеансе:

a => psql -d First_db -c 'VACUUM FULL ttt_test'

Проверьте в другом сеансе, что активность наблюдается посредством представления:

b => psql -d First_db -c 'table dba_activity_vacuum'

relname | age | pid | datid | datname | relid | command | phase | heap_blks_total | heap_blks_scanned | heap_blks_vacuumed | index_vacuum_count | max_dead_tuples | num_dead_tuples
----------+-----+------+-------+----------+-------+-------------+-------------------+-----------------+-------------------+--------------------+--------------------+-----------------+-----------------
ttt_test | 3 | 7761 | 16800 | First_db | 17882 | VACUUM FULL | seq scanning heap | 535715 | 359999 | | | |
(1 row)

dba_waits: Построение отчета о распределении процессов по событиям ожидания

Название поляТипОписание поля
datnamenameИмя БД
cntbigintКоличество процессов, ожидающих на данном событии
wait_eventtextСобытие ожидания
statetextСтатус процесса
wait_event_typetextТип события ожидания

DDL:

First_db=# \d+ dba_waits
View "public.dba_waits"
Column | Type | Collation | Nullable | Default | Storage | Description
-----------------+--------+-----------+----------+---------+----------+-------------
datname | name | | | | plain |
state | text | | | | extended |
wait_event_type | text | | | | extended |
wait_event | text | | | | extended |
cnt | bigint | | | | plain |
View definition:
SELECT pg_stat_activity.datname,
pg_stat_activity.state,
pg_stat_activity.wait_event_type,
pg_stat_activity.wait_event,
count(*) AS cnt
FROM pg_stat_activity
GROUP BY pg_stat_activity.datname, pg_stat_activity.state, pg_stat_activity.wait_event_type, pg_stat_activity.wait_event
ORDER BY pg_stat_activity.state, (count(*)) DESC, pg_stat_activity.datname;
Пример эксплуатации

В данном примере наблюдается картина, использованная для демонстрации представления dba_locks (мониторинг дерева блокировок):

psql -d First_db -c 'table dba_waits'


datname | state | wait_event_type | wait_event | cnt
----------+---------------------+-----------------+-------------------------+-----
First_db | active | Lock | transactionid | 2
First_db | active | Lock | tuple | 2
First_db | active | | | 1
First_db | idle | Client | ClientRead | 1
postgres | idle | Extension | Extension | 1
First_db | idle in transaction | Client | ClientRead | 1
| | Activity | BgWriterHibernate | 1
| | Activity | PasswordPolicyCacheMain | 1
| | Activity | WalWriterMain | 1
| | Activity | LogicalLauncherMain | 1
| | Activity | CheckpointerMain | 1
| | Activity | AutoVacuumMain | 1
| | Activity | PerfInsightsMain | 1
(13 rows)

dba_bloat_wastedbytes: Построение списка таблиц, отсортированных по объему пространства, не занятого для хранения актуальных версий записей

Название поляТипОписание поля
nspnamenameИмя схемы
tabnamenameИмя таблицы, к которой относится отношение
relnamenameИмя отношения
reltypetextТип отношения
tsizetextРазмер отношения (в байтах)
tplcntbigintКоличество актуальных версий записей в отношении
wspaceprcdouble precisionОценка доли пространства в отношении, не занятого для хранения актуальных версий записей
wspacebtextОценка размера пространства (в байтах) в отношении, не занятого для хранения актуальных версий записей

DDL:

First_db=# \d+ dba_bloat_wastedbytes
View "public.dba_bloat_wastedbytes"
Column | Type | Collation | Nullable | Default | Storage | Description
-------------------+---------+-----------+----------+---------+----------+-------------
nspname | name | | | | plain |
tblname | name | | | | plain |
relname | name | | | | plain |
relkind | "char" | | | | plain |
relsize | text | | | | extended |
wstd_space | text | | | | extended |
bloat | numeric | | | | main |
min_poss_bloat | numeric | | | | main |
reltuples | bigint | | | | plain |
avg_tpl_size | integer | | | | plain |
avg_full_tpl_size | bigint | | | | plain |
max_rows_per_page | integer | | | | plain |
View definition:
SELECT foo3.nspname,
foo3.tblname,
foo3.relname,
foo3.relkind,
pg_size_pretty(foo3.relpages::numeric * foo3.block_size) AS relsize,
pg_size_pretty((foo3.relpages::double precision - foo3.expected_pages)::integer::numeric * foo3.block_size) AS wstd_space,
round(((foo3.relpages::double precision - foo3.expected_pages) / foo3.relpages::double precision)::numeric, 2) AS bloat,
round(((foo3.page_usable_space - foo3.avg_full_tpl_size::double precision * foo3.max_rows_per_page) / foo3.page_usable_space)::numeric, 2) AS min_poss_bloat,
foo3.reltuples::bigint AS reltuples,
foo3.datawidth AS avg_tpl_size,
foo3.avg_full_tpl_size,
foo3.max_rows_per_page::integer AS max_rows_per_page
FROM ( SELECT foo2.nspname,
foo2.tblname,
foo2.relname,
foo2.relkind,
foo2.relpages,
foo2.reltuples,
foo2.fillfactor,
foo2.hdr,
foo2.maxalign,
foo2.block_size,
foo2.datawidth,
foo2.hdr_and_nullbits,
foo2.datawidth_ma,
foo2.hdr_and_nullbits_ma,
foo2.avg_full_tpl_size,
foo2.page_usable_space,
foo2.avg_full_tpl_size::double precision * foo2.reltuples / foo2.page_usable_space AS expected_pages,
floor(foo2.page_usable_space / foo2.avg_full_tpl_size::double precision) AS max_rows_per_page
FROM ( SELECT foo1.nspname,
foo1.tblname,
foo1.relname,
foo1.relkind,
foo1.relpages,
foo1.reltuples,
foo1.fillfactor,
foo1.hdr,
foo1.maxalign,
foo1.block_size,
foo1.datawidth,
foo1.hdr_and_nullbits,
foo1.datawidth_ma,
foo1.hdr_and_nullbits_ma,
CASE
WHEN foo1.relkind = 'i'::"char" THEN (foo1.datawidth_ma + 8 + 4)::bigint
ELSE foo1.datawidth_ma + foo1.hdr_and_nullbits_ma + 4
END AS avg_full_tpl_size,
foo1.fillfactor::double precision / 100::double precision * (foo1.block_size - 24::numeric)::double precision AS page_usable_space
FROM ( SELECT foo.nspname,
foo.tblname,
foo.relname,
foo.relkind,
foo.relpages,
foo.reltuples,
foo.fillfactor,
foo.hdr,
foo.maxalign,
foo.block_size,
foo.datawidth,
foo.hdr_and_nullbits,
foo.datawidth + foo.maxalign -
CASE
WHEN (foo.datawidth % foo.maxalign) = 0 THEN foo.maxalign
ELSE foo.datawidth % foo.maxalign
END AS datawidth_ma,
foo.hdr_and_nullbits + foo.maxalign -
CASE
WHEN (foo.hdr_and_nullbits % foo.maxalign::bigint) = 0 THEN foo.maxalign::bigint
ELSE foo.hdr_and_nullbits % foo.maxalign::bigint
END AS hdr_and_nullbits_ma
FROM ( SELECT ns.nspname,
rels.tblname,
rels.relname,
rels.relkind,
rels.relpages,
rels.reltuples,
CASE
WHEN "position"(rels.reloptions::text, 'fillfactor='::text) > 0 THEN (regexp_match(rels.reloptions::text, 'fillfactor=(\d+)'::text))[1]
ELSE
CASE
WHEN rels.relkind = 'i'::"char" THEN '90'::text
ELSE '100'::text
END
END AS fillfactor,
constants.hdr,
constants.maxalign,
constants.block_size,
sum((1::double precision - COALESCE(stat.null_frac, 0::real)) * COALESCE(stat.avg_width, 2048)::double precision)::integer AS datawidth,
constants.hdr + 1 + sum(
CASE
WHEN stat.null_frac <> 0::double precision THEN 1
ELSE 0
END) / 8 AS hdr_and_nullbits
FROM ( SELECT tbl.relnamespace,
tbl.oid AS reloid,
tbl.relname,
tbl.relkind,
tbl.relpages,
tbl.reltuples,
tbl.reloptions,
tbl.relname AS tblname
FROM pg_class tbl
WHERE (tbl.relkind = ANY (ARRAY['r'::"char", 'm'::"char"])) AND tbl.relpages > 10
UNION ALL
SELECT ind.relnamespace,
ind.oid AS reloid,
ind.relname,
ind.relkind,
ind.relpages,
ind.reltuples,
ind.reloptions,
indtbl.relname AS tblname
FROM pg_class ind
JOIN pg_index pgi ON pgi.indexrelid = ind.oid
JOIN pg_class indtbl ON indtbl.oid = pgi.indrelid
WHERE ind.relkind = 'i'::"char" AND ind.relpages > 10) rels
JOIN pg_namespace ns ON ns.oid = rels.relnamespace
JOIN pg_attribute att ON att.attrelid = rels.reloid
LEFT JOIN pg_stats stat ON stat.schemaname = ns.nspname AND stat.tablename = rels.tblname AND stat.attname = att.attname AND stat.inherited = false,
( SELECT ( SELECT current_setting('block_size'::text)::numeric AS current_setting) AS block_size,
CASE
WHEN "substring"(split_part(foo_1.v, ' '::text, 2), '#"[0-9]+.[0-9]+#"%'::text, '#'::text) = ANY (ARRAY['8.0'::text, '8.1'::text, '8.2'::text]) THEN 27
ELSE 23
END AS hdr,
CASE
WHEN foo_1.v ~ 'mingw32'::text OR foo_1.v ~ '64-bit'::text THEN 8
ELSE 4
END AS maxalign
FROM ( SELECT version() AS v) foo_1) constants
WHERE att.attnum > 0
GROUP BY ns.nspname, rels.tblname, rels.relname, rels.relkind, rels.relpages, rels.reltuples, (
CASE
WHEN "position"(rels.reloptions::text, 'fillfactor='::text) > 0 THEN (regexp_match(rels.reloptions::text, 'fillfactor=(\d+)'::text))[1]
ELSE
CASE
WHEN rels.relkind = 'i'::"char" THEN '90'::text
ELSE '100'::text
END
END), constants.hdr, constants.maxalign, constants.block_size) foo) foo1) foo2) foo3
WHERE (foo3.relpages::double precision - foo3.expected_pages)::integer > 0
ORDER BY (foo3.relpages::double precision - foo3.expected_pages) DESC
LIMIT 200;
Пример эксплуатации

Создайте таблицу, заполните ее данными и измените их. Поскольку используется fillfactor=100 (по умолчанию), при обновлении часто придется переносить записи в другие страницы. Размер записи с заголовками составляет ~1044 байта, в одну страницу будет помещаться 7 записей. Поэтому даже после VACUUM FULL останется какое-то количество незаполненного пространства (хотя таблица и станет меньше):

psql -d First_db <<EOF
drop table ttt_test;
create table ttt_test(id bigint, name text);
create index on ttt_test(name,id);

insert into ttt_test(id,name)
select a.t, lpad('',1000,'test') || a.t
from generate_series(1,100000) a(t);

update ttt_test
set name = lpad('',1000,'TEST') || id
where mod(id,10) = 0;
EOF

Подождите, пока AUTOVACUUM не закончит собирать статистику по новой таблице:

psql -d First_db -c 'table dba_bloat_wastedbytes'

nspname | tblname | relname | relkind | relsize | wstd_space | bloat | min_poss_bloat | reltuples | avg_tpl_size | avg_full_tpl_size | max_rows_per_page
--------------+--------------------+---------------------------------+---------+---------+------------+-------+----------------+-----------+--------------+-------------------+-------------------
ext | ttt_test | ttt_test | r | 123 MB | 23 MB | 0.19 | 0.11 | 100000 | 1016 | 1044 | 7
public | pgbench_accounts | pgbench_accounts | r | 128 MB | 1864 kB | 0.01 | 0.01 | 1000000 | 97 | 132 | 61
pg_catalog | pg_proc | pg_proc | r | 1624 kB | 544 kB | 0.34 | 0.01 | 3393 | 286 | 324 | 25
pg_catalog | psql_omd | psql_omd | r | 464 kB | 256 kB | 0.55 | 0.00 | 4098 | 24 | 52 | 157
pgse_profile | last_stat_indexes | last_stat_indexes | r | 440 kB | 224 kB | 0.52 | 0.02 | 435 | 465 | 500 | 16
...

psql -d First_db -c 'VACUUM FULL ttt_test'
psql -d First_db -c 'table dba_bloat_wastedbytes'

nspname | tblname | relname | relkind | relsize | wstd_space | bloat | min_poss_bloat | reltuples | avg_tpl_size | avg_full_tpl_size | max_rows_per_page
--------------+--------------------+---------------------------------+---------+---------+------------+-------+----------------+-----------+--------------+-------------------+-------------------
ext | ttt_test | ttt_test | r | 112 MB | 12 MB | 0.11 | 0.11 | 100000 | 1016 | 1044 | 7
public | pgbench_accounts | pgbench_accounts | r | 128 MB | 1864 kB | 0.01 | 0.01 | 1000000 | 97 | 132 | 61
pg_catalog | pg_proc | pg_proc | r | 1624 kB | 544 kB | 0.34 | 0.01 | 3393 | 286 | 324 | 25
pg_catalog | psql_omd | psql_omd | r | 464 kB | 256 kB | 0.55 | 0.00 | 4098 | 24 | 52 | 157
pgse_profile | last_stat_indexes | last_stat_indexes | r | 440 kB | 224 kB | 0.52 | 0.02 | 435 | 465 | 500 | 16
...

Оценка доли пространства, не используемого для актуальных версий записей, снизилась до минимально возможной оценки (bloat = min_poss_bloat).

dba_bloat_tbloat: Построение списка таблиц, отсортированных по доле пространства, не занятого для хранения актуальных версий записей

Название поляТипОписание поля
nspnamenameИмя схемы
tabnamenameИмя таблицы, к которой относится отношение
relnamenameИмя отношения
reltypetextТип отношения
tsizetextРазмер отношения (в байтах)
tplcntbigintКоличество актуальных версий записей в отношении
wspaceprcdouble precisionОценка доли пространства в отношении, не занятого для хранения актуальных версий записей
wspacebtextОценка размера пространства (в байтах) в отношении, не занятого для хранения актуальных версий записей

DDL:

First_db=# \d+ dba_bloat_tbloat
View "public.dba_bloat_tbloat"
Column | Type | Collation | Nullable | Default | Storage | Description
-------------------+---------+-----------+----------+---------+----------+-------------
nspname | name | | | | plain |
tblname | name | | | | plain |
relname | name | | | | plain |
relkind | "char" | | | | plain |
relsize | text | | | | extended |
wstd_space | text | | | | extended |
bloat | numeric | | | | main |
min_poss_bloat | numeric | | | | main |
reltuples | bigint | | | | plain |
avg_tpl_size | integer | | | | plain |
avg_full_tpl_size | bigint | | | | plain |
max_rows_per_page | integer | | | | plain |
View definition:
SELECT foo3.nspname,
foo3.tblname,
foo3.relname,
foo3.relkind,
pg_size_pretty(foo3.relpages::numeric * foo3.block_size) AS relsize,
pg_size_pretty((foo3.relpages::double precision - foo3.expected_pages)::integer::numeric * foo3.block_size) AS wstd_space,
round(((foo3.relpages::double precision - foo3.expected_pages) / foo3.relpages::double precision)::numeric, 2) AS bloat,
round(((foo3.page_usable_space - foo3.avg_full_tpl_size::double precision * foo3.max_rows_per_page) / foo3.page_usable_space)::numeric, 2) AS min_poss_bloat,
foo3.reltuples::bigint AS reltuples,
foo3.datawidth AS avg_tpl_size,
foo3.avg_full_tpl_size,
foo3.max_rows_per_page::integer AS max_rows_per_page
FROM ( SELECT foo2.nspname,
foo2.tblname,
foo2.relname,
foo2.relkind,
foo2.relpages,
foo2.reltuples,
foo2.fillfactor,
foo2.hdr,
foo2.maxalign,
foo2.block_size,
foo2.datawidth,
foo2.hdr_and_nullbits,
foo2.datawidth_ma,
foo2.hdr_and_nullbits_ma,
foo2.avg_full_tpl_size,
foo2.page_usable_space,
foo2.avg_full_tpl_size::double precision * foo2.reltuples / foo2.page_usable_space AS expected_pages,
floor(foo2.page_usable_space / foo2.avg_full_tpl_size::double precision) AS max_rows_per_page
FROM ( SELECT foo1.nspname,
foo1.tblname,
foo1.relname,
foo1.relkind,
foo1.relpages,
foo1.reltuples,
foo1.fillfactor,
foo1.hdr,
foo1.maxalign,
foo1.block_size,
foo1.datawidth,
foo1.hdr_and_nullbits,
foo1.datawidth_ma,
foo1.hdr_and_nullbits_ma,
CASE
WHEN foo1.relkind = 'i'::"char" THEN (foo1.datawidth_ma + 8 + 4)::bigint
ELSE foo1.datawidth_ma + foo1.hdr_and_nullbits_ma + 4
END AS avg_full_tpl_size,
foo1.fillfactor::double precision / 100::double precision * (foo1.block_size - 24::numeric)::double precision AS page_usable_space
FROM ( SELECT foo.nspname,
foo.tblname,
foo.relname,
foo.relkind,
foo.relpages,
foo.reltuples,
foo.fillfactor,
foo.hdr,
foo.maxalign,
foo.block_size,
foo.datawidth,
foo.hdr_and_nullbits,
foo.datawidth + foo.maxalign -
CASE
WHEN (foo.datawidth % foo.maxalign) = 0 THEN foo.maxalign
ELSE foo.datawidth % foo.maxalign
END AS datawidth_ma,
foo.hdr_and_nullbits + foo.maxalign -
CASE
WHEN (foo.hdr_and_nullbits % foo.maxalign::bigint) = 0 THEN foo.maxalign::bigint
ELSE foo.hdr_and_nullbits % foo.maxalign::bigint
END AS hdr_and_nullbits_ma
FROM ( SELECT ns.nspname,
rels.tblname,
rels.relname,
rels.relkind,
rels.relpages,
rels.reltuples,
CASE
WHEN "position"(rels.reloptions::text, 'fillfactor='::text) > 0 THEN (regexp_match(rels.reloptions::text, 'fillfactor=(\d+)'::text))[1]
ELSE
CASE
WHEN rels.relkind = 'i'::"char" THEN '90'::text
ELSE '100'::text
END
END AS fillfactor,
constants.hdr,
constants.maxalign,
constants.block_size,
sum((1::double precision - COALESCE(stat.null_frac, 0::real)) * COALESCE(stat.avg_width, 2048)::double precision)::integer AS datawidth,
constants.hdr + 1 + sum(
CASE
WHEN stat.null_frac <> 0::double precision THEN 1
ELSE 0
END) / 8 AS hdr_and_nullbits
FROM ( SELECT tbl.relnamespace,
tbl.oid AS reloid,
tbl.relname,
tbl.relkind,
tbl.relpages,
tbl.reltuples,
tbl.reloptions,
tbl.relname AS tblname
FROM pg_class tbl
WHERE (tbl.relkind = ANY (ARRAY['r'::"char", 'm'::"char"])) AND tbl.relpages > 10
UNION ALL
SELECT ind.relnamespace,
ind.oid AS reloid,
ind.relname,
ind.relkind,
ind.relpages,
ind.reltuples,
ind.reloptions,
indtbl.relname AS tblname
FROM pg_class ind
JOIN pg_index pgi ON pgi.indexrelid = ind.oid
JOIN pg_class indtbl ON indtbl.oid = pgi.indrelid
WHERE ind.relkind = 'i'::"char" AND ind.relpages > 10) rels
JOIN pg_namespace ns ON ns.oid = rels.relnamespace
JOIN pg_attribute att ON att.attrelid = rels.reloid
LEFT JOIN pg_stats stat ON stat.schemaname = ns.nspname AND stat.tablename = rels.tblname AND stat.attname = att.attname AND stat.inherited = false,
( SELECT ( SELECT current_setting('block_size'::text)::numeric AS current_setting) AS block_size,
CASE
WHEN "substring"(split_part(foo_1.v, ' '::text, 2), '#"[0-9]+.[0-9]+#"%'::text, '#'::text) = ANY (ARRAY['8.0'::text, '8.1'::text, '8.2'::text]) THEN 27
ELSE 23
END AS hdr,
CASE
WHEN foo_1.v ~ 'mingw32'::text OR foo_1.v ~ '64-bit'::text THEN 8
ELSE 4
END AS maxalign
FROM ( SELECT version() AS v) foo_1) constants
WHERE att.attnum > 0
GROUP BY ns.nspname, rels.tblname, rels.relname, rels.relkind, rels.relpages, rels.reltuples, (
CASE
WHEN "position"(rels.reloptions::text, 'fillfactor='::text) > 0 THEN (regexp_match(rels.reloptions::text, 'fillfactor=(\d+)'::text))[1]
ELSE
CASE
WHEN rels.relkind = 'i'::"char" THEN '90'::text
ELSE '100'::text
END
END), constants.hdr, constants.maxalign, constants.block_size) foo) foo1) foo2) foo3
WHERE (foo3.relpages::double precision - foo3.expected_pages)::integer > 0
ORDER BY (round(((foo3.relpages::double precision - foo3.expected_pages) / foo3.relpages::double precision)::numeric, 2)) DESC
LIMIT 200;
Пример эксплуатации

В данном разделе приведен пример вывода списка таблиц с самой большой долей пространства, не используемого для актуальных версий записей.

Очень часто наверху списка оказываются словарные небольшие таблицы, поэтому использование представления dba_bloat_wastedbytes в общем случае более полезно:

psql -d First_db -c 'table dba_bloat_tbloat'

nspname | tblname | relname | relkind | relsize | wstd_space | bloat | min_poss_bloat | reltuples | avg_tpl_size | avg_full_tpl_size | max_rows_per_page
--------------+--------------------+---------------------------------+---------+---------+------------+-------+----------------+-----------+--------------+-------------------+-------------------
public | pgbench_tellers | pgbench_tellers | r | 216 kB | 208 kB | 0.98 | 0.00 | 100 | 12 | 44 | 185
pg_catalog | psql_omd | psql_omd_oid_index | i | 208 kB | 120 kB | 0.57 | 0.00 | 4098 | 8 | 20 | 367
pg_catalog | psql_omd | psql_omd | r | 464 kB | 256 kB | 0.55 | 0.00 | 4098 | 24 | 52 | 157
pgse_profile | last_stat_tables | last_stat_tables | r | 336 kB | 176 kB | 0.53 | 0.00 | 318 | 468 | 508 | 16
pgse_profile | last_stat_indexes | last_stat_indexes | r | 440 kB | 224 kB | 0.51 | 0.02 | 436 | 465 | 500 | 16
...

Выполните полную очистку таблицы с самой большой долей неиспользуемого пространства. В данном случае это pgbench_tellers:

psql -d First_db -c 'VACUUM FULL pgbench_tellers'

Перепроверьте информацию по таблице:

psql -d First_db -c 'table dba_bloat_tbloat'

Поскольку таблица пропала из выборки dba_bloat_tbloat (доля неиспользуемого пространства стала околонулевой), можно проверить ее текущий размер посредством команды:

psql -d First_db -c "select relpages*8192 as relation_size from pg_class where relname = 'pgbench_tellers'"

relation_size
---------------
8192
(1 row)

Размер таблицы действительно уменьшился до одной страницы.

dba_unused_indexes: Построение списка неиспользуемых индексов

Название поляТипОписание поля
schemanamenameИмя схемы
relnamenameИмя таблицы, к которой относится индекс
index_namenameИмя индекса
indexdeftext«Определение» индекса: алгоритм индексирования, список колонок индекса, дополнительные свойства (набор INCLUDE колонок, ограничения WHERE, параметры хранения)
index_sizetextРазмер индекса
last_db_stats_resettimestamp with time zoneВремя последнего сброса статистики по БД

DDL:

First_db=# \d+ dba_unused_indexes
View "public.dba_unused_indexes"
Column | Type | Collation | Nullable | Default | Storage | Description
---------------------+--------------------------+-----------+----------+---------+----------+-------------
schema_name | name | | | | plain |
tab_name | name | | | | plain |
idx_name | name | | | | plain |
idx_def | text | | | | extended |
idx_size | text | | | | extended |
last_db_stats_reset | timestamp with time zone | | | | plain |
View definition:
SELECT pgsai.schemaname AS schema_name,
pgsai.relname AS tab_name,
pgsai.indexrelname AS idx_name,
"substring"(pgi.indexdef, "position"(pgi.indexdef, ' USING '::text) + 7) AS idx_def,
pg_size_pretty(pg_relation_size(pgsai.indexrelid::regclass)) AS idx_size,
stats_reset.stats_reset AS last_db_stats_reset
FROM pg_stat_all_indexes pgsai,
pg_indexes pgi,
( SELECT pg_stat_database.stats_reset
FROM pg_stat_database
WHERE pg_stat_database.datname = current_database()) stats_reset
WHERE pgsai.idx_scan = 0 AND (pgsai.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND pgsai.relname !~~ 'pg_toast%'::text AND pgi.schemaname = pgsai.schemaname AND pgi.tablename = pgsai.relname AND pgi.indexname = pgsai.indexrelname
ORDER BY (pg_relation_size(pgsai.indexrelid::regclass)) DESC
LIMIT 30;
Пример эксплуатации

Создайте таблицу с двумя индексами:

psql -d First_db <<EOF
drop table ttt_test;
create table ttt_test(
id bigint,
name text
);

insert into ttt_test
select
a.t,
'name ' || a.t
from generate_series(1,100000) a(t);

create index ttt_test_id_name_unused on ttt_test(id,name);
create index ttt_test_name_id_unused on ttt_test(name,id);
EOF

В верхней части списка неиспользуемых индексов наблюдаются оба индекса (поскольку они крупнее остальных неиспользуемых индексов):

psql -d First_db -c 'table dba_unused_indexes'

schema_name | tab_name | idx_name | idx_def | idx_size | last_db_stats_reset
--------------+------------------------------+---------------------------------+------------------------------------------------------+------------+-------------------------------
ext | ttt_test | ttt_test_name_id_unused | btree (name, id) | 3984 kB | 2023-07-13 12:31:05.637901+03
ext | ttt_test | ttt_test_id_name_unused | btree (id, name) | 3976 kB | 2023-07-13 12:31:05.637901+03
pgse_profile | sample_statements | ix_sample_stmts_qid | btree (queryid_md5) | 32 kB | 2023-07-13 12:31:05.637901+03
pgse_profile | sample_kcache | pk_sample_kcache_n | btree (server_id, sample_id, datid, userid, queryid) | 32 kB | 2023-07-13 12:31:05.637901+03
pgse_profile | sample_kcache_total | pk_sample_kcache_total | btree (server_id, sample_id, datid) | 16 kB | 2023-07-13 12:31:05.637901+03
...

Используйте один из индексов:

explain analyze
select * from ttt_test where name = 'name 1000';

Index Only Scan using ttt_test_name_id_unused on ttt_test (cost=0.42..2.44 rows=1 width=19) (actual time=0.072..0.072 rows=1 loops=1)
Index Cond: (name = 'name 1000'::text)
Heap Fetches: 0
Planning Time: 0.328 ms
Execution Time: 0.113 ms

Перепроверьте список неиспользуемых индексов:

psql -d First_db -c 'table dba_unused_indexes'

schema_name | tab_name | idx_name | idx_def | idx_size | last_db_stats_reset
--------------+------------------------------+---------------------------------+------------------------------------------------------+------------+-------------------------------
ext | ttt_test | ttt_test_id_name_unused | btree (id, name) | 3976 kB | 2023-07-13 12:31:05.637901+03
pgse_profile | sample_statements | ix_sample_stmts_qid | btree (queryid_md5) | 32 kB | 2023-07-13 12:31:05.637901+03
pgse_profile | sample_kcache | pk_sample_kcache_n | btree (server_id, sample_id, datid, userid, queryid) | 32 kB | 2023-07-13 12:31:05.637901+03
pgse_profile | sample_kcache_total | pk_sample_kcache_total | btree (server_id, sample_id, datid) | 16 kB | 2023-07-13 12:31:05.637901+03
pgse_profile | sample_statements_total | pk_sample_statements_total | btree (server_id, sample_id, datid) | 16 kB | 2023-07-13 12:31:05.637901+03
...

Индекс ttt_test_name_id_unused из списка пропал, поскольку был использован.

dba_duplicated_indexes: Построение списка индексов, перекрываемых другими индексами

Название поляТипОписание поля
tab_nametextИмя схемы + имя таблицы
idx_nameregclassИмя индекса
idx_deftext«Определение» индекса: алгоритм индексирования, список колонок индекса, дополнительные свойства (набор INCLUDE колонок, ограничения WHERE, параметры хранения)
used_by_constraintstextСписок ограничений целостности, использующих данный индекс (имя ограничения целостности и тип)
idx_sizebigintРазмер индекса
cover_idx_nameregclassИмя перекрывающего индекса
cover_idx_deftext«Определение» перекрывающего индекса: алгоритм индексирования, список колонок индекса, дополнительные свойства (набор INCLUDE колонок, ограничения WHERE, параметры хранения)
cover_used_by_constraintstextСписок ограничений целостности, использующих перекрывающий индекс (имя ограничения целостности и тип)

DDL:

First_db=# \d+ dba_duplicated_indexes
View "public.dba_duplicated_indexes"
Column | Type | Collation | Nullable | Default | Storage | Description
---------------------------+--------+-----------+----------+---------+----------+-------------
schema_name | name | | | | plain |
tab_name | name | | | | plain |
idx_name | name | | | | plain |
idx_def | text | | | | extended |
used_by_constraints | text | C | | | extended |
idx_size | bigint | | | | plain |
cover_idx_name | name | | | | plain |
cover_idx_def | text | | | | extended |
cover_used_by_constraints | text | C | | | extended |
View definition:
WITH indlist AS (
SELECT pgi.schemaname AS schema_name,
pgi.tablename AS table_name,
pgi.indexname AS index_name,
"substring"(pgi.indexdef, "position"(pgi.indexdef, ' USING '::text) + 7) AS indexdef,
( SELECT string_agg(attrs.attname::text, ', '::text) AS string_agg
FROM ( SELECT a.attname
FROM pg_attribute a
WHERE a.attrelid = ((pgi.schemaname::text || '.'::text) || pgi.indexname::text)::regclass::oid
ORDER BY a.attnum) attrs) AS column_list,
( SELECT string_agg(((c.conname::text || '('::text) || c.contype::text) || ')'::text, ','::text) AS string_agg
FROM pg_constraint c
WHERE c.conindid = ((pgi.schemaname::text || '.'::text) || pgi.indexname::text)::regclass::oid) AS used_by_constraints,
pg_relation_size(((pgi.schemaname::text || '.'::text) || pgi.indexname::text)::regclass) AS index_size
FROM pg_indexes pgi
WHERE pgi.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name, 'pg_toast'::name])
)
SELECT il1.schema_name,
il1.table_name AS tab_name,
il1.index_name AS idx_name,
il1.indexdef AS idx_def,
il1.used_by_constraints,
il1.index_size AS idx_size,
il2.index_name AS cover_idx_name,
il2.indexdef AS cover_idx_def,
il2.used_by_constraints AS cover_used_by_constraints
FROM indlist il1,
indlist il2
WHERE il2.schema_name = il1.schema_name AND il2.table_name = il1.table_name AND il2.index_name <> il1.index_name AND "position"(il2.column_list, il1.column_list) = 1
ORDER BY il1.index_size DESC
LIMIT 30;
Пример эксплуатации

Создайте таблицу с несколькими индексами, часть из которых «перекрывает» друг друга:

psql -d First_db <<EOF
drop table ttt_test;
create table ttt_test(
id bigint primary key,
name text
);

create index ttt_test_id_test on ttt_test using hash(id);
create index ttt_test_id_name_test on ttt_test(id,name);
create index ttt_test_name_id_test on ttt_test(name,id);
EOF

Вывод списка дублирующих индексов:

psql -d First_db -c 'table dba_duplicated_indexes'

schema_name | tab_name | idx_name | idx_def | used_by_constraints | idx_size | cover_idx_name | cover_idx_def | cover_used_by_constraints
-------------+----------+------------------+------------+---------------------+----------+-----------------------+------------------+---------------------------
ext | ttt_test | ttt_test_id_test | hash (id) | | 49152 | ttt_test_id_name_test | btree (id, name) |
ext | ttt_test | ttt_test_id_test | hash (id) | | 49152 | ttt_test_pkey | btree (id) | ttt_test_pkey(p)
ext | ttt_test | ttt_test_pkey | btree (id) | ttt_test_pkey(p) | 8192 | ttt_test_id_name_test | btree (id, name) |
ext | ttt_test | ttt_test_pkey | btree (id) | ttt_test_pkey(p) | 8192 | ttt_test_id_test | hash (id) |
(4 rows)

В полученном списке наблюдаются индексы ttt_test_id_test и ttt_test_pkey по два раза, потому что каждый из них может быть использован вместо другого, индекс ttt_test_id_name_test, у которого первая колонка также id, может быть использован вместо каждого из них.