Thread: Advice on implementing counters in postgreSQL

Advice on implementing counters in postgreSQL

From
"Marco Bizzarri"
Date:
Hi all.

I need to keep a numer of counters in my application; my counters are
currently stored in a table:

name | next_value | year


The counters must be progressive numbers with no holes in between
them, and they must restart from 1 every year. What I've done so far
is to access them while in SERIALIZABLE ISOLATION LEVEL, with the
following:

SELECT next_value FROM counters WHERE name = 'name' for update;
UPDATE counters SET next_value = next_value + 1 WHERE name = 'name';

of course, if I do not find the counter, I create it (which
automatically happens at the begin of a new year).

This seems to work to me, but I've two questions:

1) is there any scenario which I'm missing here and which could lead
me to troubles? Deadlocks excluded.


2) while this works, it has the unfortunate behaviour to cause
conflict between concurrent transactions; so, one of them has to be
restarted and redone from scratch. Is there a way to avoid this
behaviour? maybe with lock to tables?


Thanks you all for your attention

Regards
Marco

--
Marco Bizzarri
http://iliveinpisa.blogspot.com/

Re: Advice on implementing counters in postgreSQL

From
Craig Ringer
Date:
Marco Bizzarri wrote:
> Hi all.
>
> I need to keep a numer of counters in my application; my counters are
> currently stored in a table:
>
> name | next_value | year
>
>
> The counters must be progressive numbers with no holes in between
> them, and they must restart from 1 every year. What I've done so far
> is to access them while in SERIALIZABLE ISOLATION LEVEL, with the
> following:
>
> SELECT next_value FROM counters WHERE name = 'name' for update;
> UPDATE counters SET next_value = next_value + 1 WHERE name = 'name';

If you're using a sufficiently recent version of Pg you can use:

UPDATE counters
SET next_value = next_value + 1
WHERE name = 'name'
RETURNING next_value;

instead, which is slightly nicer. It'll return the *new* value of
`next_value', so you'd have to make a few tweaks.

--
Craig Ringer

Re: Advice on implementing counters in postgreSQL

From
"Marco Bizzarri"
Date:
Thanks for the advice, Craig.

I'm on a number of different PostgreSQL versions, ranging from 7.4 to
8.3, so I've to retain, where possible, compatibility with older
versions.

Is this better on a transaction/serialization point of view?

Regards
Marco

On Sat, Aug 2, 2008 at 10:19 AM, Craig Ringer
<craig@postnewspapers.com.au> wrote:
> Marco Bizzarri wrote:
>> Hi all.
>>
>> I need to keep a numer of counters in my application; my counters are
>> currently stored in a table:
>>
>> name | next_value | year
>>
>>
>> The counters must be progressive numbers with no holes in between
>> them, and they must restart from 1 every year. What I've done so far
>> is to access them while in SERIALIZABLE ISOLATION LEVEL, with the
>> following:
>>
>> SELECT next_value FROM counters WHERE name = 'name' for update;
>> UPDATE counters SET next_value = next_value + 1 WHERE name = 'name';
>
> If you're using a sufficiently recent version of Pg you can use:
>
> UPDATE counters
> SET next_value = next_value + 1
> WHERE name = 'name'
> RETURNING next_value;
>
> instead, which is slightly nicer. It'll return the *new* value of
> `next_value', so you'd have to make a few tweaks.
>
> --
> Craig Ringer
>



--
Marco Bizzarri
http://iliveinpisa.blogspot.com/

Re: Advice on implementing counters in postgreSQL

From
Craig Ringer
Date:
Marco Bizzarri wrote:
> Thanks for the advice, Craig.
>
> I'm on a number of different PostgreSQL versions, ranging from 7.4 to
> 8.3, so I've to retain, where possible, compatibility with older
> versions.
>
> Is this better on a transaction/serialization point of view?

As far as I know it's not significantly different, though I expect it'd
be somewhat more efficient. However, support for UPDATE ... RETURNING
was only added in 8.2 (or somewhere around there) anyway, so if you need
to work with old versions like 7.4 it's no good to you anyway.

I take it there's no way you can present the gapless identifiers at the
application level, leaving the actual tables with nice SEQUENCE
numbering? Or, alternately, insert them by timestamp/sequence (leaving
the user-visible ID null) then have another transaction come back and
assign them their gapless numeric identifiers in a single simple pass later?

You're really going to suffer on concurrency if you have to acquire
values from a gapless sequence as part of a transaction that does much
other work.

--
Craig Ringer

Re: Advice on implementing counters in postgreSQL

From
"Marco Bizzarri"
Date:
On Sat, Aug 2, 2008 at 11:04 AM, Craig Ringer
<craig@postnewspapers.com.au> wrote:
> Marco Bizzarri wrote:
>> Thanks for the advice, Craig.
>>
>> I'm on a number of different PostgreSQL versions, ranging from 7.4 to
>> 8.3, so I've to retain, where possible, compatibility with older
>> versions.
>>
>> Is this better on a transaction/serialization point of view?
>
> As far as I know it's not significantly different, though I expect it'd
> be somewhat more efficient. However, support for UPDATE ... RETURNING
> was only added in 8.2 (or somewhere around there) anyway, so if you need
> to work with old versions like 7.4 it's no good to you anyway.
>
> I take it there's no way you can present the gapless identifiers at the
> application level, leaving the actual tables with nice SEQUENCE
> numbering? Or, alternately, insert them by timestamp/sequence (leaving
> the user-visible ID null) then have another transaction come back and
> assign them their gapless numeric identifiers in a single simple pass later?


> You're really going to suffer on concurrency if you have to acquire
> values from a gapless sequence as part of a transaction that does much
> other work.

Well, the sequence must be gapless, because it is an implementation of
a law regarding how documents must be recorded when they are received
or sent in a public administration.

I can accept a "degraded" performance in this topic, considering that
usually, I've between 200 and 1000 documents recorded (i.e. numbered)
in a day, which is not such a great number.


However, I would avoid as much as possible serialization errors, which
would force me to repeat the transaction.

I'm experimenting with LOCK counters IN EXCLUSIVE MODE; it seems it is
able to rip me of thos serialization errors. Do you see any problems
in this?

Regards
Marco

--
Marco Bizzarri
http://iliveinpisa.blogspot.com/

Re: Advice on implementing counters in postgreSQL

From
David Fetter
Date:
On Sat, Aug 02, 2008 at 09:23:31AM +0200, Marco Bizzarri wrote:
> Hi all.
>
> I need to keep a numer of counters in my application; my counters
> are currently stored in a table:
>
> name | next_value | year
>
> The counters must be progressive numbers with no holes in between
> them, and they must restart from 1 every year.

Here's a backward-compatible way to do this:

http://www.varlena.com/GeneralBits/130.php

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778  AIM: dfetter666  Yahoo!: dfetter
Skype: davidfetter      XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

Re: Advice on implementing counters in postgreSQL

From
Berend Tober
Date:
Marco Bizzarri wrote:
 > Hi all.
 >
 > I need to keep a numer of counters in my application; my
counters are
 > currently stored in a table:
 >
 > name | next_value | year
 >
 >
 > The counters must be progressive numbers with no holes in between
 > them, and they must restart from 1 every year. What I've done
so far
 > is to access them while in SERIALIZABLE ISOLATION LEVEL, with the
 > following:
 >
 > SELECT next_value FROM counters WHERE name = 'name' for update;
 > UPDATE counters SET next_value = next_value + 1 WHERE name =
'name';
 >...
 > 2) while this works, it has the unfortunate behaviour to cause
 > conflict between concurrent transactions; so, one of them has
to be
 > restarted and redone from scratch. Is there a way to avoid this
 > behaviour? maybe with lock to tables?


The way I understand the documentation at

"http://www.postgresql.org/docs/8.3/static/transaction-iso.html"

and

'http://www.postgresql.org/docs/current/static/explicit-locking.html',

you should not have to use the serial isolation level.

I would define the counter table so as to hold the last-used
value, rather that the "next" value, and then do the UPDATE first.

As a consequence, assuming all this happens within a transaction
  of course, the SELECT FOR UPDATE syntax is not required either
because the UPDATE will grab a lock on the row and block other
updates until the transaction is finished. That is, concurrency
is protected and you don't have to restart any transactions
because subsequent transactions will just wait until the first
one finishes due to nature of the lock automatically acquired by
the initial UPDATE statement.






Re: Advice on implementing counters in postgreSQL

From
"Marco Bizzarri"
Date:
On Sat, Aug 2, 2008 at 5:11 PM, David Fetter <david@fetter.org> wrote:
> On Sat, Aug 02, 2008 at 09:23:31AM +0200, Marco Bizzarri wrote:
>> Hi all.
>>
>> I need to keep a numer of counters in my application; my counters
>> are currently stored in a table:
>>
>> name | next_value | year
>>
>> The counters must be progressive numbers with no holes in between
>> them, and they must restart from 1 every year.
>
> Here's a backward-compatible way to do this:
>
> http://www.varlena.com/GeneralBits/130.php
>
> Cheers,
> David.
> --
> David Fetter <david@fetter.org> http://fetter.org/
> Phone: +1 415 235 3778  AIM: dfetter666  Yahoo!: dfetter
> Skype: davidfetter      XMPP: david.fetter@gmail.com
>
> Remember to vote!
> Consider donating to Postgres: http://www.postgresql.org/about/donate
>


David, thanks for pointing me to such a complete solution.

These work both on READ-COMMITTED and SERIALIZABLE isolation levels, am I right?

Regards
Marco


--
Marco Bizzarri
http://iliveinpisa.blogspot.com/

Re: Advice on implementing counters in postgreSQL

From
"Marco Bizzarri"
Date:
On Sun, Aug 3, 2008 at 1:40 AM, Berend Tober <btober@ct.metrocast.net> wrote:
>
>
> The way I understand the documentation at
>
> "http://www.postgresql.org/docs/8.3/static/transaction-iso.html"
>
> and
>
> 'http://www.postgresql.org/docs/current/static/explicit-locking.html',
>
> you should not have to use the serial isolation level.
>
> I would define the counter table so as to hold the last-used value, rather
> that the "next" value, and then do the UPDATE first.
>
> As a consequence, assuming all this happens within a transaction  of course,
> the SELECT FOR UPDATE syntax is not required either because the UPDATE will
> grab a lock on the row and block other updates until the transaction is
> finished. That is, concurrency is protected and you don't have to restart
> any transactions because subsequent transactions will just wait until the
> first one finishes due to nature of the lock automatically acquired by the
> initial UPDATE statement.
>

Yes, I'm considering moving away from serializable; the problem is
that I have to explore all the implications of this on my code. Up to
now, I wrote considering a serializable level, so I think I should do
quite a review to be sure about it.

Regards
Marco

--
Marco Bizzarri
http://iliveinpisa.blogspot.com/

Re: Advice on implementing counters in postgreSQL

From
"Scott Marlowe"
Date:
On Sun, Aug 3, 2008 at 1:50 AM, Marco Bizzarri <marco.bizzarri@gmail.com> wrote:
> On Sun, Aug 3, 2008 at 1:40 AM, Berend Tober <btober@ct.metrocast.net> wrote:
>>
>>
>> The way I understand the documentation at
>>
>> "http://www.postgresql.org/docs/8.3/static/transaction-iso.html"
>>
>> and
>>
>> 'http://www.postgresql.org/docs/current/static/explicit-locking.html',
>>
>> you should not have to use the serial isolation level.
>>
>> I would define the counter table so as to hold the last-used value, rather
>> that the "next" value, and then do the UPDATE first.
>>
>> As a consequence, assuming all this happens within a transaction  of course,
>> the SELECT FOR UPDATE syntax is not required either because the UPDATE will
>> grab a lock on the row and block other updates until the transaction is
>> finished. That is, concurrency is protected and you don't have to restart
>> any transactions because subsequent transactions will just wait until the
>> first one finishes due to nature of the lock automatically acquired by the
>> initial UPDATE statement.
>>
>
> Yes, I'm considering moving away from serializable; the problem is
> that I have to explore all the implications of this on my code. Up to
> now, I wrote considering a serializable level, so I think I should do
> quite a review to be sure about it.

A fairly simple test shows that you can do this in read committed:

S1: # show transaction_isolation;
 read committed

(setup a table for the value)
# create table t (i int);
# insert into t values (5);


S1: # begin;
S1: # update t set i=i+1;
S2: # update t set i=i+1;
(S2 now waits for S1)
S1: # select i from t;
 6
S1: # commit;
(S2 now can continue...)
S2: # select i from t;
 7
S2: # commit;