libpq конвейерный режим позволяет приложениям отправлять запрос, не дожидаясь завершения чтения результата предыдущего отправленного запроса. Благодаря преимуществам конвейерного режима сокращается время ожидания сервера клиентом, так как передача нескольких запросов и получение результатов могут осуществляться в рамках одной сетевой транзакции.
Несмотря на то что конвейерный режим обеспечивает значительный прирост производительности, реализация клиентов с его поддержкой является более сложной задачей, так как требует управления очередью исходящих запросов и сопоставления полученных результатов с соответствующими запросами в очереди.
Режим конвейеризации также обычно потребляет больше памяти как на стороне клиента, так и на стороне сервера, хотя тщательное и активное управление очередью отправки и получения может минимизировать данное воздействие. Это условие соблюдается независимо от того, находится ли соединение в блокирующем или неблокирующем режиме.
Несмотря на то, что libpqинтерфейс программирования приложений (API) конвейерной обработки был представлен в Digital Q.DataBase версии 14, это клиентская функциональность, которая не требует специальной поддержки со стороны сервера и работает с любым сервером, поддерживающим расширенный протокол запросов версии 3. Для получения дополнительной информации см. Раздел 7.4.2.4.
Для использования конвейеров приложение должно переключить соединение
в режим конвейеризации,
что выполняется с помощью функции PQenterPipelineMode.
PQpipelineStatus может использоваться
для проверки того, активен ли режим конвейеризации.
В режиме конвейеризации разрешены только асинхронные операции
, использующие расширенный протокол запросов, разрешены, тогда как командные строки, содержащие несколько SQL-команд, не допускаются, равно как и COPY.
Использование функций синхронного выполнения команд,
таких как PQfn,
PQexec,
PQexecParams,
PQprepare,
PQexecPrepared,
PQdescribePrepared,
PQdescribePortal,
PQclosePrepared,
PQclosePortal,
является состоянием ошибки.
PQsendQuery также запрещена, поскольку в ней используется протокол простых запросов. После обработки результатов всех отправленных команд и получения завершающего результата конвейера приложение может вернуться в режим без конвейеризации с помощью PQexitPipelineMode.
Режим конвейеризации лучше всего использовать в libpq в неблокирующем режиме. При использовании в блокирующем режиме возможно возникновение взаимной блокировки между клиентом и сервером. [15]
После перехода в режим конвейеризации приложение отправляет запросы с помощью функции
PQsendQueryParams
или её аналога для подготовленных запросов
PQsendQueryPrepared. Данные запросы помещаются в очередь на стороне клиента до их отправки на сервер; это происходит в тех случаях, когда PQpipelineSync используется для создания точки синхронизации в конвейере, или когда PQflush вызывается.
Функции PQsendPrepare,
PQsendDescribePrepared,
PQsendDescribePortal,
PQsendClosePreparedи
PQsendClosePortal также поддерживают работу в конвейерном режиме.
Порядок обработки результатов описан ниже.
Сервер выполняет операторы и возвращает результаты в том же порядке, в котором их отправляет клиент. Сервер начинает выполнение команд в конвейере немедленно, не ожидая его завершения. Следует учитывать, что на стороне сервера результаты буферизуются; сервер очищает
данный буфер при установке точки синхронизации с помощью функции
PQpipelineSync или
PQsendPipelineSync, а также при вызове функции
PQsendFlushRequest вызывается. Если при выполнении какого-либо оператора возникает ошибка, сервер прерывает текущую транзакцию и не выполняет последующие команды в очереди до достижения следующей точки синхронизации; PGRES_PIPELINE_ABORTED для каждой такой команды формируется результат. (Это правило остается в силе, даже если команды в конвейере вызовут откат транзакции.) Обработка запросов возобновляется после точки синхронизации.
Допускается зависимость одной операции от результатов предыдущей; например, один запрос может определять таблицу, используемую следующим запросом в том же конвейере. Аналогичным образом приложение может создать именованный подготовленный оператор и выполнить его в последующих операторах того же конвейера.
Чтобы обработать результат одного запроса в конвейере, приложение многократно вызывает функцию
PQgetResult и обрабатывает каждый результат
до тех пор, пока PQgetResult не вернет значение NULL.
Затем результат следующего запроса в конвейере может быть получен путем повторного вызова функции
PQgetResult после чего цикл повторяется.
Результаты отдельных операторов приложение обрабатывает в обычном режиме.
Когда результаты всех запросов в конвейере будут
возвращены, PQgetResult возвращает результат, содержащий значение статуса PGRES_PIPELINE_SYNC
Клиент может выбрать вариант с отложенной обработкой результатов до завершения отправки всего конвейера или совмещать её с отправкой последующих запросов; см. Раздел 4.1.5.1.4.
PQgetResult работает так же, как и при обычной
асинхронной обработке, за исключением возможности появления новых
PGresult типов PGRES_PIPELINE_SYNC
и PGRES_PIPELINE_ABORTED.
PGRES_PIPELINE_SYNC сообщается ровно один раз для каждого
PQpipelineSync или
PQsendPipelineSync в соответствующей точке
конвейера.
PGRES_PIPELINE_ABORTED выдается вместо обычного
результата запроса при возникновении первой ошибки и для всех последующих результатов
до следующего PGRES_PIPELINE_SYNC;
см. Раздел 4.1.5.1.3.
PQisBusy, PQconsumeInput, и т. д.
работают в штатном режиме при обработке результатов конвейера. В частности,
вызов функции PQisBusy в середине конвейера
возвращает 0, если результаты всех отправленных на данный момент запросов уже
были получены.
libpq не предоставляет приложению никакой информации о текущем обрабатываемом запросе (за исключением того, что
PQgetResult возвращает значение NULL, указывая на начало
возврата результатов следующего запроса). Приложение должно самостоятельно отслеживать
порядок отправки запросов для их сопоставления с соответствующими
результатами.
Как правило, для этой цели в приложениях используется конечный автомат или очередь FIFO.
С точки зрения клиента, после того как PQresultStatus
возвращает значение PGRES_FATAL_ERROR,
конвейер помечается как прерванный.
PQresultStatus будет возвращать
PGRES_PIPELINE_ABORTED результат для каждой оставшейся в очереди
операции в прерванном конвейере. Результат для
PQpipelineSync или
PQsendPipelineSync возвращается как
PGRES_PIPELINE_SYNC для сигнализации о завершении прерванного конвейера
и возобновлении обычной обработки результатов.
Клиент должен обрабатывает результаты с помощью функции
PQgetResult в процессе восстановления после ошибки.
Если в конвейере использовалась неявная транзакция, то уже выполненные операции откатываются, а операции, находившиеся в очереди после сбойной операции, полностью пропускаются. Аналогичное поведение сохраняется, если в конвейере запускается и фиксируется одна явная транзакция (т. е. первым оператором является BEGIN а последним —
COMMIT), за исключением того, что по завершении работы конвейера сеанс остается в состоянии прерванной транзакции. Если конвейер содержит
несколько явных транзакций, то все транзакции,
зафиксированные до возникновения ошибки, остаются зафиксированными, текущая
выполняемая транзакция прерывается, а все последующие операции,
включая последующие транзакции, полностью пропускаются. Если точка синхронизации конвейера возникает в момент, когда явный блок транзакции находится в состоянии прерывания, выполнение следующего конвейера будет немедленно прекращено, если только следующая команда не переведет транзакцию в нормальный режим с помощью ROLLBACK.
Клиент не должен предполагать, что работа зафиксирована, когда он
отправляет её COMMIT — фиксация считается завершенной только при получении соответствующего результата. Поскольку ошибки поступают асинхронно, приложению необходимо иметь возможность возобновления работы с последнего полученного зафиксированное изменение и повторную отправку результатов выполненной после этого момента работы в случае возникновения ошибки.
Во избежание взаимоблокировок в больших конвейерах архитектуру клиента следует основывать на неблокирующем цикле событий с использованием таких средств операционной системы, как select, poll,
WaitForMultipleObjectEx, и т. д.
Как правило, клиентскому приложению следует поддерживать очередь задач, ожидающих отправки, и очередь задач, которые уже были отправлены, но результаты выполнения которых еще не обработаны. Когда сокет становится доступен для записи, следует передать на выполнение дополнительный объем задач. Когда сокет становится доступен для чтения, следует прочитать результаты и обработать их, сопоставляя их со следующей записью в соответствующей очереди результатов. Исходя из объема доступной памяти, результаты из сокета следует считывать часто: для чтения результатов нет необходимости ждать завершения работы конвейера. Область действия конвейеров должна ограничиваться логическими единицами работы, как правило (но не обязательно), одной транзакцией на конвейер. Нет необходимости выходить из режима конвейера и повторно входить в него между конвейерами или ожидать завершения одного конвейера перед отправкой следующего.
Пример использования select() и простого конечного автомата для отслеживания отправленных и полученных задач приведен в файле
src/test/modules/libpq_pipeline/libpq_pipeline.c
в составе дистрибутива исходных кодов PostgreSQL.
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 #Отмечает точку синхронизации в конвейере путем отправки сообщения синхронизации и принудительной очистки буфера передачи. Это служит разделителем неявной транзакции и механизмом восстановления после ошибки point; see Раздел 4.1.5.1.3.
int PQpipelineSync(PGconn *conn);
В случае успешного выполнения функция возвращает 1. Функция возвращает 0, если соединение не находится в режиме конвейеризации или если отправка сообщения синхронизации завершилась неудачно.
PQsendPipelineSync #Отмечает точку синхронизации в конвейере путем отправки сообщения синхронизации без очистки буфера отправки. Это служит разделителем неявной транзакции и механизмом восстановления после ошибки point; see Раздел 4.1.5.1.3.
int PQsendPipelineSync(PGconn *conn);
В случае успешного выполнения функция возвращает 1. Функция возвращает 0, если соединение не находится в
режиме конвейеризации или если отправка
сообщения синхронизации
завершилась неудачно.
Следует учитывать, что само сообщение не сбрасывается на сервер автоматически;
используйте функцию PQflush если это необходимо.
PQsendFlushRequest #Отправляет серверу запрос на очистку его выходного буфера.
int PQsendFlushRequest(PGconn *conn);
В случае успешного выполнения функция возвращает 1. Функция возвращает 0 при возникновении любой ошибки.
Сервер автоматически очищает свой выходной буфер в результате
PQpipelineSync вызова или
при любом запросе вне режима конвейеризации; данная функция полезна
для принудительной очистки сервером своего выходного буфера в режиме конвейеризации
без установления точки синхронизации.
Обратите внимание, что автоматически запрос на сервер не отправляется;
используйте функцию PQflush если это необходимо.
Как и при работе в асинхронном режиме запросов, при использовании конвейерного режима значительные накладные расходы на производительность отсутствуют. Конвейерный режим повышает сложность клиентского приложения и требует особой осторожности для предотвращения взаимных блокировок между клиентом и сервером, однако он может обеспечить значительное повышение производительности ценой повышенного потребления памяти из-за более длительного хранения состояний.
Конвейерный режим наиболее эффективен в тех случаях, когда сервер удален, то есть когда сетевая задержка («время круговой задержки») велика, а также при быстром последовательном выполнении множества мелких операций. Использование конвейерных команд обычно менее выгодно, если время выполнения каждого запроса многократно превышает время приема-передачи данных между клиентом и сервером. Выполнение операции из 100 операторов на сервере с временем приема-передачи 300 мс без конвейеризации заняло бы 30 секунд только за счет сетевых задержек; при использовании конвейеризации время ожидания результатов от сервера может сократиться до 0,3 с.
Конвейерные команды следует использовать в тех случаях, когда приложение выполняет множество мелких
INSERT, UPDATE и
DELETE операции, которые не могут быть легко преобразованы в операции над множествами или в COPY операцию.
Режим конвейерной обработки малоэффективен, если для формирования следующей операции приложению требуются сведения, полученные в результате выполнения предыдущей операции. В подобных ситуациях на стороне клиента необходимо вводить точку синхронизации и ожидать завершения полного цикла приема-передачи данных (round-trip) между клиентом и сервером для получения требуемых результатов. Однако структуру клиентского приложения зачастую можно изменить таким образом, чтобы обмен необходимой информацией происходил на стороне сервера. Особенно подходящими кандидатами для такой оптимизации являются циклы «чтение-изменение-запись» (read-modify-write); например:
BEGIN; SELECT x FROM mytable WHERE id = 42 FOR UPDATE; -- результат: x=2 -- клиент прибавляет 1 к x: UPDATE mytable SET x = 3 WHERE id = 42; COMMIT;
можно выполнить гораздо эффективнее с помощью выражения:
UPDATE mytable SET x = x + 1 WHERE id = 42;
Конвейерная обработка менее полезна и более сложна в тех случаях, когда один конвейер содержит несколько транзакций (см. Раздел 4.1.5.1.3).
[15] Клиент будет заблокирован при попытке отправить запросы серверу, в то время как сервер будет заблокирован при попытке отправить клиенту результаты запросов, которые им уже были обработаны. Это происходит только в том случае, если клиент отправляет количество запросов, достаточное для заполнения как своего выходного буфера, так и приемного буфера сервера до перехода к обработке входящих данных от сервера, однако точно предсказать момент наступления этого события сложно.