Обсуждение: fetching unique pins in a high-transaction environment...

Поиск
Список
Период
Сортировка

fetching unique pins in a high-transaction environment...

От
"Bobus"
Дата:
Hi,

I posted this question to the "general" forum, but then discovered this
one which I think is more appropriate.  Apologies for the cross-post.

We are in the process of porting an application from SQL Server to
PostgresQL.

We have a table which contains a bunch of prepaid PINs.  What is the
best way to fetch the next available unique pin from the table in a
high-traffic environment with lots of concurrent requests?

For example, our PINs table might look like this and contain thousands
of records.  (FYI, the PIN numbers are generated by a third party and
loaded into the table):

ID        PIN     USED_BY    DATE_USED
....
100     1864678198
101     7862517189
102     6356178381
....

10 users request a pin at the same time.  What is the easiest/best way
to ensure that the 10 users will get 10 unique pins, while eliminating
any waiting?

SQL Server supports the notion of a SELECT FOR UPDATE with a READPAST
hint which tells SQL Server to skip over locked rows instead of waiting
until the lock is lifted.  This guarantees a unique pin will be
acquired every time without hampering performance.

Is there any equivalent in Postgres?

Any help would be greatly appreciated...



Re: fetching unique pins in a high-transaction environment...

От
"Bobus"
Дата:
I think we've figured out a way to implement the equivalent of a
READPAST hint in a function.

The basic idea is to loop until we find the next available unlocked
row, using the lock_not_available exception to determine if the record
is locked or not.  Our early testing seems to indicate that this
solution will work, but we would love to hear about simpler and more
efficient ways to accomplish this.

Here's a simplified version of the function which illustrates the
principle:

CREATE OR REPLACE FUNCTION "getpin"() RETURNS varchar
as $$
DECLARE v_id integer := 0; v_pin varchar;
BEGIN LOOP   BEGIN     -- Find the first available PIN.     -- Note: we cannot lock down the row here since we need to
be    -- able to store the ID of the pin to implement the READPAST.     select id into v_id from pins where id > v_id
andstatus = 0     order by id limit 1;
 
     -- Exit if there are no PINs available.     IF NOT FOUND THEN       RAISE EXCEPTION 'no pins available';     END
IF;
     -- Lock down the PIN.  If another transaction beat us to it, we     -- trap the error (see below) and loop looking
forthe next     -- available pin.  If another transaction already updated the     -- status to 1 in between this select
andthe previous, then we     -- loop (see ELSE statement).     select pin into v_pin from pins where id = v_id and
status= 0     for update nowait;
 
     IF FOUND THEN       -- Update the PIN.  The status = 0 check is unnecessary,       -- but better safe than sorry.
    update pins set status = 1 where id = v_id and status = 0;
 
       -- I don't think this should ever happen.       IF NOT FOUND THEN           RAISE EXCEPTION 'this should never
happen';      END IF;
 
       RETURN v_pin;     ELSE       -- Somebody snuck in and updated/grabbed the pin.  Loop.     END IF;
   EXCEPTION WHEN lock_not_available THEN     -- Loop looking for the next available unlocked pin.   END; END LOOP;
END;
$$
language plpgsql;

Thanks...



Database recovery in postgres 7.2.4.

От
"Santosh"
Дата:
Hi All.

My setup is as follows:
OS: Sun Solaris 5.8.
Postgres: 7.2.4

I have very large database, which contain 15 tables and each table is
contain more than 10,00,000 records.

My application is parsing text data files and inserting records into
database.

When this process was running last night, machine was got down because
of power failure.

Today when I come back to office and I have compaired record count in
data files and in database and find that some records are missing in
database.

Then I have checked postgres log and found log similar to as follows:
=================================
DEBUG:  The DataBase system was not properly shut downAutomatic recovery is in progress...
DEBUG:  Redo starts at (0, 1064)
=================================

I have read some WAL related stuff on postgres site but not found any
solution to recover those uncommited records.

Can any one tell me how to recover those missing records?

Thanks,
Santosh



Re: fetching unique pins in a high-transaction environment...

От
Scott Marlowe
Дата:
On Sun, 2006-10-29 at 10:36, Bobus wrote:
> Hi,
> 
> I posted this question to the "general" forum, but then discovered this
> one which I think is more appropriate.  Apologies for the cross-post.
> 
> We are in the process of porting an application from SQL Server to
> PostgresQL.
> 
> We have a table which contains a bunch of prepaid PINs.  What is the
> best way to fetch the next available unique pin from the table in a
> high-traffic environment with lots of concurrent requests?
> 
> For example, our PINs table might look like this and contain thousands
> of records.  (FYI, the PIN numbers are generated by a third party and
> loaded into the table):
> 
> ID        PIN     USED_BY    DATE_USED
> ....
> 100     1864678198
> 101     7862517189
> 102     6356178381
> ....
> 
> 10 users request a pin at the same time.  What is the easiest/best way
> to ensure that the 10 users will get 10 unique pins, while eliminating
> any waiting?

Best practice, to me, is to do a couple things.  One, create a sequence
and set it to the first available pin number.  Let's say you have pins
available from the number 1 to 9999.  Create a default sequence, it'll
start on 1.  Then, select nextval('yourseqhere') and use that to fetch
the pin like so:

begin;
select nextval('yourseqhere'); --  store in a var
update pin set date_used=now() where id=$var and date_used IS NULL

If date_used is not null, then someone grabbed it from you.  Given that
we're grabbing them using a sequence, this is unlikely, but you never
know when things might go south.  

Otherwise you just reserved it.  Then grab it:

select pin from table where id=$var;
commit;

if a transaction fails, you might not use a pin, no big loss.  Better
than accidentally giving it out twice.

I'd wrap what I just wrote in a simple pl/pgsql script using security
definer and set the perms so ONLY the user defined function can get you
a new pin.


Re: fetching unique pins in a high-transaction environment...

От
Richard Broersma Jr
Дата:
> Best practice, to me, is to do a couple things.  One, create a sequence
> and set it to the first available pin number.  Let's say you have pins
> available from the number 1 to 9999.  Create a default sequence, it'll
> start on 1.  Then, select nextval('yourseqhere') and use that to fetch
> the pin like so:
> 
> begin;
> select nextval('yourseqhere'); --  store in a var
> update pin set date_used=now() where id=$var and date_used IS NULL
> 
> If date_used is not null, then someone grabbed it from you.  Given that
> we're grabbing them using a sequence, this is unlikely, but you never
> know when things might go south.  
> 
> Otherwise you just reserved it.  Then grab it:
> 
> select pin from table where id=$var;
> commit;
> 
> if a transaction fails, you might not use a pin, no big loss.  Better
> than accidentally giving it out twice.
> 
> I'd wrap what I just wrote in a simple pl/pgsql script using security
> definer and set the perms so ONLY the user defined function can get you
> a new pin.

It is my understanding that nexval and even currentval are safe across transactions or even user
sessions.  I was curious of the datatype for pin,  in the previous example I think that it was
defined as a varchar.  Perhaps casting the sequence to a varchar would be the finial step before
updating/inserting the records.

Regards,

Richard Broersma Jr. 


Re: Database recovery in postgres 7.2.4.

От
Scott Marlowe
Дата:
On Mon, 2006-10-30 at 04:25, Santosh wrote:
> Hi All.
> 
> My setup is as follows:
> OS: Sun Solaris 5.8.
> Postgres: 7.2.4

Just so you know, 7.2 is ancient. You should, at a minimum be running
the latest 7.2 release, 7.2.8.  You should really look into upgrading to
a later version as soon as possible.

> I have very large database, which contain 15 tables and each table is
> contain more than 10,00,000 records.
> 
> My application is parsing text data files and inserting records into
> database.
> 
> When this process was running last night, machine was got down because
> of power failure.
> 
> Today when I come back to office and I have compaired record count in
> data files and in database and find that some records are missing in
> database.

You may have lost data.  Hard to say from what you've told us.  How did
you determine that records are missing?

> Then I have checked postgres log and found log similar to as follows:
> =================================
> DEBUG:  The DataBase system was not properly shut down
>     Automatic recovery is in progress...
> DEBUG:  Redo starts at (0, 1064)
> =================================

This is normal.  It's the messages after this you need to worry about. 
Did the logs say anything else after this?

> I have read some WAL related stuff on postgres site but not found any
> solution to recover those uncommited records.

If the records were committed, then they'd get put into the db by the
wal recovery process.  If the hardware (i.e. the hard drive and its
controller) wasn't lying about fsync.  If the hardware was lying, you
need to look into that (hint, IDE and many SATA drives lie about fsync)

> Can any one tell me how to recover those missing records?

there are some older tools laying about, like pgfsck that might help. 
I'd ask on admin or another list.  SQL isn't really the list for admin /
system problems...


Re: fetching unique pins in a high-transaction environment...

От
Scott Marlowe
Дата:
On Mon, 2006-11-06 at 14:04, Richard Broersma Jr wrote:
> > Best practice, to me, is to do a couple things.  One, create a sequence
> > and set it to the first available pin number.  Let's say you have pins
> > available from the number 1 to 9999.  Create a default sequence, it'll
> > start on 1.  Then, select nextval('yourseqhere') and use that to fetch
> > the pin like so:
> > 
> > begin;
> > select nextval('yourseqhere'); --  store in a var
> > update pin set date_used=now() where id=$var and date_used IS NULL
> > 
> > If date_used is not null, then someone grabbed it from you.  Given that
> > we're grabbing them using a sequence, this is unlikely, but you never
> > know when things might go south.  
> > 
> > Otherwise you just reserved it.  Then grab it:
> > 
> > select pin from table where id=$var;
> > commit;
> > 
> > if a transaction fails, you might not use a pin, no big loss.  Better
> > than accidentally giving it out twice.
> > 
> > I'd wrap what I just wrote in a simple pl/pgsql script using security
> > definer and set the perms so ONLY the user defined function can get you
> > a new pin.
> 
> It is my understanding that nexval and even currentval are safe across transactions or even user
> sessions.  I was curious of the datatype for pin,  in the previous example I think that it was
> defined as a varchar.  Perhaps casting the sequence to a varchar would be the finial step before
> updating/inserting the records.

Well, in the original, he had an id field to go with the pin, so I
assumed that he was generating them ahead of time in that format.  so,
given an id number that increments to accompany the pins, you can be
sure that by using a sequence you'll never accidentally grab the same
pin twice.

By wrapping the logic in a pl/pgsql function and using sec definer, you
can be sure that some bug in your app logic can give you the same pin
twice by working around your well thought out rules of how to get a new
one.  

Note that I was using the date_used field as a kind of "taken" marker. 
If it was set, then there was an error, and you should NOT do the select
pin from table where id=$var but in fact get a new sequence number and
try again.  Or error out.