Re: Multiple Concurrent Updates of Shared Resource Counter - Mailing list pgsql-performance

From Robert Klemme
Subject Re: Multiple Concurrent Updates of Shared Resource Counter
Date
Msg-id CAM9pMnPW6Q4QAY93BqRLoUJKYd69+OBBdSh_3RKTWUiE7OTErQ@mail.gmail.com
Whole thread Raw
In response to Multiple Concurrent Updates of Shared Resource Counter  (Nir Zilberman <nirz@checkpoint.com>)
List pgsql-performance
On Thu, Jun 7, 2012 at 9:53 AM, Nir Zilberman <nirz@checkpoint.com> wrote:
> We are handling multiple concurrent clients connecting to our system -
> trying to get a license seat (each license has an initial capacity of
> seats).
> We have a table which keeps count of the acquired seats for each license.
> When a client tries to acquire a seat we first make sure that the number of
> acquired seats is less than the license capacity.
> We then increase the number of acquired seats by 1.
>
> Our main problem here is with the acquired seats table.
> It is actually a shared resource which needs to be updated concurrently by
> multiple transactions.
>
> When multiple transactions are running concurrently - each transaction takes
> a long time to complete because it waits on the lock for the shared resource
> table.
>
> Any suggestions for better implementation/design of this feature would be
> much appreciated.

Well, there are the usual suspects for lock contention

1. Reduce time a lock needs to be held.
2. Increase granularity of locking.

ad 1)

It sounds as if you need two statements for check and increase.  That
can easily be done with a single statement if you use check
constraints.  Example:

$ psql -ef seats.sql
drop table licenses;
DROP TABLE
create table licenses (
  name varchar(200) primary key,
  max_seats integer not null check ( max_seats >= 0 ),
  current_seats integer not null default 0 check ( current_seats >= 0
and current_seats <= max_seats )
);
psql:seats.sql:6: NOTICE:  CREATE TABLE / PRIMARY KEY will create
implicit index "licenses_pkey" for table "licenses"
CREATE TABLE
insert into licenses (name, max_seats) values ('foo', 4);
INSERT 0 1
update licenses set current_seats = current_seats + 1 where name = 'foo';
UPDATE 1
update licenses set current_seats = current_seats + 1 where name = 'foo';
UPDATE 1
update licenses set current_seats = current_seats + 1 where name = 'foo';
UPDATE 1
update licenses set current_seats = current_seats + 1 where name = 'foo';
UPDATE 1
update licenses set current_seats = current_seats + 1 where name = 'foo';
psql:seats.sql:12: ERROR:  new row for relation "licenses" violates
check constraint "licenses_check"
update licenses set current_seats = current_seats - 1 where name = 'foo';
UPDATE 1

The increase will fail and you can react on that.  Another scheme is to use

update licenses set current_seats = current_seats + 1
where name = 'foo' and current_seats < max_seats;

and check how many rows where changed.

If however your transaction covers increase of used license seat
count, other work and finally decrease used license seat count you
need to change your transaction handling.  You rather want three TX:

start TX
update licenses set current_seats = current_seats + 1 where name = 'foo';
commit

if OK
  start TX
  main work
  commit / rollback

  start TX
  update licenses set current_seats = current_seats - 1 where name = 'foo';
  commit
end

ad 2)
At the moment I don't see a mechanism how that could be achieved in
your case.  Distribution of counters of a single license across
multiple rows and checking via SUM(current_seats) is not concurrency
safe because of MVCC.

Generally checking licenses via a relational database does neither
seem very robust nor secure.  As long as someone has administrative
access to the database or regular access to the particular database
limits and counts can be arbitrarily manipulated.  License servers I
have seen usually work by managing seats in memory and counting
license usage via network connections.  That has the advantage that
the OS quite reliably informs the license server if a client dies.

Kind regards

robert

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

pgsql-performance by date:

Previous
From: Nir Zilberman
Date:
Subject: Multiple Concurrent Updates of Shared Resource Counter
Next
From: "Albe Laurenz"
Date:
Subject: Tablespaces and query planning