Принцип работы менеджера буфера PostgreSQL
В этом разделе описывается работа менеджера буферов. Когда backend процесс хочет получить доступ к нужной странице, он вызывает функцию ReadBufferExtended().
Поведение функции ReadBufferExtended() различается в зависимости от трех случаев, описание которых дано в последующих разделах. В последнем подразделе описан алгоритм замещения страниц clock sweep.
Доступ к странице, хранящейся в буферном пуле
Сначала описывается случай, когда нужная страница уже хранится в буферном пуле. В этом случае менеджер буферов выполняет следующие действия:
- Создает тег буфера нужной страницы ( в данно случае этот тег - 'Tag_C') и вычисляет слот хэш - бакета, содержащий запись созданного тега, используя хэш-функцию.
- Получает блокировку BufMappingLock, покрывающую полученный слот хэш-бакета в режиме общего доступа (эта блокировка будет снята на шаге (5)).
- Находит запись, чей тег - 'Tag_C', и получает из нее идентификатор буфера. В этом примере идентификатор буфера равен 2.
- Закрепляет дескриптор буфера для buffer_id 2, увеличив refcount и usage_count на 1.
- Снимает блокировку BufMappingLock.
- Получает доступ к слоту буферного пула с идентификатором buffer_id 2.
Затем в процессе чтения строк со страницы в слоте буферного пула процесс PostgreSQL получает общую блокировку content_lock соответствующего дескриптора буфера. Таким образом, слоты буферного пула могут читаться несколькими процессами одновременно.
При добавлении (а также обновлении или удалении) строк в страницу процесс Postgres получает эксклюзивную блокировку content_lock соответствующего дескриптора буфера. (Обратите внимание, что бит грязной страницы должен быть установлен на 1).
После получения доступа к нужным страницам значения refcount соответствующих дескрипторов буфера уменьшаются на 1.
Загрузка страницы из хранилища в пустой слот
Во втором случае предположим, что нужной страницы в буферном пуле нет, а во freelist есть свободные элементы (пустые дескрипторы). В этом случае менеджер буферов выполняет следующие действия:
(1) Ишет буферную таблицу (предположим, что она не найдена).
- Создает buffer_tag для нужной страницы (в нашем случае это ‘Tag_E’)
- Получает блокировку BufMappingLock.
- Ищет буферную таблицу. (однако согласно нашим вводным данным она не найдена)
- Снимает блокировку BufMappingLock.
(2) Получает пустой дескриптор буфера из freelist и фиксирует его. В нашем случае buffer_id полученного дескриптора равен 4.
(3) Получает блокировку BufMappingLock. (она будет снята на шаге (6).)
(4) Создает новую запись данных, содержащую 'Tag_E' и идентификатор буфера 4. Вставляет созданную запись в таблицу буферов.
(5) Загрузите нужные данные страницы из хранилища в слот буферного пула с идентификатором buffer_id 4 следующим образом:
- В версии 9.5 и более ранних версиях получаете эксклюзивную блокировку io_in_progress_lock соответствующего дескриптора.
- Установливает бит io_in_progress соответствующего дескриптора на 1 для того, чтобы ограничить доступ других процессов.
- Загружает нужные данные страницы из хранилища в слот буферного пула.
- Изменяетсостояния соответствующего дескриптора: бит io_in_progress устанавливается на 0, а бит valid - на 1.
- В версии 9.5 и более ранних версиях снимает блокировку io_in_progress_lock.
(6) Снимает блокировку BufMappingLock.
(7) Получает доступ к слоту буферного пула с buffer_id 4.
Загрузка страницы из хранилища в слот буферного пула - жертву
Предположим, что все слоты буферного пула заняты страницами, но нужная страница не хранится. Менеджер буферов выполняет следующие действия:
(1) Создает buffer_tag нужной страницы и просматривает таблицу буферов. В данном случае тег - 'Tag_M' (при этом нужная страница не найдена).
(2) Выберает слот буферного пула - жертву, используя алгоритм clock sweep. Получает старую запись, содержащую идентификатор буфера слота пула - жертвы, из таблицы буферов и закрепляет слот пула - жертвы в слое дескрипторов буферов. Данном случае идентификатор буфера слота - жертвы равен 5, а старая запись имеет вид 'Tag_F, id=5'.
(3) Очищает данные страницы-жертвы, если они грязные; в противном случае переходит к шагу (4).
- Грязная страница должна быть записана в память перед перезаписью новыми данными. Очистка грязной страницы выполняется следующим образом:
- Получает общую блокировку content_lock и эксклюзивную блокировку io_in_progress дескриптора с идентификатором buffer_id 5 (блокировка будет снята на шаге 6)..
- Изменяет состояние соответствующего дескриптора; бит io_in_progress устанавливается на 1, а бит just_dirtied - на 0.
- В зависимости от ситуации вызывается функция XLogFlush() для записи данных WAL в буфере WAL в текущий файл сегмента WAL (более подробная информация содержится в разделе 9)
- Перемещает данные страницы - жертвы в хранилище.
- Изменяет состояние соответствующего дескриптора; бит io_in_progress устанавливается на 0, а бит valid - на 1.
- Снимает блокировки io_in_progress и content_lock.
(4) Получает блокировку BufMappingLock, покрывающую слот, которые содержит старые записи данных.
(5) Получает еще одну блокировку BufMappingLock и вставляет новую запись в буферную таблицу:
- Создает новую запись, состоящую из нового буферного тега 'Tag_M' и идентификатора буфера - жертвы.
- Получает новую блокировку BufMappingLock, покрывающую слот, содержащий новую запись
- Вставляет новую запись в буферную таблицу.
(6) Удаляет старую запись из буферной таблицы и снимает предыдущую блокировку BufMappingLock..
(7) Загружает данные нужной страницы из хранилища в слот буфера - жертвы. Затем обновляет флаги дескриптора с идентификатором buffer_id 5; бит dirty устанавливает на 0и инициализирует оставшиеся биты.
(8) Снимает новую блокировку BufMappingLock.
(9) Получает доступ к слоту буферного пула с buffer_id = 5.
Алгоритм замещения страниц Clock Sweep
Алгоритм Clock Sweep – это вариант алгоритма выталкивания редко используемой страницы (NFU - Not Frequently Used), который выбирает редко используемые страницы более эффективно.
Представим дескрипторы буферов в виде кругового списка (рис. 96). NextVictimBuffer, беззнаковое 32-битное целое число, всегда указывает на один из дескрипторов буфера и вращается по часовой стрелке. Псевдокод и описание алгоритма приведены ниже:
Псевдокод clock-sweep
WHILE true
(1) Obtain the candidate buffer descriptor pointed by the nextVictimBuffer
(2) IF the candidate descriptor is 'unpinned' THEN
(3) IF the candidate descriptor's usage_count == 0 THEN
BREAK WHILE LOOP /* the corresponding slot of this descriptor */
/* is victim slot. */
ELSE
Decrease the candidate descriptpor's usage_count by 1
END IF
END IF
(4) Advance nextVictimBuffer to the next one
END WHILE
(5) RETURN buffer_id of the victim
(1) Определяет кондидата дескриптора буфера, на который указывает nextVictimBuffer.
(2) Если кандидат дескриптора буфера не закреплен, переходит к шагу (3). В противном случае переходит к шагу (4).
(3) Если usage_count кандидата дескриптора буфера равен 0, выберает соответствующий слот этого дескриптора в качестве жертвы и переходит к шагу (5). В противном случае уменьшает usage_count данного дескриптора на 1 и переходит к шагу (4)
(4) Перемещает буфер nextVictimBuffer к следующему дескриптору и возвращается к шагу (1). Процесс повторяется до тех пор, пока не будет найдена жертва.
(5) Возвращает buffer_id жертвы.
(1) nextVictimBuffer указывает на первый дескриптор (buffer_id 1). Однако этот дескриптор пропускается, потому что он заблокирован.
(2) nextVictimBuffer указывает на второй дескриптор (buffer_id 2). Этот дескриптор не закреплен, но его usage_count равен 2. Таким образом, usage_count уменьшается на 1, и nextVictimBuffer переходит к третьему кандидату.
(3) nextVictimBuffer указывает на третий дескриптор (buffer_id 3). Этот дескриптор не закреплен, счетчик usage_count равен 0. Таким образом, в этом раунде именно этот дескриптор является жертвой.
Каждый раз, когда nextVictimBuffer проверяет незакрепленный дескриптор, его usage_count уменьшается на 1. Поэтому, если в буферном пуле есть несколько незакрепленных дескрипторов, этот алгоритм всегда найдет жертву, usage-count которой равен 0.








