Pull to refresh

Асинхронность в DBD::Pg

Reading time 2 min
Views 2.9K
Все мы привыкли работать с базой в стиле:
  1. выполнить запрос
  2. дождаться ответа
  3. продолжить выполнение


Но пока работает длинный запрос, мы в приложении можем выполнить что-то полезное. Не простаивать же процессорному времени.

Для PostgreSQL в DBD::Pg есть некоторое подобие асинхронности. И иногда оно таки нам помогает.

Вы можете продолжить выполнять приложение, не дожидаясь выполнения запроса. Включается это параметром pg_async к prepare-запросу:

use strict;
use warnings;
use Time::HiRes 'sleep';
use DBD::Pg ':async';

my $dbh = DBI->connect('dbi:Pg:dbname=postgres', 'postgres', '', {AutoCommit=>0,RaiseError=>1});

## Запустить длинный запрос
my $sth = $dbh->prepare("SELECT pg_sleep(?)", {pg_async => PG_ASYNC});
$sth->execute(5);

## Пока работает, мы можем что-то сделать
print "Your query is processing. Thanks for waiting\n";
check_on_the_kids();

while (!$dbh->pg_ready) {
check_on_the_kids();
## и подождём чуть-чуть
sleep 0.1;
}

print "The query has finished. Gathering results\n";
my $result = $sth->pg_result;
print "Result: $result\n";
my $info = $sth->fetchall_arrayref();


Для pg_async есть три константы:
  • PG_ASYNC — выполнение запроса в асинхронном режиме
  • PG_OLDQUERY_CANCEL — если в этот момент работал предыдущий запроса, то он отменяется
  • PG_OLDQUERY_WAIT — блокируемся для ожидания предыдущего запроса и только потом начинаем выполнять новый


Так же есть три вспомогательных метода —
  • pg_cancel — отменить запрос. Реально открывается ещё одно соединение, в котором посылается SELECT pg_cancel_backend(?);
  • pg_ready — возвращает true, если запрос выполнился.
  • pg_result — блокируется до момента выполнения запроса, после чего возвращает то же, что и ->execute в стандартном режиме.

Из минусов — нельзя установить callback на момент исполнения запроса. Нужно постоянно проверять.
Так же в один момент времени в одном соединением можно выполнять только один запрос. Но что мешает открыть десяток соединений и по очереди посылать в них запросы?)

Попробую рассказать о применениях этой технологии:
  • тяжёлые запросы + сложная логика. Равномерно загружаем свой сервер и сервер БД.
    while (){
    my $foo = compute_foo();#тяжёлая функция
    #блокируемся до тех пор, пока не выполнится предыдущий запрос
    $dbh->do('UPDATE stats SET foo=?', { pg_async => PG_ASYNC + PG_OLDQUERY_WAIT }, $foo);
    }
    
  • работа с несколькими серверами БД
    $first_dbh->do('DELETE FROM old_data', { pg_async => PG_ASYNC });
    $second_dbh->do('UPDATE new_data SET status=0', { pg_async => PG_ASYNC })
  • таймауты. вы можете убить запрос по таймауту, если он ещё не отработал
  • конкурентные запросы. посылаем один и тот же запрос двум серверам и отдаём данные с того сервера, который ответил быстрее.
  • запросы, результат выполнения которых вам абсолютно безразличен
Tags:
Hubs:
+18
Comments 24
Comments Comments 24

Articles