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

    Я уже рассказывал про мониторинг запросов 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", рекомендую всем, кто не встречал ранее.

    • +36
    • 15,2k
    • 4
    okmeter.io
    78,00
    Осмысленный мониторинг серверов и сайтов
    Поделиться публикацией

    Похожие публикации

    Комментарии 4

      –3
      Может я не совсем обыватель, но то, что написано в Итого по-моему написал КЭП.
        0
        MySql ведёт себя похожим образом или там это работает сильно по другому?
          +1

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

          0

          Смотрите лекции, например, от PostgresPro и будет вам счастье. Там всё это и много чего ещё про PG рассказано.

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

          Самое читаемое