Używam postgresa również do kolejki FIFO. Pierwotnie używałem ACCESS EXCLUSIVE, który daje poprawne wyniki przy wysokiej współbieżności, ale ma niefortunny efekt wzajemnego wykluczania się z pg_dump, który podczas wykonywania uzyskuje blokadę ACCESS SHARE. To powoduje, że moja funkcja next() blokuje się na bardzo długi czas (czas trwania pg_dump). To było nie do przyjęcia, ponieważ jesteśmy sklepem 24x7, a klientom nie podobał się czas martwy w kolejce w środku nocy.
Pomyślałem, że musi istnieć mniej restrykcyjna blokada, która nadal byłaby bezpieczna współbieżnie i nie byłaby blokowana podczas działania pg_dump. Moje wyszukiwanie doprowadziło mnie do tego posta SO.
Potem zrobiłem kilka badań.
Poniższe tryby są wystarczające dla funkcji kolejki FIFO NEXT(), która zaktualizuje status zadania z w kolejce do biegania bez żadnej współbieżności nie powiedzie się, a także nie blokuje się przed pg_dump:
SHARE UPDATE EXCLUSIVE
SHARE ROW EXCLUSIVE
EXCLUSIVE
Zapytanie:
begin;
lock table tx_test_queue in exclusive mode;
update
tx_test_queue
set
status='running'
where
job_id in (
select
job_id
from
tx_test_queue
where
status='queued'
order by
job_id asc
limit 1
)
returning job_id;
commit;
Wynik wygląda tak:
UPDATE 1
job_id
--------
98
(1 row)
Oto skrypt powłoki, który testuje wszystkie różne tryby blokady przy wysokiej współbieżności (30).
#!/bin/bash
# RESULTS, feel free to repro yourself
#
# noLock FAIL
# accessShare FAIL
# rowShare FAIL
# rowExclusive FAIL
# shareUpdateExclusive SUCCESS
# share FAIL+DEADLOCKS
# shareRowExclusive SUCCESS
# exclusive SUCCESS
# accessExclusive SUCCESS, but LOCKS against pg_dump
#config
strategy="exclusive"
db=postgres
dbuser=postgres
queuecount=100
concurrency=30
# code
psql84 -t -U $dbuser $db -c "create table tx_test_queue (job_id serial, status text);"
# empty queue
psql84 -t -U $dbuser $db -c "truncate tx_test_queue;";
echo "Simulating 10 second pg_dump with ACCESS SHARE"
psql84 -t -U $dbuser $db -c "lock table tx_test_queue in ACCESS SHARE mode; select pg_sleep(10); select 'pg_dump finished...'" &
echo "Starting workers..."
# queue $queuecount items
seq $queuecount | xargs -n 1 -P $concurrency -I {} psql84 -q -U $dbuser $db -c "insert into tx_test_queue (status) values ('queued');"
#psql84 -t -U $dbuser $db -c "select * from tx_test_queue order by job_id;"
# process $queuecount w/concurrency of $concurrency
case $strategy in
"noLock") strategySql="update tx_test_queue set status='running{}' where job_id in (select job_id from tx_test_queue where status='queued' order by job_id asc limit 1);";;
"accessShare") strategySql="lock table tx_test_queue in ACCESS SHARE mode; update tx_test_queue set status='running{}' where job_id in (select job_id from tx_test_queue where status='queued' order by job_id asc limit 1);";;
"rowShare") strategySql="lock table tx_test_queue in ROW SHARE mode; update tx_test_queue set status='running{}' where job_id in (select job_id from tx_test_queue where status='queued' order by job_id asc limit 1);";;
"rowExclusive") strategySql="lock table tx_test_queue in ROW EXCLUSIVE mode; update tx_test_queue set status='running{}' where job_id in (select job_id from tx_test_queue where status='queued' order by job_id asc limit 1);";;
"shareUpdateExclusive") strategySql="lock table tx_test_queue in SHARE UPDATE EXCLUSIVE mode; update tx_test_queue set status='running{}' where job_id in (select job_id from tx_test_queue where status='queued' order by job_id asc limit 1);";;
"share") strategySql="lock table tx_test_queue in SHARE mode; update tx_test_queue set status='running{}' where job_id in (select job_id from tx_test_queue where status='queued' order by job_id asc limit 1);";;
"shareRowExclusive") strategySql="lock table tx_test_queue in SHARE ROW EXCLUSIVE mode; update tx_test_queue set status='running{}' where job_id in (select job_id from tx_test_queue where status='queued' order by job_id asc limit 1);";;
"exclusive") strategySql="lock table tx_test_queue in EXCLUSIVE mode; update tx_test_queue set status='running{}' where job_id in (select job_id from tx_test_queue where status='queued' order by job_id asc limit 1);";;
"accessExclusive") strategySql="lock table tx_test_queue in ACCESS EXCLUSIVE mode; update tx_test_queue set status='running{}' where job_id in (select job_id from tx_test_queue where status='queued' order by job_id asc limit 1);";;
*) echo "Unknown strategy $strategy";;
esac
echo $strategySql
seq $queuecount | xargs -n 1 -P $concurrency -I {} psql84 -U $dbuser $db -c "$strategySql"
#psql84 -t -U $dbuser $db -c "select * from tx_test_queue order by job_id;"
psql84 -U $dbuser $db -c "select count(distinct(status)) as should_output_100 from tx_test_queue;"
psql84 -t -U $dbuser $db -c "drop table tx_test_queue;";
Kod jest tutaj również, jeśli chcesz edytować:https://gist.github.com/1083936
Aktualizuję moją aplikację, aby używała trybu EXCLUSIVE, ponieważ jest to najbardziej restrykcyjny tryb, który a) jest poprawny i b) nie powoduje konfliktu z pg_dump. Wybrałem najbardziej restrykcyjny, ponieważ wydaje się najmniej ryzykowny, jeśli chodzi o zmianę aplikacji z ACCESS EXCLUSIVE bez bycia super-ekspertem w blokowaniu postgres.
Czuję się całkiem dobrze z moim stanowiskiem testowym i ogólnymi pomysłami, które kryją się za odpowiedzią. Mam nadzieję, że udostępnienie tego pomoże rozwiązać ten problem innym.