Я уже рассказывал про мониторинг запросов postgresql, в тот момент мне казалось, что я полностью разобрался, как postgresql работает с различными ресурсами сервера.
При постоянной работе со статистикой по запросам постгреса мы начали замечать некоторые аномалии. Я полез разбираться, заодно очередной раз восхитился понятностью исходного кода постгреса:)
Под катом небольшой рассказ о неочевидном поведении postgresql.
SELECTы "пачкают" страницы
То есть SELECT вызывает модификацию каких-то записей, которые постгрес будет записывать на диск.
Начну с краткого пояснения механизма MVCC, используемого постгресом для обеспечения транзакционной целостности.
Все изменения в базе данных происходят в ходе транзакций, у каждой транзакции есть идентификационный номер txid (int32).
Постгрес оперирует данными таблиц в виде так называемых tuple (кортеж). Тупл несет в себе как непосредственно данные конкретной строчки в таблице, так и метаданные связанные с этими данными:
Картинка: 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 — структуру поверх infomask, в которой записаны состояния транзакций xmin и xmax.
Как происходит изменение туплов при чтении, мы разобрались, осталось вспомнить, что такое "страницы" и почему они бывают "грязными":)
Дело в том, что работать с данными в памяти и на диске практически всегда эффективнее большими блоками. Таким блоком в постгресе является "страница", она содержит в себе какое-то количество туплов и метаинформация о них. Когда мы модифицируем хотя бы один тупл страницы, вся она помечается как "грязная", то есть отличающаяся по состоянию на диске, и должна быть синхронизирована. При этом почти всегда изменения записываются еще и в WAL, чтобы иметь возможность восстановить целостность данных после аварийного завершении процесса БД.
SELECT может вызывать синхронную запись на диск
Как известно, вся работа с данными в pg ведется через buffer cache, если нужных данных там нет, постгрес прочитает их с диска (c использованием OS page cache) и поместит в кэш.
При этом, если в кэше нет места, то из него вытесняется наименее востребованная страница. И наконец, если страница-кандидат на вытеснение оказывается "грязной", она должна быть записана на диск в тот же момент времени.
FrozenTransactionId
В начале статьи я упомянул, что счетчик транзакций в постгресе 32-битный, то есть он сбрасывается через каждые ~2 млрд тразакций.
Чтобы проверка видимости не превращалась в тыкву при сбросе счетчика транзакций, есть специальный процесс — wraparound vacuum.
До версии 9.4 этот процесс заменял xmin у тупла на специальное значение FrozenTransactionId=2. Транзакция с этим номером считалась старше любой другой транзакции. C 9.4 в тупл просто проставляется флаг, что xmin "заморожен", а сам xmin остается неизменным.
Для совсем внимательных: есть специальная константа BootstrapTransactionId=1, которая тоже старше всех других транзакций :)
Итого
Большинство случаев "странного" (по обывательскому мнению) поведения посгтреса вызвано оптимизацией производительности.
Пока ковырялся с постгресом нашел замечательную книгу "The Internals of PostgreSQL", рекомендую всем, кто не встречал ранее.
Автор: okmeter.io