Thanks to visit codestin.com
Credit goes to postgrespro.ru

33.5. Конвейерный режим

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

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

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

Хотя конвейерный API в libpq появился с выходом PostgreSQL 14, это клиентская функциональность, которая не требует специальной поддержки на стороне сервера и работает с любым сервером, поддерживающем 3-ю версию расширенного протокола запросов. За дополнительными сведениями обратитесь к Подразделу 54.2.4.

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

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

Примечание

Конвейерный режим рекомендуется использовать при работе libpq в неблокирующем режиме. В блокирующем режиме возможны взаимоблокировки на уровне клиент-сервер. [15]

33.5.1.1. Отправка запросов

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

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

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

33.5.1.2. Обработка результатов

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

Клиент может отложить обработку результатов до тех пор, пока весь конвейер не будет отправлен, или чередовать её с отправкой дальнейших запросов в конвейере; см. Подраздел 33.5.1.4.

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

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

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

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

33.5.1.3. Обработка ошибок

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

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

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

Примечание

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

33.5.1.4. Чередование обработки результатов и отправки запросов

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

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

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

PQpipelineStatus

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

PGpipelineStatus PQpipelineStatus(const PGconn *conn);

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

PQ_PIPELINE_ON

Подключение libpq находится в конвейерном режиме.

PQ_PIPELINE_OFF

Подключение libpq не находится в конвейерном режиме.

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

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

int PQpipelineSync(PGconn *conn);

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

PQsendFlushRequest

Отправляет серверу команду сбросить его буфер вывода.

int PQsendFlushRequest(PGconn *conn);

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

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

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

Как и в случае с асинхронным режимом запросов, при использовании конвейерного режима нет значительных издержек производительности. Использование конвейерного режима увеличивает сложность клиентского приложения и требует дополнительной осторожности во избежание взаимоблокировок клиент-сервер, но может предложить значительное улучшение производительности в обмен на увеличение объёма используемой памяти из-за более длительного выхода из состояния.

Конвейерный режим наиболее полезен, когда сервер находится на большом расстоянии от клиента, т. е. когда сетевая задержка («ping time») велика, а также когда много небольших операций выполняются в быстрой последовательности. Как правило, использование конвейерных команд даёт меньше преимуществ, когда выполнение каждого запроса занимает в несколько раз больше времени, чем передача данных клиент-сервер и обратно. Операция из 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;

Конвейеризация менее полезна и более сложна, когда один конвейер содержит несколько транзакций (см. Подраздел 33.5.1.3).



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