Глава 66. Унифицированные записи WAL

Хотя у всех внутренних модулей, взаимодействующих с WAL, имеются собственные типы записей WAL, существует также унифицированный тип записей WAL, описывающий изменения в страницах унифицированным образом. Это полезно для расширений, реализующих собственные методы доступа.

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

Примечание

Унифицированные записи WAL игнорируются во время логического декодирования. Если для вашего расширения требуется логическое декодирование, рассмотрите возможность использования пользовательского менеджера ресурсов WAL.

API для конструирования унифицированных записей WAL определён в access/generic_xlog.h и реализован в access/transam/generic_xlog.c.

Чтобы сформировать запись изменения данных для WAL, применяя механизм унифицированных записей WAL, выполните следующие действия:

  1. state = GenericXLogStart(relation) — начните формирование унифицированной записи WAL для заданного отношения.

  2. page = GenericXLogRegisterBuffer(state, buffer, flags) — зарегистрируйте буфер, который будет изменён текущей унифицированной записью WAL. Эта функция возвращает указатель на временную копию страницы буфера, в которой должны производиться изменения. (Модифицировать непосредственно содержимое буфера нельзя.) В третьем аргументе передаётся битовая маска флагов, применимых к этой операции. В настоящее время флаг только один — GENERIC_XLOG_FULL_IMAGE, который показывает, что в запись WAL нужно включить образ всей страницы, а не только изменения. Обычно этот флаг должен устанавливаться, когда страница новая или полностью перезаписана. Вызов GenericXLogRegisterBuffer можно повторять, если фиксируемое в WAL действие изменяет несколько страниц.

  3. Примените изменения к образам страниц, полученным на предыдущем шаге.

  4. GenericXLogFinish(state) — завершите изменения в буферах и выдайте унифицированную запись WAL.

Формирование записи WAL можно прервать на любом шаге, вызвав GenericXLogAbort(state). При этом будут отменены все изменения, внесённые в копии образов страниц.

Используя механизм унифицированных записей WAL, необходимо учитывать следующее:

  • Модифицировать буферы напрямую нельзя! Все изменения должны производиться в копиях, полученных от функции GenericXLogRegisterBuffer(). Другими словами, код, формирующий унифицированные записи WAL, никогда не должен сам вызывать BufferGetPage(). Однако вызывающий код отвечает за закрепление/открепление и блокировку/разблокировку буферов в подходящие моменты времени. Исключительная блокировка каждого целевого буфера должна удерживаться от вызова GenericXLogRegisterBuffer() до GenericXLogFinish().

  • Регистрацию буферов (шаг 2) и модификацию образов страниц (шаг 3) можно свободно смешивать, оба этих шага можно повторять в любой последовательности. Но помните, что буферы должны регистрироваться в том же порядке, в каком для них должны получаться блокировки при воспроизведении.

  • Максимальное число буферов, которые можно зарегистрировать для унифицированной записи WAL, составляет MAX_GENERIC_XLOG_PAGES. При исчерпании этого лимита будет выдана ошибка.

  • Унифицированный тип WAL подразумевает, что страницы, подлежащие изменению, имеют стандартную структуру, в частности между pd_lower и pd_upper нет полезных данных.

  • Так как изменяются копии страниц буфера, GenericXLogStart() не начинает критическую секцию. Таким образом вы можете безопасно выделять память, выдать ошибку и т. п. между GenericXLogStart() и GenericXLogFinish(). Единственная фактическая критическая секция присутствует внутри GenericXLogFinish(). При выходе по ошибке так же не нужно заботиться о вызове GenericXLogAbort().

  • GenericXLogFinish() помечает буферы как грязные и устанавливает для них LSN. Вам делать явно это не нужно.

  • Для нежурналируемых отношений всё работает так же, за исключением того, что фактически запись в WAL не выдаётся. Таким образом, явно проверять, является ли отношение нежурналируемым, не требуется.

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

  • Если для регистрируемого буфера не задаётся GENERIC_XLOG_FULL_IMAGE, унифицированная запись WAL содержит различие между старым и новым образом страницы, которое вычисляется при побайтовом сравнении. Результат оказывается не очень компактным при перемещении данных в странице, но это может быть доработано в будущем.

18.7. Preventing Server Spoofing

While the server is running, it is not possible for a malicious user to take the place of the normal database server. However, when the server is down, it is possible for a local user to spoof the normal server by starting their own server. The spoof server could read passwords and queries sent by clients, but could not return any data because the PGDATA directory would still be secure because of directory permissions. Spoofing is possible because any user can start a database server; a client cannot identify an invalid server unless it is specially configured.

One way to prevent spoofing of local connections is to use a Unix domain socket directory (unix_socket_directories) that has write permission only for a trusted local user. This prevents a malicious user from creating their own socket file in that directory. If you are concerned that some applications might still reference /tmp for the socket file and hence be vulnerable to spoofing, during operating system startup create a symbolic link /tmp/.s.PGSQL.5432 that points to the relocated socket file. You also might need to modify your /tmp cleanup script to prevent removal of the symbolic link.

Another option for local connections is for clients to use requirepeer to specify the required owner of the server process connected to the socket.

To prevent spoofing on TCP connections, either use SSL certificates and make sure that clients check the server's certificate, or use GSSAPI encryption (or both, if they're on separate connections).

To prevent spoofing with SSL, the server must be configured to accept only hostssl connections (Section 20.1) and have SSL key and certificate files (Section 18.9). The TCP client must connect using sslmode=verify-ca or verify-full and have the appropriate root certificate file installed (Section 36.19.1).

To prevent spoofing with GSSAPI, the server must be configured to accept only hostgssenc connections (Section 20.1) and use gss authentication with them. The TCP client must connect using gssencmode=require.