Оглавление:
Карта сайта:
Оглавление:
Карта сайта:
В этом разделе представлены ключевые концепции, необходимые для облегчения описания в последующих разделах.
Диспетчер буферов PostgreSQL состоит из таблицы буферов, дескрипторов буферов и пула буферов, которые описаны в следующем разделе. В пулах буферов страниц файлы слой хранит данные, такие как таблицы и индексы, а также карты FREESPACE и карта видимости . Пул буферов представляет собой массив, т. Е. Каждый слот хранит одну страницу файла данных. Индексы массива буферного пула упоминаются как buffer_id s.
Разделы 8.2 и 8.3 подробно описывают внутреннее устройство диспетчера буферов.
В 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.2).
Рис. 8.2. Как серверная часть читает страницу из диспетчера буферов.
Когда внутренний процесс изменяет страницу в пуле буферов (например, вставляя кортежи), измененная страница, которая еще не была сброшена в хранилище, называется грязной страницей .
Раздел 8.4 описывает, как работает диспетчер буферов.
Когда все слоты пула буферов заняты, но запрошенная страница не сохранена, диспетчер буферов должен выбрать одну страницу в пуле буферов, которая будет заменена запрошенной страницей. Обычно в области информатики алгоритмы выбора страниц называются алгоритмами замены страниц, а выбранная страница называется страницей-жертвой .
Исследования алгоритмов замены страниц продолжаются с момента появления информатики; таким образом, ранее было предложено множество алгоритмов замены. Начиная с версии 8.1, PostgreSQL использует тактовую развертку, потому что это проще и эффективнее, чем алгоритм LRU, использовавшийся в предыдущих версиях.
Раздел 8.4.4 описывает детали развертки тактовой частоты.
Грязные страницы в конечном итоге следует сбрасывать в хранилище; однако диспетчеру буферов требуется помощь для выполнения этой задачи. В PostgreSQL за эту задачу отвечают два фоновых процесса: контрольная точка и фоновый писатель .
Раздел 8.6 описывает checkpointer и background writer.
Прямой ввод / вывод
PostgreSQL не поддерживает прямой ввод-вывод, хотя иногда это обсуждается. Если вы хотите узнать больше, обратитесь к этому обсуждению pgsql-ML и этой статье .
Менеджер PostgreSQL буфер состоит из трех слоев, т.е. таблицы буфера , буферные дескрипторы и пул буферов (рис 8.3.):
Рис. 8.3. Трехуровневая структура диспетчера буферов.
Эти слои подробно описаны в следующих подразделах.
Буферную таблицу можно логически разделить на три части: хэш-функция, слоты хеш-ведра и записи данных (рис. 8.4).
Встроенная хеш-функция отображает buffer_tags на слоты хеш-сегмента. Даже если количество слотов хэш-корзины больше, чем количество слотов пула буферов, могут возникнуть коллизии. Таким образом, в буферной таблице для разрешения конфликтов используется отдельная цепочка с методом связанных списков . Когда записи данных отображаются в один и тот же слот корзины, этот метод сохраняет записи в том же связанном списке, как показано на рис. 8.4.
Рис. 8.4. Буферная таблица.
Запись данных содержит два значения: buffer_tag страницы и buffer_id дескриптора, который содержит метаданные страницы. Например, запись данных « Tag_A, id = 1 » означает, что дескриптор буфера с buffer_id 1 хранит метаданные страницы, помеченной Tag_A .
uint32 bucket_slot = calc_bucket ( беззнаковое хэш ( BufferTag buffer_tag ), uint32 bucket_size )
Обратите внимание, что основные операции (поиск, вставка и удаление записей данных) здесь не объясняются. Это очень распространенные операции, которые описаны в следующих разделах.
Структура дескриптора буфера описана в этом подразделе, а уровень дескрипторов буфера - в следующем.
Дескриптор буфера содержит метаданные сохраненной страницы в соответствующем слоте пула буферов. Структура дескриптора буфера определяется структурой 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;
Для упрощения следующих описаний определены три состояния дескриптора:
Каждый дескриптор будет иметь одно из перечисленных выше состояний. Состояние дескриптора изменяется в зависимости от конкретных условий, которые описаны в следующем подразделе.
На следующих рисунках состояния дескрипторов буфера представлены цветными прямоугольниками.
Кроме того, грязная страница обозначается буквой «X». Например, откреплен грязный дескриптор представлен X .
Коллекция дескрипторов буфера образует массив. В этом документе массив называется слоем дескрипторов буфера .
Когда сервер PostgreSQL запускается, состояние всех дескрипторов буфера пустое . В PostgreSQL эти дескрипторы составляют связанный список, называемый freelist (рис. 8.5).
Рис. 8.5. Начальное состояние диспетчера буферов.
На рис. 8.6 показано, как загружается первая страница.
Аналогичным образом загружаются вторая и последующие страницы. Дополнительная информация представлена в разделе 8.4.2 .
Рис. 8.6. Загрузка первой страницы.
Дескрипторы, полученные из списка фрилансеров, всегда содержат метаданные страницы. Другими словами, непустые дескрипторы продолжают использоваться и не возвращаются в список фрилансеров. Однако связанные дескрипторы снова добавляются в список фрилансеров, и состояние дескриптора становится «пустым», когда происходит одно из следующих событий:
Причина создания списка фрилансеров - немедленное получение первого дескриптора. Это обычная практика для распределения ресурсов динамической памяти. См. Это описание .
Слой дескрипторов буфера содержит беззнаковую 32-битную целочисленную переменную, то есть nextVictimBuffer . Эта переменная используется в алгоритме замены страниц, описанном в Разделе 8.4.4 .
Пул буферов - это простой массив, в котором хранятся страницы файлов данных, такие как таблицы и индексы. Индексы массива буферного пула упоминаются как buffer_id s.
Размер слота пула буферов составляет 8 КБ, что равно размеру страницы. Таким образом, каждый слот может хранить целую страницу.
Диспетчер буферов использует множество блокировок для разных целей. В этом разделе описаны блокировки, необходимые для пояснений в следующих разделах.
Обратите внимание, что блокировки, описанные в этом разделе, являются частью механизма синхронизации для диспетчера буферов; они не связаны ни с какими операторами SQL и опциями SQL.
BufMappingLock защищает целостность данных всей буферной таблицы. Это легкий замок, который можно использовать как в общем, так и в эксклюзивном режимах. При поиске записи в буферной таблице внутренний процесс содержит общий BufMappingLock. При вставке или удалении записей серверный процесс удерживает исключительную блокировку.
BufMappingLock разделен на разделы, чтобы уменьшить количество конфликтов в буферной таблице (по умолчанию 128 разделов). Каждый раздел BufMappingLock охраняет часть соответствующих слотов хэш-корзины.
На рисунке 8.7 показан типичный пример эффекта разделения BufMappingLock. Два внутренних процесса могут одновременно удерживать соответствующие разделы BufMappingLock в монопольном режиме для вставки новых записей данных. Если BufMappingLock - это единственная общесистемная блокировка, оба процесса должны дождаться обработки другого процесса, в зависимости от того, который начал обработку.
Рис. 8.7. Два процесса одновременно получают соответствующие разделы BufMappingLock в монопольном режиме для вставки новых записей данных.
Буферная таблица требует многих других блокировок. Например, буферная таблица внутренне использует спин-блокировку для удаления записи. Однако описания этих других блокировок опущены, потому что они не требуются в этом документе.
BufMappingLock по умолчанию был разделен на 16 отдельных блокировок до версии 9.4.
Каждый дескриптор буфера использует две облегченные блокировки, content_lock и io_in_progress_lock , для управления доступом к сохраненной странице в соответствующем слоте пула буферов. Когда значения собственных полей проверяются или изменяются, используется спин-блокировка.
Content_lock - это типичная блокировка, которая устанавливает ограничения доступа. Его можно использовать в совместном и эксклюзивном режимах.
При чтении страницы серверный процесс получает общий content_lock дескриптора буфера, в котором хранится страница.
Однако эксклюзивный content_lock приобретается при выполнении одного из следующих действий:
Официальный файл README показывает более подробную информацию.
Блокировка io_in_progress используется для ожидания завершения ввода-вывода в буфере. Когда процесс PostgreSQL загружает / записывает данные страницы из / в хранилище, процесс удерживает эксклюзивную блокировку io_in_progress соответствующего дескриптора при доступе к хранилищу.
Когда флаги или другие поля (например, refcount и usage_count) проверяются или изменяются, используется спин-блокировка. Ниже приведены два конкретных примера использования спин-блокировки:
LockBufHdr ( bufferdesc ); / * Получение спин-блокировки * / bufferdesc -> refcont ++; bufferdesc -> usage_count ++; UnlockBufHdr ( bufferdesc ); / * Отпускаем спин-блокировку * /
#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 . Если вы хотите узнать подробности, обратитесь к этому обсуждению .
В этом разделе описывается, как работает диспетчер буферов. Когда серверный процесс хочет получить доступ к желаемой странице, он вызывает функцию ReadBufferExtended .
Поведение функции ReadBufferExtended зависит от трех логических случаев. Каждый случай описан в следующих подразделах. Кроме того, в последнем подразделе описан алгоритм замены страницы тактовой развертки PostgreSQL .
Сначала описывается простейший случай, т. Е. Желаемая страница уже хранится в пуле буферов. В этом случае диспетчер буферов выполняет следующие шаги:
Рис. 8.8. Доступ к странице, хранящейся в буферном пуле.
Затем при чтении строк со страницы в слоте пула буферов процесс PostgreSQL получает общий блок content_lock соответствующего дескриптора буфера. Таким образом, слоты буферного пула могут быть прочитаны одновременно несколькими процессами.
При вставке (и обновлении или удалении) строк на страницу процесс Postgres получает эксклюзивный content_lock соответствующего дескриптора буфера (обратите внимание, что грязный бит страницы должен быть установлен в '1').
После доступа к страницам значения refcount соответствующих дескрипторов буфера уменьшаются на 1.
Во втором случае предположим, что нужная страница отсутствует в пуле буферов, а список фрилансеров имеет свободные элементы (пустые дескрипторы). В этом случае диспетчер буферов выполняет следующие шаги:
Рис. 8.9. Загрузка страницы из хранилища в пустой слот.
В этом случае предположим, что все слоты пула буферов заняты страницами, но нужная страница не сохраняется. Диспетчер буферов выполняет следующие шаги:
Рис. 8.10. Загрузка страницы из хранилища в слот пула буферов жертвы.
Рис. 8.11. Загрузка страницы из хранилища в слот пула буферов жертвы (продолжение с рис. 8.10).
Остальная часть этого раздела описывает алгоритм тактовой развертки . Этот алгоритм представляет собой вариант 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 .
При чтении или записи огромной таблицы PostgreSQL использует кольцевой буфер, а не пул буферов. Кольцевой буфер представляет собой небольшой и область временного буфера. Когда выполняется любое из перечисленных ниже условий, в разделяемую память выделяется кольцевой буфер:
Выделенный кольцевой буфер освобождается сразу после использования.
Польза кольцевого буфера очевидна. Если внутренний процесс читает огромную таблицу без использования кольцевого буфера, все сохраненные страницы в пуле буферов удаляются (удаляются); следовательно, коэффициент попадания в кэш уменьшается. Кольцевой буфер позволяет избежать этой проблемы.
Почему размер кольцевого буфера по умолчанию для массового чтения и вакуумной обработки составляет 256 КБ?
Почему 256 КБ? Ответ объясняется в README, расположенном в исходном каталоге диспетчера буферов.
Для последовательных сканирований используется кольцо размером 256 КБ. Это достаточно мало, чтобы поместиться в кэш L2, что делает перенос страниц из кеша ОС в общий буферный кеш эффективным. Часто бывает достаточно даже меньшего, но кольцо должно быть достаточно большим, чтобы вместить все страницы в отсканированном изображении, которые закреплены одновременно. (вырезать)
Помимо замены страниц-жертв, процессы контрольной точки и фоновой записи сбрасывают грязные страницы в хранилище. У обоих процессов одинаковая функция (очистка грязных страниц); однако у них разные роли и поведение.
Процесс контрольной точки записывает запись контрольной точки в файл сегмента WAL и сбрасывает грязные страницы при запуске контрольной точки. Раздел 9.7 описывает контрольные точки и время их начала.
Роль фонового писателя - уменьшить влияние интенсивного написания контрольных точек. Фоновый писатель продолжает постепенно сбрасывать грязные страницы с минимальным влиянием на активность базы данных. По умолчанию фоновый писатель просыпается каждые 200 мс (определяется bgwriter_delay ) и сбрасывает максимум bgwriter_lru_maxpages (по умолчанию 100 страниц).
Почему контрольная точка была отделена от фонового писателя?
В версии 9.1 или более ранней фоновый писатель регулярно выполнял обработку контрольных точек. В версии 9.2 процесс контрольной точки отделен от фонового процесса записи. Поскольку причина описана в предложении под названием «Разделение bgwriter и checkpointer» , предложения из него показаны ниже.
В настоящее время (в 2011 году) процесс bgwriter выполняет как фоновую запись, так и создание контрольных точек, а также некоторые другие обязанности. Это означает, что мы не можем выполнить последнюю контрольную точку fsync без остановки фоновой записи, поэтому выполнение обоих действий в одном процессе отрицательно сказывается на производительности.
Кроме того, наша цель в 9.2 - заменить петли опроса защелками для снижения мощности. Сложность циклов bgwriter высока, и маловероятно, что удастся придумать чистый подход с использованием защелок.
(вырезать)