Раньше, когда запросы начинали тупить, а метрики процессора и памяти выглядели нормально, оставалось только гадать: диск виноват или что‑то внутри PostgreSQL перемалывает данные вхолостую? В лучшем случае на помощь приходили системные утилиты вроде iostat, но они ничего не знали, какой именно процесс базы данных и какую именно операцию выполняет. PostgreSQL 16 дал администраторам инструмент, который наконец‑то соединяет внутреннюю кухню базы с реальной картиной ввода‑вывода - представление pg_stat_io. Эта статья поможет вам не просто смотреть на цифры, а по‑настоящему понимать, когда за тормозами стоит дисковая подсистема, а когда проблема совсем в другом.
Что такое pg_stat_io и где его искать
pg_stat_io - это системное представление в составе семейства pg_stat_*, появившееся в PostgreSQL 16. Оно собирает кумулятивную статистику всех операций ввода‑вывода, которые прошли через буферный кеш базы. В отличие от предшественников (pg_stat_bgwriter, pg_stat_database), здесь I/O разложен по полочкам: вы видите не просто общее количество чтений, а кто именно читал, в каком контексте и сколько времени на это потратил.
Статистика не обнуляется автоматически - она накапливается с момента старта кластера. Чтобы оценить нагрузку за конкретный промежуток времени, нужно делать снимки и вычислять дельту. Записи в представлении сгруппированы по трём ключевым измерениям:
- backend_type - тип процесса (клиентский обработчик, autovacuum worker, checkpointer, background writer и другие);
- object - тип объекта ввода‑вывода:
relation(обычные таблицы и индексы),temp relation(временные объекты) и служебные; - context - контекст операции:
normal(обычная работа),vacuum,bulkread(крупное последовательное сканирование),bulkwrite(массовая запись, например, COPY или CTAS).
Именно благодаря этим разрезам можно быстро сузить круг подозреваемых, когда система начинает спотыкаться.
Ключевые показатели pg_stat_io: разбор полей
Чтобы не утонуть в столбцах, разобьём их на три смысловые группы.
Счётчики операций
- reads - количество чтений блоками (обычно по 8 КБ), когда данных не оказалось в shared_buffers и пришлось идти к диску.
- writes - количество записей грязных блоков из кеша на нижележащий уровень.
- extends - операции расширения файла; возникают, когда таблица растёт и ей добавляются новые блоки на конце.
- hits - попадания в буферный кеш: блок нашёлся в shared_buffers, физического чтения не было.
- evictions - вытеснение блока из shared_buffers, чтобы освободить место для другого. Частые вытеснения при активной работе клиентских бэкендов - один из первых сигналов нехватки кеша.
- reuses - повторное использование блока в ограниченных кольцевых буферах, которые PostgreSQL применяет при массовых операциях (bulkread, bulkwrite, vacuum), чтобы не вымывать горячие страницы из основного кеша.
- fsyncs - количество вызовов
fsync()для сброса данных на постоянное хранилище.
Временны́е метрики
- read_time, write_time, extend_time - суммарное время, затраченное на чтение, запись и расширение файлов соответственно.
- fsync_time - общее время, потраченное на вызовы
fsync().
Все поля *_time заполняются только при включённом параметре track_io_timing. Если он выключен (значение по умолчанию off), в этих столбцах будут нули - и вы никак не оцените задержки ввода‑вывода. Сам параметр практически не сказывается на производительности современных систем, поэтому в продуктивных средах его почти всегда стоит держать включённым.
Измерения, которые помогают сужать поиск
Как уже сказано, каждая строка pg_stat_io уникальна в разрезе (backend_type, object, context). Например, процесс autovacuum worker может читать отношение в контексте vacuum, а клиентский бэкенд - в normal. Благодаря этому вы с лёгкостью отделяете фоновую активность от пользовательской и понимаете, какой именно тип работы упирается в диск.
Как отличить проблему именно с I/O от других причин тормозов
Теперь к главному: как по этим метрикам понять, что тормоза вызваны дисковой подсистемой, а не, скажем, блокировками или кривым планом запроса. Никаких жестких цифр мы не дадим - пороги зависят от вашего железа, нагрузки и ожидаемой производительности. Но логика интерпретации одна и та же.
Медленный диск: высокое время при скромном количестве операций
Если взять два последовательных снимка и посчитать среднюю задержку как read_time / reads (или write_time / writes), аномальное значение сразу бросится в глаза. Допустим, нагрузка стабильна, количество чтений не изменилось, а общее read_time выросло втрое - почти наверняка проблема на стороне дисковой подсистемы: перегруженный RAID, сетевые лаги в SAN/NAS, изношенный SSD. То же самое касается write_time и extend_time. Тревожный звоночек - когда fsync_time начинает доминировать во времени транзакций, хотя количество самих вызовов не изменилось.
Нехватка shared_buffers: лавина evictions
Резкий скачок evictions у client backend при одновременном падении доли hits говорит о том, что буферный кеш мал для текущего рабочего набора. PostgreSQL вынужден постоянно выталкивать нужные страницы и читать их заново. С точки зрения наблюдателя приложение тупит, а вы видите в pg_stat_io: много reads и evictions, мало hits. Формально проблема не в медленном диске - диск честно выполняет свою работу. Но ограниченный кеш порождает избыточный I/O, который и создаёт узкое место.
Массовые операции упираются в extends и writes
Контексты bulkread и bulkwrite покрывают тяжёлые операции вроде COPY, CTAS, больших последовательных сканов. Если в этих контекстах наблюдается аномальный рост extends и writes относительно обычного фона - возможно, нагруженная дисковая подсистема не успевает обслуживать пиковый поток записи. Тогда задержки растут не только в этих операциях, но и каскадно затрагивают обычные транзакции, конкурирующие за пропускную способность диска.
Задержки fsync и их влияние на транзакции
Когда PostgreSQL фиксирует транзакцию, он сбрасывает журнал WAL на диск - часто именно с помощью fsync(). Если fsync_time велико, а значения fsyncs не выросли, значит, каждый отдельный сброс стал медленнее. Это напрямую замедляет все операции записи и может быть симптомом проблем с диском, контроллером или батарейкой кеша RAID‑контроллера.
Практические запросы для диагностики
Ниже - не готовые ответы для вашей системы, а шаблоны, с которых можно начать. Снимайте снимки с интервалом, соответствующим характеру нагрузки (от 10 секунд до нескольких минут), и сравнивайте дельты.
Снимаем дельту за интервал
Создайте временную таблицу или используйте в рамках сессии две выборки. Простейший вариант - выгрузить текущие значения в CTE, подождать, а потом вычислить разницу:
-- Первый снимок
CREATE TEMP TABLE io_snap AS
SELECT *
FROM pg_stat_io;
-- ... проходит интервал наблюдения ...
-- Дельта за интервал по всем измерениям
SELECT cur.backend_type,
cur.object,
cur.context,
cur.reads - COALESCE(prev.reads, 0) AS reads_delta,
cur.writes - COALESCE(prev.writes, 0) AS writes_delta,
cur.read_time - COALESCE(prev.read_time, 0) AS read_time_delta,
cur.write_time - COALESCE(prev.write_time, 0) AS write_time_delta,
cur.fsync_time - COALESCE(prev.fsync_time, 0) AS fsync_time_delta
FROM pg_stat_io cur
LEFT JOIN io_snap prev ON cur.backend_type = prev.backend_type
AND cur.object = prev.object
AND cur.context = prev.context
WHERE cur.reads != prev.reads
OR cur.writes != prev.writes;
Так вы увидите, какие процессы и контексты сгенерировали основной объём I/O за интервал.
Кто генерирует больше всего I/O
Чтобы быстро найти самых активных участников, сложите операции по backend_type:
SELECT backend_type,
sum(reads) AS total_reads,
sum(writes) AS total_writes,
sum(read_time) AS total_read_time,
sum(write_time) AS total_write_time
FROM pg_stat_io
GROUP BY backend_type
ORDER BY total_read_time + total_write_time DESC;
Если в топе client backend, а время операций значительно превышает объём - вероятно, диск перегружен пользовательскими запросами. Если же лидирует autovacuum worker, возможно, автовакуум нагружает подсистему, замедляя всё остальное.
Разделяем контексты
Детализируйте по контексту, чтобы понять, обычная ли это OLTP‑нагрузка, массовые операции или работа вакуума:
SELECT backend_type,
context,
sum(reads) AS reads,
sum(writes) AS writes,
sum(extends) AS extends,
sum(evictions) AS evictions,
sum(read_time) AS read_time,
sum(write_time) AS write_time
FROM pg_stat_io
GROUP BY backend_type,
context
ORDER BY read_time + write_time DESC;
Такой срез помогает отличить всплеск от bulkwrite (кто‑то загружает большой объём данных) от деградации в обычных normal‑операциях.
Что pg_stat_io не покажет - ограничения
pg_stat_io - мощный, но не всевидящий инструмент. Нужно чётко понимать, где его данные нужно дополнять другими средствами.
Кеш ОС и контроллера остаётся невидимым. PostgreSQL учитывает операции, проходящие через буферный кеш. Если страница читается из страничного кеша Linux, а не с физического диска, read_time будет низким, и это нормально. Но вы не увидите, на каком именно уровне - ОС или RAID‑контроллер - задержка минимальна. Для полной картины обязательно добавляйте iostat, atop или аналоги.
Операции мимо shared_buffers. Некоторые действия, например перенос таблицы в другое табличное пространство командой ALTER TABLE ... SET TABLESPACE, идут в обход буферного кеша и не фиксируются в pg_stat_io. То же касается прямого ввода‑вывода, если он появится в будущих версиях.
Нет разбивки по таблицам. Если вы хотите узнать, какая конкретно таблица генерирует больше всего чтений, придётся обратиться к pg_statio_user_tables или pg_stat_statements. Последний, кстати, отлично дополняет pg_stat_io, показывая, какие запросы создают нагрузку на ввод‑вывод.
Сетевое хранилище. Задержки NAS/SAN ниже уровня системного вызова fsync() не видны напрямую. Если файловая система уже вернула управление, а данные ещё «висят» в сетевом буфере, pg_stat_io посчитает операцию быстрой. Поэтому для сетевых хранилищ параллельно мониторят сетевую статистику и задержки на уровне гипервизора или хранилища.
FAQ по pg_stat_io
Чем pg_stat_io отличается от pg_stat_bgwriter?
pg_stat_bgwriter показывает агрегированную статистику фонового писателя и контрольных точек: как часто срабатывали чекпоинты, сколько грязных страниц сбрасывал bgwriter. pg_stat_io даёт детализацию на порядок выше: кто именно (backend_type) и в каком контексте выполнял любые I/O‑операции. Их используют вместе, а не как замену друг другу.
Почему все *_time равны нулю?
Скорее всего, параметр track_io_timing выключен. Включите его командой ALTER SYSTEM SET track_io_timing = on; и перечитайте конфигурацию (SELECT pg_reload_conf();). После этого временные метрики начнут наполняться с нуля.
Как часто надо снимать метрики для построения трендов?
Универсального ответа нет. Для высоконагруженных OLTP‑систем снимают раз в 10–30 секунд, чтобы не пропустить короткие всплески. Для аналитических нагрузок допустим интервал в 1–5 минут. Главное - сохранять дельты в систему мониторинга и строить графики, тогда аномалии становятся очевидными.
Видит ли pg_stat_io задержки на сетевом хранилище (NAS/SAN)?
Да, но только те задержки, которые видны на уровне системных вызовов PostgreSQL. Если хранилище отвечает быстро, но на самом деле данные физически ещё не зафиксированы, fsync_time не отразит реальную ситуацию. Для сетевых хранилищ нужны дополнительные метрики с самого хранилища и из ОС.
Можно ли использовать pg_stat_io для обнаружения проблем с конкретной таблицей?
Напрямую - нет, так как данные группируются только по типу объекта и контексту, но не по имени отношения. Для детализации на уровне таблиц смотрите pg_statio_user_tables (попадания и чтения) или анализируйте pg_stat_statements, связывая запросы с I/O‑таймингами из pg_stat_statements и track_io_timing.
Верно ли, что высокие writes при низких fsync_time означают, что диск быстрый?
Необязательно. Возможно, большая часть записей уходит в кеш ОС и сбрасывается позже фоновыми процессами. А fsync‑вызовы привязаны к конкретным точкам синхронизации (например, фиксация транзакции). Низкое fsync_time при большом объёме writes говорит скорее о том, что синхронные сбросы редки или быстры, но не гарантирует общую высокую пропускную способность диска.
Как понять, что проблема в сети хранения, а не в самом PostgreSQL?
Когда read_time/write_time сильно варьируются при одинаковой рабочей нагрузке, а системный iostat показывает стабильно высокие значения await или svctm на соответствующих устройствах. Если при этом в PostgreSQL нет аномалий по планам запросов или блокировкам - подозрение падает на сеть хранения либо само хранилище.
Заключение: как встроить pg_stat_io в регулярный мониторинг
pg_stat_io не должен быть инструментом «на чёрный день», когда всё уже упало. Встройте его в повседневную практику: добавьте снятие дельт раз в минуту в свою систему мониторинга (Prometheus‑экспортер, Zabbix, pgwatch и т.п.), выведите на графики соотношения evictions/hits и средние задержки по типам бэкендов, настройте алерты на нехарактерный рост *_time при неизменном потоке операций. В комбинации с метриками ОС и pg_stat_statements вы получите чёткую картину того, где именно упирается ввод‑вывод, и сможете доказательно отделить дисковые проблемы от проблем самого приложения.



.svg.webp)





