Инструменты пользователя

Инструменты сайта


postgres:osnovy:buffer_manager

8.1. Обзор

В этом разделе представлены ключевые концепции, необходимые для облегчения описания в последующих разделах.

8.1.1. Структура диспетчера буфера

Диспетчер буферов PostgreSQL состоит из таблицы буферов, дескрипторов буферов и пула буферов, которые описаны в следующем разделе. В пулах буферов страниц файлы слой хранит данные, такие как таблицы и индексы, а также карты FREESPACE и карта видимости . Пул буферов представляет собой массив, т. Е. Каждый слот хранит одну страницу файла данных. Индексы массива буферного пула упоминаются как buffer_id s.

Разделы 8.2 и 8.3 подробно описывают внутреннее устройство диспетчера буферов.

8.1.2. Тег буфера

В PostgreSQL каждой странице всех файлов данных может быть присвоен уникальный тег, то есть тег буфера . Когда диспетчер буферов получает запрос, PostgreSQL использует buffer_tag желаемой страницы.

Buffer_tag состоит из трех значений: при регистрации RelFileNode и число развилок отношению к которой принадлежит его страница, а номер блока его страницы. Номера вилок таблиц, карт свободного пространства и карт видимости определены в 0, 1 и 2 соответственно.

Например, buffer_tag '{(16821, 16384, 37721), 0, 7}' идентифицирует страницу, которая находится в седьмом блоке, OID отношения и номер ответвления которого равны 37721 и 0 соответственно; отношение содержится в базе данных, OID которой равен 16384, в табличном пространстве, OID которого равен 16821. Точно так же buffer_tag '{(16821, 16384, 37721), 1, 3}' идентифицирует страницу, которая находится в третьем блоке свободного пространства карта с OID и номером форка 37721 и 1 соответственно.

/*
 * Buffer tag identifies which disk block the buffer contains.
 *
 * Note: the BufferTag data must be sufficient to determine where to write the
 * block, without reference to pg_class or pg_tablespace entries.  It's
 * possible that the backend flushing the buffer doesn't even believe the
 * relation is visible yet (its xact may have started before the xact that
 * created the rel).  The storage manager must be able to cope anyway.
 *
 * Note: if there's any pad bytes in the struct, INIT_BUFFERTAG will have
 * to be fixed to zero them, since this struct is used as a hash key.
 */
typedef struct buftag
{
        RelFileNode rnode;                  /* physical relation identifier */
        ForkNumber    forkNum;
        BlockNumber blockNum;               /* blknum relative to begin of reln */
} BufferTag;
typedef struct RelFileNode
{
    Oid         spcNode;        /* tablespace */
    Oid         dbNode;         /* database */
    Oid         relNode;        /* relation */
} RelFileNode;

8.1.3. Как серверный процесс читает страницы

В этом подразделе описывается, как серверный процесс считывает страницу из диспетчера буферов (рис. 8.2).

Рис. 8.2. Как серверная часть читает страницу из диспетчера буферов.

  • (1) При чтении таблицы или страницы индекса серверный процесс отправляет запрос, который включает в себя buffer_tag страницы, диспетчеру буферов.
  • (2) Диспетчер буферов возвращает buffer_ID слота, в котором хранится запрошенная страница. Если запрошенная страница не хранится в пуле буферов, диспетчер буферов загружает страницу из постоянного хранилища в один из слотов пула буферов, а затем возвращает слот buffer_ID.
  • (3) Внутренний процесс обращается к слоту buffer_ID (чтобы прочитать нужную страницу).

Когда внутренний процесс изменяет страницу в пуле буферов (например, вставляя кортежи), измененная страница, которая еще не была сброшена в хранилище, называется грязной страницей .

Раздел 8.4 описывает, как работает диспетчер буферов.

8.1.4. Алгоритм замены страницы

Когда все слоты пула буферов заняты, но запрошенная страница не сохранена, диспетчер буферов должен выбрать одну страницу в пуле буферов, которая будет заменена запрошенной страницей. Обычно в области информатики алгоритмы выбора страниц называются алгоритмами замены страниц, а выбранная страница называется страницей-жертвой .

Исследования алгоритмов замены страниц продолжаются с момента появления информатики; таким образом, ранее было предложено множество алгоритмов замены. Начиная с версии 8.1, PostgreSQL использует тактовую развертку, потому что это проще и эффективнее, чем алгоритм LRU, использовавшийся в предыдущих версиях.

Раздел 8.4.4 описывает детали развертки тактовой частоты.

8.1.5. Очистка грязных страниц

Грязные страницы в конечном итоге следует сбрасывать в хранилище; однако диспетчеру буферов требуется помощь для выполнения этой задачи. В PostgreSQL за эту задачу отвечают два фоновых процесса: контрольная точка и фоновый писатель .

Раздел 8.6 описывает checkpointer и background writer.


Прямой ввод / вывод

PostgreSQL не поддерживает прямой ввод-вывод, хотя иногда это обсуждается. Если вы хотите узнать больше, обратитесь к этому обсуждению pgsql-ML и этой статье .


8.2. Структура диспетчера буфера

Менеджер PostgreSQL буфер состоит из трех слоев, т.е. таблицы буфера , буферные дескрипторы и пул буферов (рис 8.3.):

Рис. 8.3. Трехуровневая структура диспетчера буферов.

 

  • Пул буферов является массивом. Каждый слот хранит страницы файла данных. Индексы слотов массива упоминаются как buffer_id s.
  • Буферные дескрипторы слой представляет собой массив буферных дескрипторов. Каждый дескриптор имеет однозначное соответствие слоту пула буферов и содержит метаданные сохраненной страницы в соответствующем слоте.
    Обратите внимание, что термин «уровень дескрипторов буфера» был принят для удобства и используется только в этом документе.
  • Таблица буферов - это хэш-таблица, в которой хранятся отношения между buffer_tag сохраненных страниц и buffer_id дескрипторов, которые содержат соответствующие метаданные сохраненных страниц.

Эти слои подробно описаны в следующих подразделах.

8.2.1. Таблица буферов

Буферную таблицу можно логически разделить на три части: хэш-функция, слоты хеш-ведра и записи данных (рис. 8.4).

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

Рис. 8.4. Буферная таблица.

Запись данных содержит два значения: buffer_tag страницы и buffer_id дескриптора, который содержит метаданные страницы. Например, запись данных « Tag_A, id = 1 » означает, что дескриптор буфера с buffer_id 1 хранит метаданные страницы, помеченной Tag_A .

Хеш-функция - это составная функция calc_bucket () и hash () . Ниже приводится его представление в виде псевдофункции.
uint32 bucket_slot = calc_bucket ( беззнаковое хэш ( BufferTag buffer_tag ), uint32 bucket_size )


Обратите внимание, что основные операции (поиск, вставка и удаление записей данных) здесь не объясняются. Это очень распространенные операции, которые описаны в следующих разделах.

8.2.2. Дескриптор буфера

Структура дескриптора буфера описана в этом подразделе, а уровень дескрипторов буфера - в следующем.

Дескриптор буфера содержит метаданные сохраненной страницы в соответствующем слоте пула буферов. Структура дескриптора буфера определяется структурой BufferDesc . Хотя в этой структуре много полей, в основном они показаны ниже:

/*
 * Flags for buffer descriptors
 *
 * Note: TAG_VALID essentially means that there is a buffer hashtable
 * entry associated with the buffer's tag.
 */
#define BM_DIRTY                (1 << 0)    /* data needs writing */
#define BM_VALID                (1 << 1)    /* data is valid */
#define BM_TAG_VALID            (1 << 2)    /* tag is assigned */
#define BM_IO_IN_PROGRESS       (1 << 3)    /* read or write in progress */
#define BM_IO_ERROR             (1 << 4)    /* previous I/O failed */
#define BM_JUST_DIRTIED         (1 << 5)    /* dirtied since write started */
#define BM_PIN_COUNT_WAITER     (1 << 6)    /* have waiter for sole pin */
#define BM_CHECKPOINT_NEEDED    (1 << 7)    /* must write for checkpoint */
#define BM_PERMANENT            (1 << 8)    /* permanent relation (not unlogged) */
 
src/include/storage/buf_internals.h
typedef struct sbufdesc
{
   BufferTag    tag;                 /* ID of page contained in buffer */
   BufFlags     flags;               /* see bit definitions above */
   uint16       usage_count;         /* usage counter for clock sweep code */
   unsigned     refcount;            /* # of backends holding pins on buffer */
   int          wait_backend_pid;    /* backend PID of pin-count waiter */
   slock_t      buf_hdr_lock;        /* protects the above fields */
   int          buf_id;              /* buffer's index number (from 0) */
   int          freeNext;            /* link in freelist chain */
 
   LWLockId     io_in_progress_lock; /* to wait for I/O to complete */
   LWLockId     content_lock;        /* to lock access to buffer contents */
} BufferDesc;
  • тег содержит buffer_tag сохраненной страницы в соответствующем слоте пула буферов (тег буфера определен в Разделе 8.1.2 ).
  • buffer_id идентифицирует дескриптор (эквивалент buffer_id соответствующего слота буферного пула).
  • refcount содержит количество процессов PostgreSQL, которые в настоящее время обращаются к связанной сохраненной странице. Это также называется подсчетом выводов . Когда процесс PostgreSQL обращается к сохраненной странице, его refcount должен быть увеличен на 1 (refcount ++). После доступа к странице ее refcount необходимо уменьшить на 1 (refcount–).
    Когда счетчик ссылок равен нулю, т. Е. К связанной сохраненной странице в настоящее время не осуществляется доступ, страница открепляется ; в противном случае он закреплен .
  • usage_count хранится количество обращений к связанной сохраненной странице с момента ее загрузки в соответствующий слот пула буферов. Обратите внимание, что usage_count используется в алгоритме замены страницы ( раздел 8.4.4 ).
  • context_lock и io_in_progress_lock являются легкими замками, которые используются для управления доступа к соответствующей сохраненной странице. Эти поля описаны в Разделе 8.3.2 .
  • флаги могут содержать несколько состояний связанной сохраненной страницы. Основные состояния следующие:
    • грязный бит указывает, грязная ли сохраненная страница.
    • Действительный бит указывает, может ли сохраненная страница быть прочитана или записана (действительна). Например, если этот бит действителен , то соответствующий слот пула буферов хранит страницу, а этот дескриптор (действительный бит) содержит метаданные страницы; таким образом, сохраненная страница может быть прочитана или записана. Если этот бит недействителен , то этот дескриптор не содержит никаких метаданных; это означает, что сохраненная страница не может быть прочитана или записана или диспетчер буферов заменяет сохраненную страницу.
    • Бит io_in_progress указывает, читает / записывает ли диспетчер буферов связанную страницу из / в хранилище. Другими словами, этот бит указывает, удерживает ли отдельный процесс блок io_in_progress_lock этого дескриптора.
  • freeNext является указателем на следующий дескриптор для генерации FreeList , который описан в следующем разделе.

Структура BufferDesc определена в src / include / storage / buf_internals.h .


Для упрощения следующих описаний определены три состояния дескриптора:

  • Пусто : когда соответствующий слот пула буферов не хранит страницу (т.е. refcount и usage_count равны 0), состояние этого дескриптора пусто .
  • Закреплено : когда в соответствующем слоте пула буферов хранится страница и какие-либо процессы PostgreSQL обращаются к странице (т. Е. Refcount и usage_count больше или равны 1), состояние этого дескриптора буфера фиксируется .
  • Незакреплено : когда в соответствующем слоте пула буферов хранится страница, но ни один из процессов PostgreSQL не обращается к странице (т. Е. Usage_count больше или равно 1, но refcount равно 0), состояние этого дескриптора буфера откреплено .

Каждый дескриптор будет иметь одно из перечисленных выше состояний. Состояние дескриптора изменяется в зависимости от конкретных условий, которые описаны в следующем подразделе.

На следующих рисунках состояния дескрипторов буфера представлены цветными прямоугольниками.

  •     (белый) Пусто
  •     (синий) Закреплено
  •     (голубой) Незакреплен

Кроме того, грязная страница обозначается буквой «X». Например, откреплен грязный дескриптор представлен  X  .

8.2.3. Слой дескрипторов буфера

Коллекция дескрипторов буфера образует массив. В этом документе массив называется слоем дескрипторов буфера .

Когда сервер PostgreSQL запускается, состояние всех дескрипторов буфера пустое . В PostgreSQL эти дескрипторы составляют связанный список, называемый freelist (рис. 8.5).


Обратите внимание, что фрилисты в PostgreSQL полностью отличаются от фрилистов в Oracle. Свободный список PostgreSQL - это только связанный список дескрипторов пустых буферов. В PostgreSQL карты свободного пространства , описанные в Разделе 5.3.4 , выполняют ту же роль, что и свободные списки в Oracle.


Рис. 8.5. Начальное состояние диспетчера буферов.

На рис. 8.6 показано, как загружается первая страница.

  • (1) Извлеките пустой дескриптор из верхней части списка фрилансеров и закрепите его (т. Е. Увеличьте его refcount и usage_count на 1).
  • (2) Вставьте новую запись, которая содержит отношение между тегом первой страницы и buffer_id полученного дескриптора, в таблицу буферов.
  • (3) Загрузите новую страницу из хранилища в соответствующий слот пула буферов.
  • (4) Сохраните метаданные новой страницы в полученном дескрипторе.

Аналогичным образом загружаются вторая и последующие страницы. Дополнительная информация представлена ​​в разделе 8.4.2 .

Рис. 8.6. Загрузка первой страницы.

Дескрипторы, полученные из списка фрилансеров, всегда содержат метаданные страницы. Другими словами, непустые дескрипторы продолжают использоваться и не возвращаются в список фрилансеров. Однако связанные дескрипторы снова добавляются в список фрилансеров, и состояние дескриптора становится «пустым», когда происходит одно из следующих событий:

  1. Таблицы или индексы были отброшены.
  2. Базы данных были отброшены.
  3. Таблицы или индексы были очищены с помощью команды VACUUM FULL.

Почему пустые дескрипторы составляют список фрилансеров?

Причина создания списка фрилансеров - немедленное получение первого дескриптора. Это обычная практика для распределения ресурсов динамической памяти. См. Это описание .


Слой дескрипторов буфера содержит беззнаковую 32-битную целочисленную переменную, то есть nextVictimBuffer . Эта переменная используется в алгоритме замены страниц, описанном в Разделе 8.4.4 .

8.2.4. Буферный пул

Пул буферов - это простой массив, в котором хранятся страницы файлов данных, такие как таблицы и индексы. Индексы массива буферного пула упоминаются как buffer_id s.

Размер слота пула буферов составляет 8 КБ, что равно размеру страницы. Таким образом, каждый слот может хранить целую страницу.

8.3. Блокировки диспетчера буфера

Диспетчер буферов использует множество блокировок для разных целей. В этом разделе описаны блокировки, необходимые для пояснений в следующих разделах.


Обратите внимание, что блокировки, описанные в этом разделе, являются частью механизма синхронизации для диспетчера буферов; они не связаны ни с какими операторами SQL и опциями SQL.


8.3.1. Блокировки буферной таблицы

BufMappingLock защищает целостность данных всей буферной таблицы. Это легкий замок, который можно использовать как в общем, так и в эксклюзивном режимах. При поиске записи в буферной таблице внутренний процесс содержит общий BufMappingLock. При вставке или удалении записей серверный процесс удерживает исключительную блокировку.

BufMappingLock разделен на разделы, чтобы уменьшить количество конфликтов в буферной таблице (по умолчанию 128 разделов). Каждый раздел BufMappingLock охраняет часть соответствующих слотов хэш-корзины.

На рисунке 8.7 показан типичный пример эффекта разделения BufMappingLock. Два внутренних процесса могут одновременно удерживать соответствующие разделы BufMappingLock в монопольном режиме для вставки новых записей данных. Если BufMappingLock - это единственная общесистемная блокировка, оба процесса должны дождаться обработки другого процесса, в зависимости от того, который начал обработку.

Рис. 8.7. Два процесса одновременно получают соответствующие разделы BufMappingLock в монопольном режиме для вставки новых записей данных.

Буферная таблица требует многих других блокировок. Например, буферная таблица внутренне использует спин-блокировку для удаления записи. Однако описания этих других блокировок опущены, потому что они не требуются в этом документе.


BufMappingLock по умолчанию был разделен на 16 отдельных блокировок до версии 9.4.


8.3.2. Блокировки для каждого дескриптора буфера

Каждый дескриптор буфера использует две облегченные блокировки, content_lock и io_in_progress_lock , для управления доступом к сохраненной странице в соответствующем слоте пула буферов. Когда значения собственных полей проверяются или изменяются, используется спин-блокировка.

8.3.2.1. content_lock

Content_lock - это типичная блокировка, которая устанавливает ограничения доступа. Его можно использовать в совместном и эксклюзивном режимах.

При чтении страницы серверный процесс получает общий content_lock дескриптора буфера, в котором хранится страница.

Однако эксклюзивный content_lock приобретается при выполнении одного из следующих действий:

  • Вставка строк (т. Е. Кортежей) на сохраненную страницу или изменение полей t_xmin / t_xmax кортежей на сохраненной странице (t_xmin и t_xmax описаны в Разделе 5.2 ; просто, при удалении или обновлении строк эти поля связанных кортежей изменяются) .
  • Физическое удаление кортежей или уплотнение свободного места на сохраненной странице (выполняется с помощью вакуумной обработки и HOT, которые описаны в главах 6 и 7 соответственно).
  • Замораживание кортежей на сохраненной странице (замораживание описано в Разделе 5.10.1 и Разделе 6.3 ).

Официальный файл README показывает более подробную информацию.

8.3.2.2. io_in_progress_lock

Блокировка io_in_progress используется для ожидания завершения ввода-вывода в буфере. Когда процесс PostgreSQL загружает / записывает данные страницы из / в хранилище, процесс удерживает эксклюзивную блокировку io_in_progress соответствующего дескриптора при доступе к хранилищу.

8.3.2.3. спин-блокировка

Когда флаги или другие поля (например, refcount и usage_count) проверяются или изменяются, используется спин-блокировка. Ниже приведены два конкретных примера использования спин-блокировки:

  • (1) Ниже показано, как закрепить дескриптор буфера:
    • 1. Получите спин-блокировку дескриптора буфера.
    • 2. Увеличьте значения его refcount и usage_count на 1.
    • 3. Отпустите спин-блокировку.
  • LockBufHdr ( bufferdesc ); / * 
    Получение спин-блокировки * / bufferdesc -> refcont ++; 
    bufferdesc -> usage_count ++; UnlockBufHdr ( bufferdesc ); / * Отпускаем спин-блокировку * /
  • (2) Ниже показано, как установить грязный бит в «1»:
    • 1. Получите спин-блокировку дескриптора буфера.
    • 2. Установите «грязный» бит в «1» с помощью побитовой операции.
    • 3. Отпустите спин-блокировку.
  • #define BM_DIRTY              ( 1 << 0 ) / * данные требуют записи * / #define BM_VALID              ( 1 << 1 ) / * данные действительны * / #define BM_TAG_VALID          ( 1 << 2 ) / * назначен тег * / #define BM_IO_IN_PROGRESS ( 1 << 3 ) / * выполняется     чтение или запись * / #define BM_JUST_DIRTIED       ( 1 << 5 ) / * загрязнено с момента начала записи * /      
     
     
     
     
     
    LockBufHdr ( bufferdesc ); 
    bufferdesc -> flags | = BM_DIRTY ; UnlockBufHdr ( bufferdesc );

    Аналогичным образом выполняется изменение других битов.


Замена спин-блокировки диспетчера буферов атомарными операциями

В версии 9.6 спин-блокировки диспетчера буферов будут заменены атомарными операциями. Посмотрите этот результат commitfest . Если вы хотите узнать подробности, обратитесь к этому обсуждению .


8.4. Как работает диспетчер буферов

В этом разделе описывается, как работает диспетчер буферов. Когда серверный процесс хочет получить доступ к желаемой странице, он вызывает функцию ReadBufferExtended .

Поведение функции ReadBufferExtended зависит от трех логических случаев. Каждый случай описан в следующих подразделах. Кроме того, в последнем подразделе описан алгоритм замены страницы тактовой развертки PostgreSQL .

8.4.1. Доступ к странице, хранящейся в пуле буферов

Сначала описывается простейший случай, т. Е. Желаемая страница уже хранится в пуле буферов. В этом случае диспетчер буферов выполняет следующие шаги:

  • (1) Создайте buffer_tag желаемой страницы (в этом примере buffer_tag - «Tag_C») и вычислите слот хэш -корзины , который содержит связанную запись созданного buffer_tag , используя хеш-функцию.
  • (2) Получите раздел BufMappingLock, который покрывает полученный слот хэш-корзины в общем режиме (эта блокировка будет снята на шаге (5)).
  • (3) Найдите запись с тегом «Tag_C» и получите из нее buffer_id . В этом примере buffer_id равен 2.
  • (4) Закрепите дескриптор буфера для buffer_id 2, т.е. refcount и usage_count дескриптора увеличиваются на 1 ( закрепление описывается в разделе 8.3.2 ).
  • (5) Отпустите BufMappingLock.
  • (6) Получите доступ к слоту пула буферов с помощью buffer_id 2.

Рис. 8.8. Доступ к странице, хранящейся в буферном пуле.

Затем при чтении строк со страницы в слоте пула буферов процесс PostgreSQL получает общий блок content_lock соответствующего дескриптора буфера. Таким образом, слоты буферного пула могут быть прочитаны одновременно несколькими процессами.

При вставке (и обновлении или удалении) строк на страницу процесс Postgres получает эксклюзивный content_lock соответствующего дескриптора буфера (обратите внимание, что грязный бит страницы должен быть установлен в '1').

После доступа к страницам значения refcount соответствующих дескрипторов буфера уменьшаются на 1.

8.4.2. Загрузка страницы из хранилища в пустой слот

Во втором случае предположим, что нужная страница отсутствует в пуле буферов, а список фрилансеров имеет свободные элементы (пустые дескрипторы). В этом случае диспетчер буферов выполняет следующие шаги:

  • (1) Найдите буферную таблицу (мы предполагаем, что она не найдена).
    • 1. Создайте buffer_tag желаемой страницы (в этом примере buffer_tag - Tag_E) и вычислите слот хэш-корзины.
    • 2. Получите раздел BufMappingLock в общем режиме.
    • 3. Найдите буферную таблицу (согласно предположению не найдено).
    • 4. Отпустите BufMappingLock.
  • (2) Получите дескриптор пустого буфера из списка фрилансеров и закрепите его. В этом примере buffer_id полученного дескриптора равен 4.
  • (3) Получите раздел BufMappingLock в монопольном режиме (эта блокировка будет снята на шаге (6)).
  • (4) Создать новую запись данных, содержащую buffer_tag «Tag_E» и buffer_id 4; вставить созданную запись в буферную таблицу.
  • (5) Загрузите данные нужной страницы из хранилища в слот буферного пула с buffer_id 4 следующим образом:
    • 1. Получите эксклюзивный io_in_progress_lock соответствующего дескриптора.
    • 2. Установите бит io_in_progress соответствующего дескриптора в '1, чтобы предотвратить доступ других процессов.
    • 3. Загрузите данные нужной страницы из хранилища в слот пула буферов.
    • 4. Измените состояния соответствующего дескриптора; io_in_progress бит установлен в «0», а действительный бит установлен в «1».
    • 5. Отпустите io_in_progress_lock.
  • (6) Отпустите BufMappingLock.
  • (7) Получите доступ к слоту пула буферов с помощью buffer_id 4.

Рис. 8.9. Загрузка страницы из хранилища в пустой слот.

8.4.3. Загрузка страницы из хранилища в слот буферного пула жертвы

В этом случае предположим, что все слоты пула буферов заняты страницами, но нужная страница не сохраняется. Диспетчер буферов выполняет следующие шаги:

  • (1) Создайте buffer_tag желаемой страницы и найдите таблицу буферов. В этом примере мы предполагаем, что buffer_tag равен «Tag_M» (нужная страница не найдена).
  • (2) Выберите слот пула буферов жертвы, используя алгоритм тактовой развертки, получите старую запись, которая содержит buffer_id слота пула жертв, из таблицы буферов и закрепите слот пула жертв на уровне дескрипторов буфера. В этом примере buffer_id слота жертвы равен 5, а старая запись - Tag_F, id = 5. Развертка часов описана в следующем подразделе .
  • (3) Очистить (записать и fsync) данные страницы жертвы, если она грязная; в противном случае переходите к шагу (4).
    Грязная страница должна быть записана в хранилище перед перезаписью новыми данными. Очистка грязной страницы выполняется следующим образом:
    • 1. Получите совместно используемую блокировку content_lock и эксклюзивную блокировку io_in_progress дескриптора с buffer_id 5 (освобождается на шаге 6).
    • 2. Измените состояния соответствующего дескриптора; io_in_progress бит установлен в «1» и just_dirtied бит установлен в «0».
    • 3. В зависимости от ситуации вызывается функция XLogFlush () для записи данных WAL из буфера WAL в текущий файл сегмента WAL (подробности опускаются; WAL и функция XLogFlush описаны в главе 9 ).
    • 4. Сбросьте данные страницы жертвы в хранилище.
    • 5. Измените состояния соответствующего дескриптора; io_in_progress бит установлен в «0» и действительный бит установлен в «1».
    • 6. Снимите блокировки io_in_progress и content_lock.
  • (4) Получите старый раздел BufMappingLock, закрывающий слот, содержащий старую запись, в монопольном режиме.
  • (5) Получите новый раздел BufMappingLock и вставьте новую запись в буферную таблицу:
    • 1. Создайте новую запись, состоящую из нового buffer_tag «Tag_M» и buffer_id жертвы.
    • 2. Получите новый раздел BufMappingLock, который закрывает слот, содержащий новую запись, в монопольном режиме.
    • 3. Вставьте новую запись в буферную таблицу.

Рис. 8.10. Загрузка страницы из хранилища в слот пула буферов жертвы.

  • (6) Удалите старую запись из буферной таблицы и освободите старый раздел BufMappingLock.
  • (7) Загрузите данные нужной страницы из хранилища в буферный слот жертвы. Затем обновите флаги дескриптора с buffer_id 5; грязный бит устанавливается в '0 и инициализирует другие биты.
  • (8) Освободите новый раздел BufMappingLock.
  • (9) Получите доступ к слоту пула буферов с помощью buffer_id 5.

Рис. 8.11. Загрузка страницы из хранилища в слот пула буферов жертвы (продолжение с рис. 8.10).

8.4.4. Алгоритм замены страницы: развертка по часам

Остальная часть этого раздела описывает алгоритм тактовой развертки . Этот алгоритм представляет собой вариант NFU (не часто используется) с низкими накладными расходами; он эффективно выбирает менее часто используемые страницы.

Представьте себе дескрипторы буфера в виде кольцевого списка (рис. 8.12). NextVictimBuffer, 32-разрядное целое число без знака, всегда указывает на один из дескрипторов буфера и вращается по часовой стрелке. Псевдокод и описание алгоритма следующие:


Псевдокод: часы

     ПОКА верно ( 1 ) Получение кандидата дескриптор буфера , на который указывает nextVictimBuffer
 ( 2 ) Если кандидат дескриптора является откреплены ТО ( 3 ) Если кандидат дескриптора ' ы usage_count == 0 ТОГДА BREAK While Loop   / * соответствующий слот этого дескриптора слот жертвы. * / ELSE Уменьшить кандидатскую descriptpor ' s usage_count на 1 END IF 
 
 
 
 
 
 
         END IF ( 4 ) Переход от nextVictimBuffer к следующему
       END WHILE ( 5 ) RETURN buffer_id жертвы
  • (1) Получить дескриптор буфера кандидата, на который указывает nextVictimBuffer .
  • (2) Если дескриптор буфера кандидата откреплен , перейдите к шагу (3); в противном случае переходите к шагу (4).
  • (3) Если usage_count дескриптора кандидата равно 0 , выберите соответствующий слот этого дескриптора в качестве жертвы и перейдите к шагу (5); в противном случае уменьшите usage_count этого дескриптора на 1 и перейдите к шагу (4).
  • (4) Переместите nextVictimBuffer к следующему дескриптору (если в конце, выполните циклический переход) и вернитесь к шагу (1). Повторяйте, пока не найдете жертву.
  • (5) Вернуть buffer_id жертвы.


Конкретный пример показан на рис. 8.12. Дескрипторы буфера показаны синими или голубыми прямоугольниками, а числа в прямоугольниках показывают usage_count каждого дескриптора.

Рис. 8.12. Развертка часов.

  • 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, путем вращения nextVictimBuffer .

8.5. Кольцевой буфер

При чтении или записи огромной таблицы PostgreSQL использует кольцевой буфер, а не пул буферов. Кольцевой буфер представляет собой небольшой и область временного буфера. Когда выполняется любое из перечисленных ниже условий, в разделяемую память выделяется кольцевой буфер:

  1. Массовое чтение Когда сканируется отношение, размер которого превышает четверть размера буферного пула (shared_buffers / 4). В этом случае размер кольцевого буфера составляет 256 КБ .
  2. Массовое написание Когда выполняются перечисленные ниже команды SQL. В этом случае размер кольцевого буфера составляет 16 МБ .
  3. Вакуумная обработкаКогда автовакуум выполняет вакуумную обработку. В этом случае размер кольцевого буфера составляет 256 КБ .

Выделенный кольцевой буфер освобождается сразу после использования.

Польза кольцевого буфера очевидна. Если внутренний процесс читает огромную таблицу без использования кольцевого буфера, все сохраненные страницы в пуле буферов удаляются (удаляются); следовательно, коэффициент попадания в кэш уменьшается. Кольцевой буфер позволяет избежать этой проблемы.


Почему размер кольцевого буфера по умолчанию для массового чтения и вакуумной обработки составляет 256 КБ?

Почему 256 КБ? Ответ объясняется в README, расположенном в исходном каталоге диспетчера буферов.

Для последовательных сканирований используется кольцо размером 256 КБ. Это достаточно мало, чтобы поместиться в кэш L2, что делает перенос страниц из кеша ОС в общий буферный кеш эффективным. Часто бывает достаточно даже меньшего, но кольцо должно быть достаточно большим, чтобы вместить все страницы в отсканированном изображении, которые закреплены одновременно. (вырезать)


8.6. Очистка грязных страниц

Помимо замены страниц-жертв, процессы контрольной точки и фоновой записи сбрасывают грязные страницы в хранилище. У обоих процессов одинаковая функция (очистка грязных страниц); однако у них разные роли и поведение.

Процесс контрольной точки записывает запись контрольной точки в файл сегмента WAL и сбрасывает грязные страницы при запуске контрольной точки. Раздел 9.7 описывает контрольные точки и время их начала.

Роль фонового писателя - уменьшить влияние интенсивного написания контрольных точек. Фоновый писатель продолжает постепенно сбрасывать грязные страницы с минимальным влиянием на активность базы данных. По умолчанию фоновый писатель просыпается каждые 200 мс (определяется bgwriter_delay ) и сбрасывает максимум bgwriter_lru_maxpages (по умолчанию 100 страниц).


Почему контрольная точка была отделена от фонового писателя?

В версии 9.1 или более ранней фоновый писатель регулярно выполнял обработку контрольных точек. В версии 9.2 процесс контрольной точки отделен от фонового процесса записи. Поскольку причина описана в предложении под названием «Разделение bgwriter и checkpointer» , предложения из него показаны ниже.

В настоящее время (в 2011 году) процесс bgwriter выполняет как фоновую запись, так и создание контрольных точек, а также некоторые другие обязанности. Это означает, что мы не можем выполнить последнюю контрольную точку fsync без остановки фоновой записи, поэтому выполнение обоих действий в одном процессе отрицательно сказывается на производительности.
Кроме того, наша цель в 9.2 - заменить петли опроса защелками для снижения мощности. Сложность циклов bgwriter высока, и маловероятно, что удастся придумать чистый подход с использованием защелок.
(вырезать)


postgres/osnovy/buffer_manager.txt · Последние изменения: 2023/01/12 12:18 (внешнее изменение)