Re: Serialization, Locking...implement processing Queue with a table - Mailing list pgsql-general

From D. Dante Lorenso
Subject Re: Serialization, Locking...implement processing Queue with a table
Date
Msg-id 086b01c319df$8b7507d0$1564a8c0@ROMULUS
Whole thread Raw
In response to Serialization, Locking...implement processing Queue with a table  ("D. Dante Lorenso" <dante@lorenso.com>)
Responses Re: Serialization, Locking...implement processing Queue with a table
Re: Serialization, Locking...implement processing Queue with a table
List pgsql-general
> (i)  no jobs are pending.  Commit your transaction,
> >>sleep for an appropriate delay period, and try again.
> (ii)  The row has been reserved by another transaction running at the
> same time.  In this case, restart at SELECT FOR UPDATE.  You can stay
> in the same transaction.  And you don't need to sleep (this has
> already happened while SELECT FOR UPDATE was blocked), unless there
> are lots of job processors in which case it might be advisible to
> sleep for a short random time.
>
> How do you distinguish between (i) and (ii)?  Just do
> SELECT job_id FROM job_table
>     WHERE processor_id = 0 LIMIT 1;
> If this returns 0 rows, you have (i).
> If it returns one row, you have (ii).

You can't do it this way because if this select IS successful,
you'll still need to LOCK the row for the update.  Any way you
try, you're in a race condition whereby another running thread
may lock and/or update that row after you select.  Remember, we
are in READ COMMITTED mode.  Without a lock on the row or a table,
your chances are that the row will be updated out from under you.

Here is a process that I've come up with that locks a bogus table
for me to guarantee that only one process has the right to enter
the stored procedure:

---------- 8< --------------------
BEGIN TRANSACTION;
LOCK TABLE proc_lock IN EXCLUSIVE MODE;
SELECT proc($process_id) AS RESULT;
COMMIT;
---------- 8< --------------------

If I wrap my stored procedure calls with a call like this, I can
guarantee that only one process gets INSIDE the procedure at a
time.  The "proc_lock" table can be any table and for my cases,
I use a table named the same as the table I'm going to be operating
on with _lock added to the end.  This way I don't have to actually
lock a table which is working for other selects by other apps that
don't care about concurrency locks for queue processing.

Now, my stored procedure can do any logic it wants and doesn't
have to worry about concurrency.  I tried to put the 'LOCK TABLE'
statement as the first line in the PL/PGSQL procedure, but it
didn't seem to work there.

    ex:

---------- 8< --------------------
CREATE OR REPLACE FUNCTION reserve_next_queued (bigint)
RETURNS bigint AS'
DECLARE
    processor_id ALIAS FOR $1;
    my_reserved_id BIGINT;
BEGIN
    LOCK TABLE proc_lock IN EXCLUSIVE MODE;
    ...
    RETURN (my_reserved_id);
END;
'LANGUAGE 'plpgsql';
---------- 8< --------------------

Apparently locks work in the stored proc but READ COMMITTED
does not?  For me, it looks like once the procedure has been
entered postgres taken a snapshot of the state of the database
BEFORE the procedure was called.  So, the lock blocks as it
should, but the subsequent select will see the OLD state and
not the updated COMMIT from the rows that were just updated by
the other thread that held the lock.

In other words, it looks like THIS is happening with
app (1) and app (2) trying the same stored proc call:

    (1) begin transaction
    (2) begin transaction
    (1) call PL/PGSQL function
    (2) call PL/PGSQL function
    (1) LOCK table <- no wait, lock granted immediately
    (2) LOCK table <- lock already held BLOCKED
    (1) process  PL/PGSQL function
    (1) exit PL/PGSQL function
    (1) UNLOCK table
    (2) LOCK table <- lock granted
    (2) process  PL/PGSQL function
    (1) COMMIT;
    (2) exit PL/PGSQL function
    (2) COMMIT;

Is that possible?  Can it bee that the lock is being freed
before the COMMIT is called?  Or is the procedure somehow
not allowing my other apps to see the committed data
immediately?

Pulling this lock out of the PL/PGSQL function to the calling
transaction wrapper worked, though.

In the future, I'd like to see PL/PGSQL allow me to embed
transactions stuff like that.  For now, I think my current
solution is clean enough to use without having to write try/fail
routines.  This method trys, waits, then succeeds.  It'd have to
WAIT that long anyhow, but this this approach, the wait is handled
but PostgreSQL and not my app.

I'm interested in knowing if anyone sees a FLAW with this design.
From my tests, I have code that works exactly as designed but not
as cleanly as I'd like.  What are your thoughts?

Dante

D. Dante Lorenso
dante@direct2prospect.com
972-333-4139



pgsql-general by date:

Previous
From: "Jay O'Connor"
Date:
Subject: COPY versus INSERT
Next
From: Joseph Shraibman
Date:
Subject: Re: fomatting an interval (resend)