- PVSM.RU - https://www.pvsm.ru -

Запись при чтении в postgresql: скандалы, интриги, расследования

Запись при чтении в postgresql: скандалы, интриги, расследования - 1 Я уже рассказывал про мониторинг запросов postgresql [1], в тот момент мне казалось, что я полностью разобрался, как postgresql работает с различными ресурсами сервера.

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

Под катом небольшой рассказ о неочевидном поведении postgresql.

SELECTы "пачкают" страницы

То есть SELECT вызывает модификацию каких-то записей, которые постгрес будет записывать на диск.

Запись при чтении в postgresql: скандалы, интриги, расследования - 2

Начну с краткого пояснения механизма MVCC, используемого постгресом для обеспечения транзакционной целостности.
Все изменения в базе данных происходят в ходе транзакций, у каждой транзакции есть идентификационный номер txid (int32).
Постгрес оперирует данными таблиц в виде так называемых tuple (кортеж). Тупл несет в себе как непосредственно данные конкретной строчки в таблице, так и метаданные связанные с этими данными:

Запись при чтении в postgresql: скандалы, интриги, расследования - 3

Картинка: www.interdb.jp

xmin — номер транзакции, которая создала этот tuple
xmax — номер транзакции, которая пометила этот tuple как удаленный

  • Если мы делаем INSERT в таблицу, он создает новый tuple (xmin=txid)
  • DELETE — помечает туплы, которые подходят под условие как удаленные (xmax=txid)
  • UPDATE делает условно DELETE + INSERT.

Когда мы выполняем SELECT, он помимо непосредственно поиска и выборки данных из таблицы делает еще и проверку видимости (visibility check).
Очень упрощенно, некоторая транзакця с номером txid1 "видит" данный тупл, если выполняется условия:

xmin < txid1 < xmax

Но изменения в кортежах происходят сразу, а транзакция может выполняться еще продолжительное время, поэтому в ходе проверки видимости необходимо удостовериться, завершились ли транзакции с номерами xmin, xmax и если да, то с каким статусом. Информацию о текущем
состоянии каждой транзакции постгрес хранит в CLOG (commit log).

Так как проверять состояние большого количества транзакций в CLOG достаточно дорого по ресурсам, разработчики решили "закэшировать" эту информацию прямо в заголовке тупла. То есть когда какой-то SELECT видит к примеру, что xmin завершилась, он сохраняет это в так называемый hint bits [2] — структуру поверх infomask, в которой записаны состояния транзакций xmin и xmax.

Как происходит изменение туплов при чтении, мы разобрались, осталось вспомнить, что такое "страницы" и почему они бывают "грязными":)

Дело в том, что работать с данными в памяти и на диске практически всегда эффективнее большими блоками. Таким блоком в постгресе является "страница", она содержит в себе какое-то количество туплов и метаинформация о них. Когда мы модифицируем хотя бы один тупл страницы, вся она помечается как "грязная", то есть отличающаяся по состоянию на диске, и должна быть синхронизирована. При этом почти всегда изменения записываются еще и в WAL, чтобы иметь возможность восстановить целостность данных после аварийного завершении процесса БД.

SELECT может вызывать синхронную запись на диск

Как известно, вся работа с данными в pg ведется через buffer cache, если нужных данных там нет, постгрес прочитает их с диска (c использованием OS page cache) и поместит в кэш.
При этом, если в кэше нет места, то из него вытесняется наименее востребованная страница. И наконец, если страница-кандидат на вытеснение оказывается "грязной", она должна быть записана на диск в тот же момент времени.

FrozenTransactionId

В начале статьи я упомянул, что счетчик транзакций в постгресе 32-битный, то есть он сбрасывается через каждые ~2 млрд тразакций.
Чтобы проверка видимости не превращалась в тыкву при сбросе счетчика транзакций, есть специальный процесс — wraparound vacuum.

Запись при чтении в postgresql: скандалы, интриги, расследования - 4

До версии 9.4 этот процесс заменял xmin у тупла на специальное значение FrozenTransactionId=2. Транзакция с этим номером считалась старше любой другой транзакции. C 9.4 в тупл просто проставляется флаг, что xmin "заморожен", а сам xmin остается неизменным.

Для совсем внимательных: есть специальная константа BootstrapTransactionId=1, которая тоже старше всех других транзакций :)

Итого

Большинство случаев "странного" (по обывательскому мнению) поведения посгтреса вызвано оптимизацией производительности.
Пока ковырялся с постгресом нашел замечательную книгу "The Internals of PostgreSQL" [3], рекомендую всем, кто не встречал ранее.

Автор: okmeter.io

Источник [4]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/250592

Ссылки в тексте:

[1] мониторинг запросов postgresql: https://habrahabr.ru/company/okmeter/blog/311028/

[2] hint bits: https://wiki.postgresql.org/wiki/Hint_Bits

[3] "The Internals of PostgreSQL": http://www.interdb.jp/pg/

[4] Источник: https://habrahabr.ru/post/324494/