Архитектура
Журналирование
12
Авторские права
© Postgres Professional, 2020 год.
Авторы: Егор Рогов, Павел Лузанов
Использование материалов курса
Некоммерческое использование материалов курса (презентации,
демонстрации) разрешается без ограничений. Коммерческое
использование возможно только с письменного разрешения компании
Postgres Professional. Запрещается внесение изменений в материалы
курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу:
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за
любые повреждения и убытки, включая потерю дохода, нанесенные
прямым или непрямым, специальным или случайным использованием
материалов курса. Компания Postgres Professional не предоставляет
каких-либо гарантий на материалы курса. Материалы курса
предоставляются на основе принципа «как есть» и компания Postgres
Professional не обязана предоставлять сопровождение, поддержку,
обновления, расширения и изменения.
2
Темы
Буферный кеш
Журнал предзаписи
3
Буферный кеш
Устройство и использование
Фоновая запись
Локальный кеш для временных таблиц
4
Буферный кеш
хеш-таблица
shared_buffers
кеш операционной системы
Задача буферного кеша — сглаживать разницу в производительности
двух типов памяти: оперативной (быстрая, но мало) и дисковой
(медленная, но много). Чтобы работать с данными — читать или
изменять, — процессы читают страницы в буферный кеш и, пока
страница находится в кеше, мы экономим на обращениях к диску.
Буферный кеш располагается в общей памяти сервера и представляет
собой массив буферов (его размер задается конфигурационным
параметром shared_buffers). Каждый буфер состоит из места под одну
страницу данных и заголовка. Заголовок содержит расположение
страницы на диске (файл и номер страницы) и другую информацию.
Чтобы нужную страницу можно было быстро найти в кеше,
используется хеш-таблица. Ключом хеширования в ней служит файл и
номер страницы внутри файла. Чтобы найти страницу, надо проверить
все буферы, указанные в соответствующей корзине хеш-таблицы: либо
страница найдется, либо мы убедимся в том, что ее нет в кеше.
В последнем случае страницу придется прочитать в буферный кеш
с диска. Однако напомним, что PostgreSQL читает и записывает данные
не в обход кеша операционной системы, а через него. Это, очевидно,
приводит к дублированию данных и двойному кешированию, но такова
текущая реализация. Поэтому «промах» мимо буферного кеша не
обязательно означает, что данные будут физически читаться с диска.
6
Страница в кеше
2
число
обращений
+1
хеш-таблица
буфер
закреплен
Рассмотрим случай, когда необходимая страница находится в кеше.
Буферный кеш и хеш-таблица находятся в общей памяти, и доступ
к ним есть у всех процессов сервера. Поэтому одновременный,
конкурентный доступ к этим структурам необходимо упорядочивать.
Вычислив хеш-код, процесс блокирует необходимый фрагмент хеш-
таблицы в разделяемом режиме (только для чтения) с помощью так
называемой «легкой блокировки», и уже затем находит буфер,
содержащий нужную страницу.
Сервер управляет блокировками данных в разделяемой памяти
полностью автоматически. Разработчики PostgreSQL прилагают много
усилий к тому, чтобы эти блокировки работали эффективно. Например,
чтобы не блокировать всю хеш-таблицу сразу, она разделена на
несколько (128) фрагментов, каждый из которых может работать
независимо от других. Тем не менее надо понимать, что, хотя доступ
к кешу быстрее, чем к диску, он все же не бесплатен.
Дальше процесс «закрепляет» буфер, увеличивая счетчик pin count
в заголовке страницы. Пока буфер закреплен (значение pin count
больше нуля), считается, что буфер используется и его содержимое не
должно «радикально» измениться. Например, в странице может
появиться новая версия строки — обычно это не мешает благодаря
многоверсионности и правилам видимости. Но в закрепленный буфер
не может быть прочитана другая страница.
Процесс также увеличивает счетчик обращений к странице (usage
count). Счетчик используется для того, чтобы понимать, какие страницы
используются часто, а какие нет.
7
Изменение в странице
хеш-таблица
2
грязный
буфер
Если процессу необходимо изменить содержимое страницы в буфере,
он должен получить монопольную блокировку для этой страницы
(чтобы не менять данные в тот момент, когда их читают другие
процессы).
Измененная страница, не записанная еще на диск, называется
«грязной». В заголовке буфера делается отметка об этом. (На рисунках
грязные буферы выделены цветом и штриховкой.)
8
Чтение с вытеснением
хеш-таблица
0
следующая
жертва
1 0
нельзя
вытеснить:
закреплен
еще рано:
используется
активно
можно
вытеснить
–1 –1
–1
Если нужной страницы не нашлось в кеше, ее необходимо прочитать
с диска, а для этого надо освободить какой-то из буферов, вытеснив
находящуюся там другую страницу. Идея состоит в том, чтобы
вытеснять страницы, к которым обращались редко, с тем, чтобы
активно используемые данные как можно дольше находились в кеше.
Механизм вытеснения использует указатель на следующую «жертву».
Этот указатель пробегает по кругу все буферы, уменьшая на единицу
их счетчики обращений (usage count). Выбирается первый же буфер,
который одновременно:
- имеет значение счетчика обращений, равное 0;
- не закреплен.
По-английски этот алгоритм называется clock-sweep.
Чем больше значение счетчика у буфера (то есть чем чаще он
используется), тем больше у него шансов задержаться в кеше. Чтобы
избежать «наматывания кругов» при вытеснении, максимальное
значение счетчика обращений ограничено числом 5,
В нашем примере процесс обращается к первому буферу по указателю.
Счетчик обращений этого буфера еще не равен 0, так что он
уменьшается на единицу и указатель сдвигается к следующему буферу.
Следующий буфер закреплен (то есть используется каким-то
процессом), и поэтому страница не может быть вытеснена из него.
Значение его счетчика также уменьшается на единицу.
Наконец, мы приходим к незакрепленному буферу с нулевым счетчиком
обращений. Страница из этого буфера и будет вытеснена.
9
Чтение с вытеснением
хеш-таблица
0
следующая
жертва
1 0
лучше,
когда запись
фоновая
Однако в нашем примере найденный буфер оказался грязным — он
содержит измененные данные. Поэтому сначала страницу требуется
сохранить на диск. Для этого буфер закрепляется и устанавливается
блокировка содержимого страницы, после чего страница записывается
на диск.
Значительно эффективнее, когда запись происходит асинхронно и
процесс обнаруживает вытесняемую страницу чистой. Чтобы это было
возможным, существует процесс фоновой записи (background writer).
Процесс фоновой записи использует тот же самый алгоритм поиска
буферов для вытеснения, что и обслуживающие процессы, только
использует свой указатель. Он может опережать указатель на «жертву»,
но никогда не отстает от него.
Записываются буферы, которые одновременно:
- содержат измененные (грязные) страницы,
- не закреплены (pin count = 0),
- имеют нулевое число обращений (usage count = 0).
Таким образом фоновый процесс записи находит те буферы, которые
с большой вероятностью вскоре потребуется вытеснить.
Теперь страница может быть выброшена, а из хеш-таблицы
необходимо удалить ссылку на эту страницу. Для этого потребуется
монопольно заблокировать нужный фрагмент хеш-таблицы.
10
Чтение с вытеснением
хеш-таблица
1
следующая
жертва
1 0
+1
После того как страница из грязного буфера записана на диск,
в освободившийся буфер читается новая страница, а в хеш-таблицу
добавляется ссылка на нее.
Счетчик обращений увеличивается на единицу. Ссылка на следующую
«жертву» уже указывает на следующий буфер, а у только что
загруженного есть время нарастить счетчик обращений, пока указатель
не обойдет по кругу весь буферный кеш и не вернется вновь.
12
Временные таблицы
Данные временных таблиц
видны только одному сеансу — нет смысла использовать общий кеш
существуют в пределах сеанса — не жалко потерять при сбое
Используется локальный буферный кеш
не требуются блокировки
память выделяется по необходимости в пределах temp_buffers
обычный алгоритм вытеснения
Исключение из общего правила представляют собой временные
таблицы. Поскольку временные данные видны только одному процессу,
им нечего делать в общем буферном кеше. Более того, временные
данные существуют только в рамках одного сеанса, так что их не нужно
защищать от сбоя.
Для временных данных используется облегченный локальный кеш.
Поскольку локальный кеш доступен только одному процессу, для него
не требуются блокировки. Память выделяется по мере необходимости
(в пределах, заданных параметром temp_buffers), ведь временные
таблицы используются далеко не во всех сеансах. В локальном кеше
используется обычный алгоритм вытеснения.
13
Журнал предзаписи
Устройство журнала
Контрольная точка
Восстановление после сбоя
Синхронный и асинхронный режимы
14
Журнал предзаписи
Необходимость
данные меняются в оперативной памяти (в частности, в буферном кеше)
и попадают на диск асинхронно
при сбое остается только информация, записанная на диск
Механизм
действия над данными записываются в журнал:
изменение страниц в буферном кеше, фиксация и отмена транзакций
журнальная запись попадает на диск раньше измененных данных
после сбоя операции, записанные в журнал, но не попавшие на диск,
можно выполнить повторно
Основная причина существования журнала — необходимость
восстановления согласованности данных в случае сбоя, при котором
теряется содержимое оперативной памяти, в частности, буферный кеш.
Тем самым обеспечивается выполнение свойства долговечности (буква
«D» из набора свойств транзакций ACID).
Одновременно с изменением данных создается запись в журнале,
содержащая достаточную информацию для повторения этой операции.
Журнальная запись в обязательном порядке попадает на диск (или
другое энергонезависимое устройство) до того, как туда попадет
измененная страница — отсюда и название: «журнал предзаписи»,
«write-ahead log».
Журналировать нужно все операции, при выполнении которых
возможна ситуация, что при сбое результат операции не дойдет
до диска. В частности, в журнал записываются изменение страниц
в буферном кеше и факт фиксации и отмены транзакций.
В журнал не записываются операции с нежурналируемыми
(и временными) таблицами.
В случае сбоя можно прочитать журнал и при необходимости повторить
те операции, которые уже были выполнены, но результат которых не
успел попасть на диск.
15
Устройство журнала
LSN
номер
записи
0
заголовок:
номер транзакции,
менеджер ресурсов,
контрольная
сумма
данные
последовательность записей
номер записи — 64-битный LSN (log sequence number)
специальный тип pg_lsn
Логически журнал можно представить себе как последовательность
записей различной длины. Каждая запись содержит данные
о некоторой операции, предваренные заголовком. В заголовке, в числе
прочего, указаны:
- Номер транзакции, к которой относится запись.
- Менеджер ресурсов — компонент системы, ответственный за данную
запись, который понимает, как интерпретировать данные. Есть
отдельные менеджеры для таблиц, для каждого типа индекса, для
статуса транзакций и т. п.
- Контрольная сумма (CRC).
Для того, чтобы сослаться на определенную запись, используется тип
данных pg_lsn (LSN = log sequence number) — 64-битное число,
представляющее собой байтовое смещение до записи относительно
начала журнала.
На диске журнал хранится в виде файлов фиксированного размера
(обычно 16 Мбайт). Когда заканчивается место в текущем файле,
система переключается на следующий.
В оперативной памяти работа с журналом ведется в специальных
буферах, которые организованы наподобие буферного кеша.