31.5. Режим конвейера#

31.5. Режим конвейера

31.5. Режим конвейера #

libpq режим конвейера позволяет приложениям отправлять запрос без необходимости читать результат предыдущего отправленного запроса. Используя режим конвейера, клиент будет меньше ждать сервера, так как несколько запросов/результатов могут быть отправлены/получены в одной сетевой транзакции.

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

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

В то время как конвейерный API libpq был введен в Tantor BE 14, это клиентская функция, которая не требует специальной поддержки сервера и работает на любом сервере, поддерживающем расширенный протокол запросов v3. Дополнительную информацию см. в разделе Раздел 52.2.4.

31.5.1. Использование режима конвейера #

Для выполнения конвейеров приложение должно переключить соединение в режим конвейера, что делается с помощью PQenterPipelineMode. PQpipelineStatus можно использовать для проверки активности режима конвейера. В режиме конвейера разрешены только асинхронные операции, использующие расширенный протокол запросов, запросы, содержащие несколько SQL-команд, запрещены, а также COPY. Использование синхронных функций выполнения команд таких как PQfn, PQexec, PQexecParams, PQprepare, PQexecPrepared, PQdescribePrepared, PQdescribePortal, является ошибочным состоянием. PQsendQuery также запрещено, потому что он использует простой протокол запросов. После того, как все отправленные команды будут обработаны и результат конвейера будет получен, приложение может вернуться в непрерывный режим с помощью PQexitPipelineMode.

Примечание

Лучше всего использовать режим конвейера с libpq в неблокирующем режиме. Если используется в блокирующем режиме, возможна блокировка клиента/сервера. [15]

31.5.1.1. Выполнение запросов #

После входа в режим конвейера приложение отправляет запросы с использованием функции PQsendQueryParams или ее подготовленного аналога PQsendQueryPrepared. Эти запросы ставятся в очередь на стороне клиента до тех пор, пока они не будут отправлены на сервер; это происходит при использовании функции PQpipelineSync для установки точки синхронизации в конвейере или при вызове функции PQflush. Функции PQsendPrepare, PQsendDescribePrepared и PQsendDescribePortal также работают в режиме конвейера. Обработка результатов описана ниже.

Сервер выполняет операторы и возвращает результаты в том порядке, в котором они были отправлены клиентом. Сервер начинает выполнение команд в конвейере немедленно, не дожидаясь окончания конвейера. Обратите внимание, что результаты буферизуются на стороне сервера; сервер сбрасывает этот буфер при установлении точки синхронизации с функцией PQpipelineSync или при вызове функции PQsendFlushRequest. Если какой-либо оператор сталкивается с ошибкой, сервер прерывает текущую транзакцию и не выполняет последующие команды в очереди до следующей точки синхронизации; для каждой такой команды генерируется результат PGRES_PIPELINE_ABORTED. (Это остается верным даже в том случае, если команды в конвейере откатывают транзакцию). Обработка запроса возобновляется после точки синхронизации.

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

31.5.1.2. Результаты обработки #

Для обработки результата одного запроса в конвейере приложение вызывает функцию PQgetResult повторно и обрабатывает каждый результат, пока PQgetResult не вернет null. Результат следующего запроса в конвейере может быть получен снова с помощью функции PQgetResult, и цикл повторяется. Приложение обрабатывает отдельные результаты запросов как обычно. Когда результаты всех запросов в конвейере были получены, PQgetResult возвращает результат, содержащий значение статуса PGRES_PIPELINE_SYNC.

Клиент может выбрать отложенную обработку результата до тех пор, пока не будет отправлена полная конвейерная линия, или вклинить это в отправку дополнительных запросов в конвейере; см. Раздел 31.5.1.4.

Для входа в режим одиночной строки вызовите функцию PQsetSingleRowMode перед получением результатов с помощью функции PQgetResult. Этот режим выбора действует только для текущего выполняемого запроса. Дополнительную информацию о использовании функции PQsetSingleRowMode см. в разделе Раздел 31.6.

PQgetResult ведет себя так же, как и для обычной асинхронной обработки, за исключением того, что он может содержать новые типы PGresult PGRES_PIPELINE_SYNC и PGRES_PIPELINE_ABORTED. PGRES_PIPELINE_SYNC сообщается ровно один раз для каждого PQpipelineSync в соответствующей точке в конвейере. PGRES_PIPELINE_ABORTED генерируется вместо обычного результата запроса для первой ошибки и всех последующих результатов до следующего PGRES_PIPELINE_SYNC; см. Раздел 31.5.1.3.

{PQisBusy, PQconsumeInput, и т.д. работают как обычно при обработке результатов конвейера. В частности, вызов PQisBusy в середине конвейера возвращает 0, если результаты всех до сих пор выпущенных запросов были обработаны.

libpq не предоставляет приложению никакой информации о текущем выполняемом запросе (за исключением того, что PQgetResult возвращает null, чтобы указать, что мы начинаем возвращать результаты следующего запроса). Приложение должно отслеживать порядок, в котором оно отправляет запросы, чтобы связать их с соответствующими результатами. Обычно приложения используют конечный автомат или очередь FIFO для этого.

31.5.1.3. Обработка ошибок #

С точки зрения клиента, после того, как функция PQresultStatus возвращает значение PGRES_FATAL_ERROR, конвейер помечается как прерванный. Функция PQresultStatus будет сообщать о результате PGRES_PIPELINE_ABORTED для каждой оставшейся операции в прерванном конвейере. Результат для функции PQpipelineSync сообщается как PGRES_PIPELINE_SYNC, чтобы сигнализировать о завершении прерванного конвейера и возобновлении обычной обработки результатов.

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

Если конвейер использовал неявную транзакцию, то операции, которые уже выполнены, откатываются, а операции, которые были запланированы после неудачной операции, полностью пропускаются. То же самое происходит, если конвейер запускается и коммитит одну явную транзакцию (т.е. первый оператор - BEGIN, а последний - COMMIT), за исключением того, что сессия остается в состоянии прерванной транзакции в конце конвейера. Если конвейер содержит несколько явных транзакций, все транзакции, которые были подтверждены до ошибки, остаются зафиксированными, текущая выполняющаяся транзакция прерывается, и все последующие операции полностью пропускаются, включая последующие транзакции. Если точка синхронизации конвейера происходит с блоком явной транзакции в состоянии прерванной, следующий конвейер немедленно становится прерванным, если следующая команда не переводит транзакцию в нормальный режим с помощью ROLLBACK.

Примечание

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

31.5.1.4. Обработка результатов и диспетчеризация запросов взаимно переплетены #

Чтобы избежать блокировок в больших конвейерах, клиент должен быть структурирован вокруг неблокирующего цикла событий, используя средства операционной системы, такие как select, poll, WaitForMultipleObjectEx и т. д.

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

Пример использования функции select() и простого конечного автомата для отслеживания отправленной и полученной работы находится в файле src/test/modules/libpq_pipeline/libpq_pipeline.c в дистрибутиве исходного кода PostgreSQL.

31.5.2. Функции, связанные с режимом конвейера #

PQpipelineStatus #

Возвращает текущий статус режима конвейера соединения libpq.

PGpipelineStatus PQpipelineStatus(const PGconn *conn);

PQpipelineStatus может возвращать одно из следующих значений:

PQ_PIPELINE_ON

Соединение libpq находится в режиме конвейера.

PQ_PIPELINE_OFF

Соединение libpq не находится в режиме pipeline.

PQ_PIPELINE_ABORTED

Соединение libpq находится в режиме конвейера, и произошла ошибка при обработке текущего конвейера. Флаг "отменено" сбрасывается, когда функция PQgetResult возвращает результат типа PGRES_PIPELINE_SYNC.

PQenterPipelineMode #

Заставляет соединение перейти в режим конвейера, если оно в настоящее время находится в режиме простоя или уже находится в режиме конвейера.

int PQenterPipelineMode(PGconn *conn);

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

PQexitPipelineMode #

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

int PQexitPipelineMode(PGconn *conn);

Возвращает 1 в случае успеха. Возвращает 1 и не выполняет никаких действий, если не находится в режиме конвейера. Если текущий оператор еще не завершил обработку или функция PQgetResult не была вызвана для сбора результатов всех ранее отправленных запросов, возвращает 0 (в этом случае используйте PQerrorMessage для получения дополнительной информации об ошибке).

PQpipelineSync #

Отмечает точку синхронизации в конвейере, отправляя сообщение синхронизации и очищая буфер отправки. Это служит разделителем неявной транзакции и точкой восстановления после ошибки; см. Раздел 31.5.1.3.

int PQpipelineSync(PGconn *conn);

Возвращает 1 в случае успеха. Возвращает 0, если соединение не находится в режиме конвейера или отправка сообщения sync не удалась.

PQsendFlushRequest #

Отправляет запрос серверу для сброса его буфера вывода.

int PQsendFlushRequest(PGconn *conn);

Возвращает 1 в случае успеха. Возвращает 0 в случае любой ошибки.

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

31.5.3. Когда использовать режим конвейера #

Подобно асинхронному режиму запросов, использование режима конвейера не влечет за собой увеличения издержек на производительность. Однако это увеличивает сложность клиентского приложения, и требуется дополнительная осторожность, чтобы предотвратить блокировки клиента/сервера. Однако режим конвейера может предложить значительное улучшение производительности в обмен на увеличение использования памяти за счет сохранения состояния на более длительное время.

Режим конвейера наиболее полезен, когда сервер находится на большом расстоянии, то есть задержка в сети (время отклика ping) высокая, а также когда выполняется много маленьких операций одна за другой в быстром темпе. Обычно нет большой выгоды в использовании команд конвейера, когда каждый запрос занимает много кратное время обмена данными между клиентом и сервером. Операция с 100 запросами, запущенная на сервере, находящемся на расстоянии 300 мс времени обмена данными, займет 30 секунд только на задержку в сети без использования конвейера; с использованием конвейера она может занять всего 0,3 секунды ожидания результатов от сервера.

Используйте команды с конвейером, когда ваше приложение выполняет множество маленьких операций INSERT, UPDATE и DELETE, которые не могут быть легко преобразованы в операции над множествами или в операцию COPY.

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

BEGIN;
SELECT x FROM mytable WHERE id = 42 FOR UPDATE;
-- result: x=2
-- client adds 1 to x:
UPDATE mytable SET x = 3 WHERE id = 42;
COMMIT;

может быть сделано намного более эффективно с помощью:

UPDATE mytable SET x = x + 1 WHERE id = 42;

Pipelining менее полезен и более сложен, когда одна конвейер содержит несколько транзакций (см. Раздел 31.5.1.3).



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