Привет
Помогите, пожалуйста, придумать логику для модуля perl асинхронных запросов к БД. Почти всё получилось сделать как задумал, но внезапно выявилась архитектурная неопределённость.
Модуль будет работать только с postgres с помощью фичей DBD::Pg (http://search.cpan.org/~turnstep/DBD-Pg-2.19.3/Pg.pm#Asynchronous_Queries) и фреймворка AnyEvent.
Примерный сценарий использования:
use mk::adb;
my $adb=new mk::adb(recv=>["dbi:Pg:dbname=disarmer;host=/run/postgresql/;"]);
my $str1=$adb->q("select 1,pg_sleep(1);");#какой-то долгий запрос, $str1 - на самом деле не строка, а объект promise
my $str2=$adb->q("select 2,pg_sleep(2);");
... #выполняем какие то еще задачи
say "Result 1=$str1, result 2=$str2"; #Возвращаем результаты из БД, при преобразовании в promise в строку возвращаются нужные результаты запросов в виде строки
Суть такова: модуль при инициализации инстанса подключается к postgres с помощью обычного DBI несколькими подключениями (из аргументов). Затем при создании запрос попадает в очередь объекта (метод q), и если есть незанятый коннект к базе, запрос сразу отправляется в БД с флагом PG_ASYNC, на соответствующий сокет вешается AnyEvent watcher, управление возвращается программе, вместе с будущим результатом запроса (future/promise модель). То есть создание запроса происходит практически мгновенно, без блокирования.
После выполнения запроса (по срабатыванию watcher) вызывается callback, получающий строки результата и обрабатывающий их с помощью переданной в методе q функции. Если запрос еще не закончен, но больше делать нечего, то при вызове метода promise->finalize, выполнение блокируется, до получения результатов по нужному запросу($sth->pg_result).
Вот такой код получился на данный момент: http://disarmer.ru/ln/?li, вполне работает как задумано, если только число запросов не превышает числа коннектов к базе. Иначе возникает проблема: например ставим в очередь 10 долгих запросов и пытаемся получить результат последнего, а он еще не начал выполняться. По задумке тут нужно блокироваться пока БД не ответит на все предыдущие запросы, но как это красиво сделать не понимаю.
Да, про опасность слушания сокета к БД знаю (может прийти не только результат запроса а какие нибудь NOTIFY). В этом случае случится возможно долгая блокировка, но вроде без использования NOTIFY такого не должно происходить.
Самый похожий из имеющихся на cpan: AnyEvent::Pg::Pool, но он использует какой-то свой неповторимый XS драйвер к БД, и его использование вызывает много варнингов.