===== 8.1. Обзор ===== В этом разделе представлены ключевые концепции, необходимые для облегчения описания в последующих разделах. ==== 8.1.1. Структура диспетчера буфера ==== Диспетчер буферов PostgreSQL состоит из таблицы буферов, дескрипторов буферов и пула буферов, которые описаны в следующем разделе. В **пулах буферов** страниц файлы слой хранит данные, такие как таблицы и индексы, а также [[https://www-interdb-jp.translate.goog/pg/pgsql05.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_5.3.4.|карты FREESPACE]] и [[https://www-interdb-jp.translate.goog/pg/pgsql06.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_6.2.|карта видимости]] . Пул буферов представляет собой массив, т. Е. Каждый слот хранит одну страницу файла данных. Индексы массива буферного пула упоминаются как **buffer_id** s. [[https://www-interdb-jp.translate.goog/pg/pgsql08.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_8.2.|Разделы 8.2]] и [[https://www-interdb-jp.translate.goog/pg/pgsql08.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_8.3.|8.3]] подробно описывают внутреннее устройство диспетчера буферов. ==== 8.1.2. Тег буфера ==== В PostgreSQL каждой странице всех файлов данных может быть присвоен уникальный тег, то есть **тег буфера** . Когда диспетчер буферов получает запрос, PostgreSQL использует buffer_tag желаемой страницы. [[javascript:void(0)?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http|Buffer_tag]] состоит из трех значений: при регистрации [[javascript:void(0)?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http|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. Как серверная часть читает страницу из диспетчера буферов.** {{ :postgres:основы:fig-8-02.png?nolink |}} * (1) При чтении таблицы или страницы индекса серверный процесс отправляет запрос, который включает в себя buffer_tag страницы, диспетчеру буферов. * (2) Диспетчер буферов возвращает buffer_ID слота, в котором хранится запрошенная страница. Если запрошенная страница не хранится в пуле буферов, диспетчер буферов загружает страницу из постоянного хранилища в один из слотов пула буферов, а затем возвращает слот buffer_ID. * (3) Внутренний процесс обращается к слоту buffer_ID (чтобы прочитать нужную страницу). Когда внутренний процесс изменяет страницу в пуле буферов (например, вставляя кортежи), измененная страница, которая еще не была сброшена в хранилище, называется **грязной страницей** . [[https://www-interdb-jp.translate.goog/pg/pgsql08.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_8.4.|Раздел 8.4]] описывает, как работает диспетчер буферов. ==== 8.1.4. Алгоритм замены страницы ==== Когда все слоты пула буферов заняты, но запрошенная страница не сохранена, диспетчер буферов должен выбрать одну страницу в пуле буферов, которая будет заменена запрошенной страницей. Обычно в области информатики алгоритмы выбора страниц называются //алгоритмами замены страниц,// а выбранная страница называется **страницей-жертвой** . Исследования алгоритмов замены страниц продолжаются с момента появления информатики; таким образом, ранее было предложено множество алгоритмов замены. Начиная с версии 8.1, PostgreSQL использует **тактовую развертку,** потому что это проще и эффективнее, чем алгоритм LRU, использовавшийся в предыдущих версиях. [[https://www-interdb-jp.translate.goog/pg/pgsql08.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_8.4.4.|Раздел 8.4.4]] описывает детали развертки тактовой частоты. ==== 8.1.5. Очистка грязных страниц ==== Грязные страницы в конечном итоге следует сбрасывать в хранилище; однако диспетчеру буферов требуется помощь для выполнения этой задачи. В PostgreSQL за эту задачу отвечают два фоновых процесса: **контрольная точка** и **фоновый писатель** . [[https://www-interdb-jp.translate.goog/pg/pgsql08.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_8.6.|Раздел 8.6]] описывает **checkpointer** и **background writer**. \\ //Прямой ввод / вывод// PostgreSQL **не** поддерживает прямой ввод-вывод, хотя иногда это обсуждается. Если вы хотите узнать больше, обратитесь к [[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=http://www.postgresql.org/message-id/529E267F.4050700@agliodbs.com|этому обсуждению]] pgsql-ML и [[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=http://lwn.net/Articles/580542/|этой статье]] . \\ ===== 8.2. Структура диспетчера буфера ===== Менеджер PostgreSQL буфер состоит из трех слоев, т.е. //таблицы буфера// , //буферные дескрипторы// и //пул буферов// (рис 8.3.): **Рис. 8.3. Трехуровневая структура диспетчера буферов.** {{ :postgres:основы:fig-8-03.png?nolink |}}   * **Пул буферов** является массивом. Каждый слот хранит страницы файла данных. Индексы слотов массива упоминаются как //buffer_id// s. * **Буферные дескрипторы** слой представляет собой массив буферных дескрипторов. Каждый дескриптор имеет однозначное соответствие слоту пула буферов и содержит метаданные сохраненной страницы в соответствующем слоте.\\ Обратите внимание, что термин «уровень дескрипторов буфера» был принят для удобства и используется только в этом документе. * Таблица **буферов** - это хэш-таблица, в которой хранятся отношения между //buffer_tag// сохраненных страниц и //buffer_id// дескрипторов, которые содержат соответствующие метаданные сохраненных страниц. Эти слои подробно описаны в следующих подразделах. ==== 8.2.1. Таблица буферов ==== Буферную таблицу можно логически разделить на три части: хэш-функция, слоты хеш-ведра и записи данных (рис. 8.4). Встроенная хеш-функция отображает buffer_tags на слоты хеш-сегмента. Даже если количество слотов хэш-корзины больше, чем количество слотов пула буферов, могут возникнуть коллизии. Таким образом, в буферной таблице для разрешения конфликтов используется //отдельная цепочка с// методом //связанных списков// . Когда записи данных отображаются в один и тот же слот корзины, этот метод сохраняет записи в том же связанном списке, как показано на рис. 8.4. **Рис. 8.4. Буферная таблица.** {{ :postgres:основы:fig-8-04.png?nolink |}} Запись данных содержит два значения: buffer_tag страницы и buffer_id дескриптора, который содержит метаданные страницы. Например, запись данных « //Tag_A, id = 1// » означает, что дескриптор буфера с buffer_id //1// хранит метаданные страницы, помеченной //Tag_A// . Хеш-функция - это составная функция [[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=https://doxygen.postgresql.org/dynahash_8c.html%23ae802f2654df749ae0e0aadf4b5c5bcbd|calc_bucket ()]] и [[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=https://doxygen.postgresql.org/rege__dfa_8c.html%23a6aa3a27e7a0fc6793f3329670ac3b0cb|hash ()]] . Ниже приводится его представление в виде псевдофункции. uint32 bucket_slot = calc_bucket ( беззнаковое хэш ( BufferTag buffer_tag ), uint32 bucket_size ) \\ Обратите внимание, что основные операции (поиск, вставка и удаление записей данных) здесь не объясняются. Это очень распространенные операции, которые описаны в следующих разделах. ==== 8.2.2. Дескриптор буфера ==== Структура дескриптора буфера описана в этом подразделе, а уровень дескрипторов буфера - в следующем. Дескриптор буфера содержит метаданные сохраненной страницы в соответствующем слоте пула буферов. Структура дескриптора буфера определяется структурой [[javascript:void(0)?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http|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// сохраненной страницы в соответствующем слоте пула буферов (тег буфера определен в [[https://www-interdb-jp.translate.goog/pg/pgsql08.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_8.1.2.|Разделе 8.1.2]] ). * **buffer_id** идентифицирует дескриптор (эквивалент //buffer_id// соответствующего слота буферного пула). * **refcount** содержит количество процессов PostgreSQL, которые в настоящее время обращаются к связанной сохраненной странице. Это также называется **подсчетом выводов** . Когда процесс PostgreSQL обращается к сохраненной странице, его refcount должен быть увеличен на 1 (refcount ++). После доступа к странице ее refcount необходимо уменьшить на 1 (refcount--).\\ Когда счетчик ссылок равен нулю, т. Е. К связанной сохраненной странице в настоящее время не осуществляется доступ, страница **открепляется** ; в противном случае он **закреплен** . * **usage_count** хранится количество обращений к связанной сохраненной странице с момента ее загрузки в соответствующий слот пула буферов. Обратите внимание, что usage_count используется в алгоритме замены страницы ( [[https://www-interdb-jp.translate.goog/pg/pgsql08.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_8.4.4.|раздел 8.4.4]] ). * **context_lock** и **io_in_progress_lock** являются легкими замками, которые используются для управления доступа к соответствующей сохраненной странице. Эти поля описаны в [[https://www-interdb-jp.translate.goog/pg/pgsql08.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_8.3.2.|Разделе 8.3.2]] . * **флаги** могут содержать несколько состояний связанной сохраненной страницы. Основные состояния следующие: * **грязный бит** указывает, **грязная** ли сохраненная страница. * **Действительный бит** указывает, может ли сохраненная страница быть прочитана или записана (действительна). Например, если этот бит //действителен// , то соответствующий слот пула буферов хранит страницу, а этот дескриптор (действительный бит) содержит метаданные страницы; таким образом, сохраненная страница может быть прочитана или записана. Если этот бит //недействителен// , то этот дескриптор не содержит никаких метаданных; это означает, что сохраненная страница не может быть прочитана или записана или диспетчер буферов заменяет сохраненную страницу. * **Бит io_in_progress** указывает, читает / записывает ли диспетчер буферов связанную страницу из / в хранилище. Другими словами, этот бит указывает, удерживает ли отдельный процесс блок io_in_progress_lock этого дескриптора. * **freeNext** является указателем на следующий дескриптор для генерации //FreeList// , который описан в следующем разделе. \\ ////Структура //BufferDesc// определена в [[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=https://github.com/postgres/postgres/blob/master/src/include/storage/buf_internals.h|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 //карты свободного пространства// , описанные в [[https://www-interdb-jp.translate.goog/pg/pgsql05.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_5.3.4.|Разделе 5.3.4]] , выполняют ту же роль, что и свободные списки в Oracle. \\ **Рис. 8.5. Начальное состояние диспетчера буферов.** {{ :postgres:основы:fig-8-05.png?nolink |}} На рис. 8.6 показано, как загружается первая страница. * (1) Извлеките пустой дескриптор из верхней части списка фрилансеров и закрепите его (т. Е. Увеличьте его refcount и usage_count на 1). * (2) Вставьте новую запись, которая содержит отношение между тегом первой страницы и buffer_id полученного дескриптора, в таблицу буферов. * (3) Загрузите новую страницу из хранилища в соответствующий слот пула буферов. * (4) Сохраните метаданные новой страницы в полученном дескрипторе. Аналогичным образом загружаются вторая и последующие страницы. Дополнительная информация представлена ​​в [[https://www-interdb-jp.translate.goog/pg/pgsql08.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_8.4.2.|разделе 8.4.2]] . **Рис. 8.6. Загрузка первой страницы.** {{ :postgres:основы:fig-8-06.png?nolink |}} Дескрипторы, полученные из списка фрилансеров, всегда содержат метаданные страницы. Другими словами, непустые дескрипторы продолжают использоваться и не возвращаются в список фрилансеров. Однако связанные дескрипторы снова добавляются в список фрилансеров, и состояние дескриптора становится «пустым», когда происходит одно из следующих событий: - Таблицы или индексы были отброшены. - Базы данных были отброшены. - Таблицы или индексы были очищены с помощью команды VACUUM FULL. \\ //Почему пустые дескрипторы составляют список фрилансеров?// Причина создания списка фрилансеров - немедленное получение первого дескриптора. Это обычная практика для распределения ресурсов динамической памяти. См. [[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=https://en.wikipedia.org/wiki/Free_list|Это описание]] . \\ Слой дескрипторов буфера содержит беззнаковую 32-битную целочисленную переменную, то есть **nextVictimBuffer** . Эта переменная используется в алгоритме замены страниц, описанном в [[https://www-interdb-jp.translate.goog/pg/pgsql08.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_8.4.4.|Разделе 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 в монопольном режиме для вставки новых записей данных.** {{ :postgres:основы:fig-8-07.png?nolink |}} Буферная таблица требует многих других блокировок. Например, буферная таблица внутренне использует спин-блокировку для удаления записи. Однако описания этих других блокировок опущены, потому что они не требуются в этом документе. \\ ////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 описаны в [[https://www-interdb-jp.translate.goog/pg/pgsql05.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_5.2.|Разделе 5.2]] ; просто, при удалении или обновлении строк эти поля связанных кортежей изменяются) . * Физическое удаление кортежей или уплотнение свободного места на сохраненной странице (выполняется с помощью вакуумной обработки и HOT, которые описаны в [[https://www-interdb-jp.translate.goog/pg/pgsql06.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http|главах 6]] и [[https://www-interdb-jp.translate.goog/pg/pgsql07.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http|7]] соответственно). * Замораживание кортежей на сохраненной странице (замораживание описано в [[https://www-interdb-jp.translate.goog/pg/pgsql05.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_5.10.1.|Разделе 5.10.1]] и [[https://www-interdb-jp.translate.goog/pg/pgsql06.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_6.3.|Разделе 6.3]] ). Официальный файл [[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=https://github.com/postgres/postgres/blob/master/src/backend/storage/buffer/README|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 спин-блокировки диспетчера буферов будут заменены атомарными операциями. Посмотрите этот [[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=https://commitfest.postgresql.org/9/408/|результат commitfest]] . Если вы хотите узнать подробности, обратитесь к [[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=http://www.postgresql.org/message-id/flat/2400449.GjM57CE0Yg@dinodell%232400449.GjM57CE0Yg@dinodell|этому обсуждению]] . \\ ===== 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 ( закрепление описывается в [[https://www-interdb-jp.translate.goog/pg/pgsql08.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_8.3.2.|разделе 8.3.2]] ). * (5) Отпустите BufMappingLock. * (6) Получите доступ к слоту пула буферов с помощью buffer_id 2. **Рис. 8.8. Доступ к странице, хранящейся в буферном пуле.** {{ :postgres:основы:fig-8-08.png?nolink |}} Затем при чтении строк со страницы в слоте пула буферов процесс 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. Загрузка страницы из хранилища в пустой слот.** {{ :postgres:основы:fig-8-09.png?nolink |}} ==== 8.4.3. Загрузка страницы из хранилища в слот буферного пула жертвы ==== В этом случае предположим, что все слоты пула буферов заняты страницами, но нужная страница не сохраняется. Диспетчер буферов выполняет следующие шаги: * (1) Создайте buffer_tag желаемой страницы и найдите таблицу буферов. В этом примере мы предполагаем, что buffer_tag равен «Tag_M» (нужная страница не найдена). * (2) Выберите слот пула буферов жертвы, используя алгоритм тактовой развертки, получите старую запись, которая содержит buffer_id слота пула жертв, из таблицы буферов и закрепите слот пула жертв на уровне дескрипторов буфера. В этом примере buffer_id слота жертвы равен 5, а старая запись - Tag_F, id = 5. Развертка часов описана в [[https://www-interdb-jp.translate.goog/pg/pgsql08.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_8.4.4.|следующем подразделе]] . * (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// описаны в [[https://www-interdb-jp.translate.goog/pg/pgsql09.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http|главе 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. Загрузка страницы из хранилища в слот пула буферов жертвы.** {{ :postgres:основы:fig-8-10.png?nolink |}} * (6) Удалите старую запись из буферной таблицы и освободите старый раздел BufMappingLock. * (7) Загрузите данные нужной страницы из хранилища в буферный слот жертвы. Затем обновите флаги дескриптора с buffer_id 5; грязный бит устанавливается в '0 и инициализирует другие биты. * (8) Освободите новый раздел BufMappingLock. * (9) Получите доступ к слоту пула буферов с помощью buffer_id 5. **Рис. 8.11. Загрузка страницы из хранилища в слот пула буферов жертвы (продолжение с рис. 8.10).** {{ :postgres:основы:fig-8-11.png?nolink |}} ==== 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. Развертка часов.** {{ :postgres:основы:fig-8-12.png?nolink |}} * 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 использует **кольцевой буфер,** а не пул буферов. //Кольцевой буфер// представляет собой небольшой и область временного буфера. Когда выполняется любое из перечисленных ниже условий, в разделяемую память выделяется кольцевой буфер: - Массовое чтение Когда сканируется отношение, размер которого превышает четверть размера буферного пула (shared_buffers / 4). В этом случае размер кольцевого буфера составляет //256 КБ// . - Массовое написание Когда выполняются перечисленные ниже команды SQL. В этом случае размер кольцевого буфера составляет //16 МБ// . * //[[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=http://www.postgresql.org/docs/current/static/sql-copy.html|КОПИРОВАТЬ ИЗ]]// команды. * //[[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=http://www.postgresql.org/docs/current/static/sql-createtableas.html|СОЗДАТЬ ТАБЛИЦУ КАК]]// . * //[[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=http://www.postgresql.org/docs/current/static/sql-creatematerializedview.html|СОЗДАТЬ МАТЕРИАЛИЗОВАННЫЙ ВИД]]// или //[[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=http://www.postgresql.org/docs/current/static/sql-refreshmaterializedview.html|ОБНОВИТЬ МАТЕРИАЛИЗИРОВАННЫЙ ВИД]]// команды. * ////Команда //[[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=http://www.postgresql.org/docs/current/static/sql-altertable.html|ALTER TABLE]]// . - Вакуумная обработкаКогда автовакуум выполняет вакуумную обработку. В этом случае размер кольцевого буфера составляет //256 КБ// . Выделенный кольцевой буфер освобождается сразу после использования. Польза кольцевого буфера очевидна. Если внутренний процесс читает огромную таблицу без использования кольцевого буфера, все сохраненные страницы в пуле буферов удаляются (удаляются); следовательно, коэффициент попадания в кэш уменьшается. Кольцевой буфер позволяет избежать этой проблемы. \\ //Почему размер кольцевого буфера по умолчанию для массового чтения и вакуумной обработки составляет 256 КБ?// Почему 256 КБ? Ответ объясняется в [[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=https://github.com/postgres/postgres/blob/master/src/backend/storage/buffer/README|README,]] расположенном в исходном каталоге диспетчера буферов. > Для последовательных сканирований используется кольцо размером 256 КБ. Это достаточно мало, чтобы поместиться в кэш L2, что делает перенос страниц из кеша ОС в общий буферный кеш эффективным. Часто бывает достаточно даже меньшего, но кольцо должно быть достаточно большим, чтобы вместить все страницы в отсканированном изображении, которые закреплены одновременно. (вырезать) \\ ===== 8.6. Очистка грязных страниц ===== Помимо замены страниц-жертв, процессы контрольной точки и фоновой записи сбрасывают грязные страницы в хранилище. У обоих процессов одинаковая функция (очистка грязных страниц); однако у них разные роли и поведение. Процесс контрольной точки записывает запись контрольной точки в файл сегмента WAL и сбрасывает грязные страницы при запуске контрольной точки. [[https://www-interdb-jp.translate.goog/pg/pgsql09.html?_x_tr_sl=en&_x_tr_tl=ru&_x_tr_hl=en&_x_tr_pto=nui&_x_tr_sch=http#_9.7.|Раздел 9.7]] описывает контрольные точки и время их начала. Роль фонового писателя - уменьшить влияние интенсивного написания контрольных точек. Фоновый писатель продолжает постепенно сбрасывать грязные страницы с минимальным влиянием на активность базы данных. По умолчанию фоновый писатель просыпается каждые 200 мс (определяется [[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=http://www.postgresql.org/docs/current/static/runtime-config-resource.html%23GUC-BGWRITER-DELAY|bgwriter_delay]] ) и сбрасывает [[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=http://www.postgresql.org/docs/current/static/runtime-config-resource.html%23GUC-BGWRITER-LRU-MAXPAGES|максимум bgwriter_lru_maxpages]] (по умолчанию 100 страниц). \\ //Почему контрольная точка была отделена от фонового писателя?// В версии 9.1 или более ранней фоновый писатель регулярно выполнял обработку контрольных точек. В версии 9.2 процесс контрольной точки отделен от фонового процесса записи. Поскольку причина описана в предложении под названием [[https://translate.google.com/website?sl=en&tl=ru&nui=1&u=https://www.postgresql.org/message-id/CA%252BU5nMLv2ah-HNHaQ%253D2rxhp_hDJ9jcf-LL2kW3sE4msfnUw9gA%2540mail.gmail.com|«Разделение bgwriter и checkpointer»]] , предложения из него показаны ниже. > В настоящее время (в 2011 году) процесс bgwriter выполняет как фоновую запись, так и создание контрольных точек, а также некоторые другие обязанности. Это означает, что мы не можем выполнить последнюю контрольную точку fsync без остановки фоновой записи, поэтому выполнение обоих действий в одном процессе отрицательно сказывается на производительности.\\ Кроме того, наша цель в 9.2 - заменить петли опроса защелками для снижения мощности. Сложность циклов bgwriter высока, и маловероятно, что удастся придумать чистый подход с использованием защелок.\\ (вырезать) \\