Содержание

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. Как серверная часть читает страницу из диспетчера буферов.

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

Раздел 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. Трехуровневая структура диспетчера буферов.

 

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

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;

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


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

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

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

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

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

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

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


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


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

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

Аналогичным образом загружаются вторая и последующие страницы. Дополнительная информация представлена ​​в разделе 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 приобретается при выполнении одного из следующих действий:

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

8.3.2.2. io_in_progress_lock

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

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

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


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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Рис. 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 жертвы


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

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

Каждый раз, когда 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 высока, и маловероятно, что удастся придумать чистый подход с использованием защелок.
(вырезать)