By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
438,710 Members | 1,887 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 438,710 IT Pros & Developers. It's quick & easy.

Transaction Question

P: n/a
I have to convert an java web application currently using an Oracle DB
back end to one using a Postgres backend.

In Oracle much of the application logic is abstracted away from the java
middleware layer using stored procedures within the Oracle database. There
are certain features in Oracle that appear to be missing from Postgres
which are causing us some concern, and we wondered how much we're going to
have to butcher the backend and db stored procs.

Issue - nested transactions
=====

Oracle provides the #pragma autonomous hint which allows you to declare
that a procedure/function will run in its own transaction and which can be
committed regardless of the final commit/rollback state of the enclosing
transaction.

This is an issue for us because some procedures make use of a function
which issues a row level lock on a table (select ... for update) in order
to read and then update a counter, and which then commits to release the
lock. The nested function returns the new counter value on return. We
cannot use Sequence objects, because the counter is tied directly to the
record which contains it, and there are any number of these record types.

We have the function being called by many threads simultaneously, and if
the lock is only released at the end of the enclosing transaction, then
the subsequent calls after the first will block until the first completes.
In other words, although threads are making calls in parallel, they will
only run serially because of the bottleneck.

I have seen a note about using separate connections/threads to resolve
this issue. There is NO possibility of our java middleware using two
threads/connections to separate out the transaction as the idea is that
the java makes one call to the database, and it handles all concurrency
issues (beautifully) without us having to embed db specific code/logic in
the middleware.

Is there a simple/elegant solution to this problem? And is there a good
document on dealing with concurrency issues - I have read the manual for
7.4 and while it describes the transaction isolation levels, and MVCC - it
doesn't really offer any practical tips or solutions to this problem.

Thanks for any info anyone can provide.

John Sidney-Woollett


---------------------------(end of broadcast)---------------------------
TIP 2: you can get off all lists at once with the unregister command
(send "unregister YourEmailAddressHere" to ma*******@postgresql.org)

Nov 12 '05 #1
Share this Question
Share on Google+
12 Replies


P: n/a
On Wednesday 03 December 2003 08:08, John Sidney-Woollett wrote:
I have to convert an java web application currently using an Oracle DB
back end to one using a Postgres backend. [snip] Issue - nested transactions
=====
[snip] This is an issue for us because some procedures make use of a function
which issues a row level lock on a table (select ... for update) in order
to read and then update a counter, and which then commits to release the
lock. The nested function returns the new counter value on return. We
cannot use Sequence objects, because the counter is tied directly to the
record which contains it, and there are any number of these record types.
Can you elaborate on what this counter is/how you are using it? It sounds like
the "counter" gets incremented regardless of whether an insert/update gets
committed, which makes me wonder what it is counting.
Is there a simple/elegant solution to this problem? And is there a good
document on dealing with concurrency issues - I have read the manual for
7.4 and while it describes the transaction isolation levels, and MVCC - it
doesn't really offer any practical tips or solutions to this problem.


Hmm - we don't seem to have any items dealing with concurrency issues on
techdocs.postgresql.org, which is a shame since they are exactly the sort of
thing benefit from having examples of pitfalls.

--
Richard Huxton
Archonet Ltd

---------------------------(end of broadcast)---------------------------
TIP 2: you can get off all lists at once with the unregister command
(send "unregister YourEmailAddressHere" to ma*******@postgresql.org)

Nov 12 '05 #2

P: n/a
Here are two procedures converted from Oracle's PL/SQL (so the oracle
specific stuff (like #pragma autonomous) has gone.

This first function returns two values (it used to use an extra pair of
out parameters). You are correct in that the function SHOULD increment the
counter regardless of whether the enclosing transaction commits or not.
(Of course in Postgres this is not the case).

CREATE OR REPLACE FUNCTION GetVolumeFileReference (varchar, integer)
RETURNS integer AS '
-- allocates a volume and unique file ID for storing a resource
-- determines where to store a (file) resource according to the
allocation strategy
-- pAllocStrategy can be either
-- null, FIRST (default) = fill first available volume before using next
-- DISTRIBUTE = distribute files across the available volumes

DECLARE
pAllocStrategy ALIAS OF $1;
pSpaceReqd ALIAS OF $2;

vAllocStrategy varchar(16) := pAllocStrategy;
vVolumeID integer := -1;
vFileID integer := -1;

BEGIN
-- set defaults
vVolumeID := -1;
pFileID := -1;

-- get the volume ID, and next file ID
-- check what allocation strategy has been requested
-- to locate the most appropriate volume
if (upper(vAllocStrategy) = ''DISTRIBUTE'') then
select WDVolumeID into vVolumeID
from WDVolume
where Writeable = ''Y''
and DiskAvailPC >= 5
order by DiskAvailPC desc
limit 1;
else
-- use first volume with space available
select WDVolumeID into vVolumeID
from WDVolume
where Writeable = ''Y''
and DiskAvailPC >= 5
order by WDVolumeID
limit 1;
end if;

-- cannot find a free volume
if vVolumeID is null then
return -1;
end if;

-- now lock the volume (and wait for it to become free)
select LastFileSeq into vFileID
from WDVolume
where WDVolumeID = vVolumeID
for update;

-- increment the file seq counter
if (vFileID is null) then vFileID := 0; end if;
vFileID := vFileID + 1;

-- update the volume, and write the changed values back
update WDVolume
set LastFileSeq = vFileID
where WDVolumeID = vVolumeID;

RETURN lpad(vVolumeID, 10, '' '') || lpad(vFileID, 10, '' '');
END;
' LANGUAGE 'plpgsql';

Here is a stripped down version of the function that calls the one above -
there are many threads calling this function simultaneously:

CREATE OR REPLACE FUNCTION CreateFile (integer, varchar, varchar, integer)
RETURNS integer AS '
-- creates a new file (if it does not exist)
-- and returns the WDResource ID of the newly
-- created file. If the file already exists
-- then the resource ID is returned, otherwise
-- if the file could not be created then -1 is returned

DECLARE
pFolderID ALIAS OF $1;
pFilename ALIAS OF $2;
pContentType ALIAS OF $3;
pSize ALIAS OF $4;

vUserID integer;
vResourceID integer := -1;
vURL varchar(255) := null;
vVolumeID integer := -1;
vFileSeq integer := -1;
vRefPath varchar(64) := null;
vRefName varchar(64) := null;
vContentType varchar(16) := pContentType;
vSize integer := pSize;
vVolFileSeq varchar(64) := null;

BEGIN
-- get the parent folder information
select WDUserID, URL into vUserID, vURL
from WDResource
where WDResourceID = pFolderID;

if vURL is null then
return -1;
end if;

-- check that the file doesnt exist already
select WDResourceID into vResourceID from WDResource
where URL = vURL||''/''||pFileName;

-- if it does exist then return the ID
if (vResourceID is not null) then
return vResourceID;
end if;

-- determine the volume to locate the file on, and get the file
-- sequence number
vVolFileSeq := GetVolumeFileReference(''FIRST'', vSize);
vVolumeID := to_number(trim(substring(vVolFileSeq, 1, 10)));
vFileSeq := to_number(trim(substring(vVolFileSeq, 11, 10)));

-- if any error occured here then abort
if (vVolumeID = -1) or (vFileSeq = -1) then
return -1;
end if;

-- guess the content type if not known
if (vContentType is null) then
vContentType := GuessContentType(pFilename);
end if;

-- create the unique file reference name
-- this is the one stored on disk
vRefName := getFileRefName(vVolumeID, vFileSeq, vContentType, pFilename);

-- get the pathname from the file reference name
vRefPath := getFileRefPath(vRefName);

-- get the next resource ID
select nextval(''SEQ_WDRESOURCE'') into vResourceID;

-- set the content length (file size) to zero initially
-- it will be updated after successfully writing the file
-- using the UpdateDiskUsed(WDResourceID, newSize) procedure
vSize := 0;

-- create the new file resource
insert into WDResource (WDRESOURCEID, WDPARENTID, WDUSERID, WDRESTYPEID,
URL, WDVOLUMEID,
FILENAME, REFPATH, REFNAME, CONTENTTYPE, CONTENTLENGTH, LASTMODIFIED)
values (vResourceID, pFolderID, vUserID, 2, vURL||''/''||pFilename,
vVolumeID,
pFilename, vRefPath, vRefName, vContentType, vSize, now());

return vResourceID;
END;
' LANGUAGE 'plpgsql';

I recognize that the above code may not be ana elegant way to code using
Postgres - but it is a fairly literal transation from the Oracle PL/SQL
code.

You can see that the call to

vVolFileSeq := GetVolumeFileReference(''FIRST'', vSize);

is going to be a bottleneck.

Is there some way of dealing with this issue? I cannot allow dirty reads
onto the value returned by this function.

I'm really unsure of how Postgres guarantees data consistancy with MVCC...

Thanks for any help.

John


Richard Huxton said:
On Wednesday 03 December 2003 08:08, John Sidney-Woollett wrote:
I have to convert an java web application currently using an Oracle DB
back end to one using a Postgres backend.

[snip]
Issue - nested transactions
=====

[snip]
This is an issue for us because some procedures make use of a function
which issues a row level lock on a table (select ... for update) in
order
to read and then update a counter, and which then commits to release the
lock. The nested function returns the new counter value on return. We
cannot use Sequence objects, because the counter is tied directly to the
record which contains it, and there are any number of these record
types.


Can you elaborate on what this counter is/how you are using it? It sounds
like
the "counter" gets incremented regardless of whether an insert/update gets
committed, which makes me wonder what it is counting.
Is there a simple/elegant solution to this problem? And is there a good
document on dealing with concurrency issues - I have read the manual for
7.4 and while it describes the transaction isolation levels, and MVCC -
it
doesn't really offer any practical tips or solutions to this problem.


Hmm - we don't seem to have any items dealing with concurrency issues on
techdocs.postgresql.org, which is a shame since they are exactly the sort
of
thing benefit from having examples of pitfalls.

--
Richard Huxton
Archonet Ltd

---------------------------(end of broadcast)---------------------------
TIP 2: you can get off all lists at once with the unregister command
(send "unregister YourEmailAddressHere" to ma*******@postgresql.org)

Nov 12 '05 #3

P: n/a
On Wednesday 03 December 2003 11:01, John Sidney-Woollett wrote:
Here are two procedures converted from Oracle's PL/SQL (so the oracle
specific stuff (like #pragma autonomous) has gone.

This first function returns two values (it used to use an extra pair of
out parameters). You are correct in that the function SHOULD increment the
counter regardless of whether the enclosing transaction commits or not.
(Of course in Postgres this is not the case).

CREATE OR REPLACE FUNCTION GetVolumeFileReference (varchar, integer)
RETURNS integer AS ' [snip] -- now lock the volume (and wait for it to become free)
select LastFileSeq into vFileID
from WDVolume
where WDVolumeID = vVolumeID
for update;

-- increment the file seq counter
if (vFileID is null) then vFileID := 0; end if;
vFileID := vFileID + 1;

-- update the volume, and write the changed values back
update WDVolume
set LastFileSeq = vFileID
where WDVolumeID = vVolumeID;


OK - here you are basically recreating what a sequence does. The difference
being that you have one sequence per "VolumeID".

Now, is "LastFileSeq" just used as a unique identifier, or does it have a
hidden meaning too (e.g. "approximate number of files created")? If it is
just a unique identifier, just share one sequence between all the volumes.

If it comes down to it, you can have many sequences, but I don't know how the
system copes if you have thousands or millions of them.

Oh - did you know you could return a pair of integers from your function? Use
CREATE TYPE to make a type containing two ints and then return that type.
Should save you a substring later (incidentally, I don't think integer will
go to 20 digits, so you'll have a problem there).

--
Richard Huxton
Archonet Ltd

---------------------------(end of broadcast)---------------------------
TIP 9: the planner will ignore your desire to choose an index scan if your
joining column's datatypes do not match

Nov 12 '05 #4

P: n/a
Richard

You summized correctly. The field being updated is basically a sequence
per volume. BUT the sequences cannot be shared across the volumes...

I did wonder about "binding" one sequence object to each Volume record, and
modifying the function so that the identified the volume to use, and then
obtained the next value from the appropriate sequence object. Is it
possible to do the following:

--declare var to "hold" the sequence name
vVolSeqName := "SEQ_VOLUME_1";

--access the sequence from the name in the variable
select nextval(vVolSeqName) into vFileSeq;

If I cannot do this, can you suggest a solution to my original bottleneck
issue. And also the problem of the sequencing number being rolled back in
the event that the CreateFile function aborts or is rolled back.

However, for me a bigger area of confusion is how to deal with concurrency
issues in Postgres generally. Are there any good docs with examples of
different scenarios?

I appreciate all your help so far. Thanks.

John

Richard Huxton said:
On Wednesday 03 December 2003 11:01, John Sidney-Woollett wrote:
Here are two procedures converted from Oracle's PL/SQL (so the oracle
specific stuff (like #pragma autonomous) has gone.

This first function returns two values (it used to use an extra pair of
out parameters). You are correct in that the function SHOULD increment
the
counter regardless of whether the enclosing transaction commits or not.
(Of course in Postgres this is not the case).

CREATE OR REPLACE FUNCTION GetVolumeFileReference (varchar, integer)
RETURNS integer AS '

[snip]
-- now lock the volume (and wait for it to become free)
select LastFileSeq into vFileID
from WDVolume
where WDVolumeID = vVolumeID
for update;

-- increment the file seq counter
if (vFileID is null) then vFileID := 0; end if;
vFileID := vFileID + 1;

-- update the volume, and write the changed values back
update WDVolume
set LastFileSeq = vFileID
where WDVolumeID = vVolumeID;


OK - here you are basically recreating what a sequence does. The
difference
being that you have one sequence per "VolumeID".

Now, is "LastFileSeq" just used as a unique identifier, or does it have a
hidden meaning too (e.g. "approximate number of files created")? If it is
just a unique identifier, just share one sequence between all the volumes.

If it comes down to it, you can have many sequences, but I don't know how
the
system copes if you have thousands or millions of them.

Oh - did you know you could return a pair of integers from your function?
Use
CREATE TYPE to make a type containing two ints and then return that type.
Should save you a substring later (incidentally, I don't think integer
will
go to 20 digits, so you'll have a problem there).

--
Richard Huxton
Archonet Ltd

---------------------------(end of broadcast)---------------------------
TIP 3: if posting/reading through Usenet, please send an appropriate
subscribe-nomail command to ma*******@postgresql.org so that your
message can get through to the mailing list cleanly

Nov 12 '05 #5

P: n/a
On Wednesday 03 December 2003 13:34, John Sidney-Woollett wrote:
Richard

You summized correctly. The field being updated is basically a sequence
per volume. BUT the sequences cannot be shared across the volumes...
Why not? Are you worried about running out of numbers, or is there some other
reason?
I did wonder about "binding" one sequence object to each Volume record, and
modifying the function so that the identified the volume to use, and then
obtained the next value from the appropriate sequence object. Is it
possible to do the following:

--declare var to "hold" the sequence name
vVolSeqName := "SEQ_VOLUME_1";

--access the sequence from the name in the variable
select nextval(vVolSeqName) into vFileSeq;
If nothing else, you could use EXECUTE and build your select in a string. See
the manual for details of EXECUTE.
If I cannot do this, can you suggest a solution to my original bottleneck
issue. And also the problem of the sequencing number being rolled back in
the event that the CreateFile function aborts or is rolled back.
A sequence number will never be rolled back. The sequence generator guarantees
However, for me a bigger area of confusion is how to deal with concurrency
issues in Postgres generally. Are there any good docs with examples of
different scenarios?


Not as far as I know (if you would like to contribute some as you learn, I'm
sure it would be appreciated). The isolation levels are listed with the
corresponding standard SQL though, so it might be worth googling for a
general reference.

--
Richard Huxton
Archonet Ltd

---------------------------(end of broadcast)---------------------------
TIP 6: Have you searched our list archives?

http://archives.postgresql.org

Nov 12 '05 #6

P: n/a
You could do something with threads on the backend, invisible to your Java
middleware. I don't have enough experience to feel confident about trying to
evaluate the pros and cons of (possibly) different ways of doing this. But
given that you can write functions in C and load them into Postgres so that
they can be called from plpgsql, I think you could in essence add to
Postgres a function which when called would hand off the sequence load &
update on a separate thread/connection, wait for its commit and completion,
and return the value. Of course you still have to be careful about
concurrency issues with this approach, so that you don't wind up with the 2
threads deadlocked.

That may well strike you as a gross hack. I don't particularly like it
either, but I think it would get the job done without requiring any changes
to your current code base except for the rewrite of GetVolumeFileReference.

BTW, in reference to other suggestions: I believe that a sequence name is
indeed just a string, so you can build the name and pass it to sequence
functions on the fly; I know that sequences do not roll back, once a value
is issued it is "burned" regardless of whether the enclosing transaction
commits or not. So you should be able to have a trigger that on insert of a
WDVolume row creates a corresponding sequence, then use that sequence within
GetVolumeFileReference. Whether this is a good idea depends I'm sure on how
many WDVolumes you'll have. I feel confident that dozens or hundreds would
be no problem; thousands I don't have any idea; millions I wouldn't try.
--
Scott Ribe
sc********@killerbytes.com
http://www.killerbytes.com/
(303) 665-7007 voice
---------------------------(end of broadcast)---------------------------
TIP 9: the planner will ignore your desire to choose an index scan if your
joining column's datatypes do not match

Nov 12 '05 #7

P: n/a
Hi Scott

Firstly, your name rings a bell. I'm sure you're a 4D developer - I was
one too (once, but no more sadly)...

You imply that it should be possible to create an embedded C function that
can access the DB in its own thread, and therefore its own transaction. Is
there a document that would explain how this is done?

Also do you know if you can embedded java functions - I'm more comfortable
with java than C these days?

I think that for this problem, I will use SEQUENCE objects who name is
stored within the WDVolume table. It will allow me to avoid the row level
lock bottleneck, and my counters won't be rolled back on a transaction
rollback. There are only going to be less than 100 Volumes - so this
approach will be fine.

I'm amazed that nested transactions are still not yet supported. Postgres
is a mature product (from my understanding), and I can see loads of
messages asking about nested transaction support.

Thanks for your feedback.

John

Scott Ribe said:
You could do something with threads on the backend, invisible to your Java
middleware. I don't have enough experience to feel confident about trying
to
evaluate the pros and cons of (possibly) different ways of doing this. But
given that you can write functions in C and load them into Postgres so
that
they can be called from plpgsql, I think you could in essence add to
Postgres a function which when called would hand off the sequence load &
update on a separate thread/connection, wait for its commit and
completion,
and return the value. Of course you still have to be careful about
concurrency issues with this approach, so that you don't wind up with the
2
threads deadlocked.

That may well strike you as a gross hack. I don't particularly like it
either, but I think it would get the job done without requiring any
changes
to your current code base except for the rewrite of
GetVolumeFileReference.

BTW, in reference to other suggestions: I believe that a sequence name is
indeed just a string, so you can build the name and pass it to sequence
functions on the fly; I know that sequences do not roll back, once a value
is issued it is "burned" regardless of whether the enclosing transaction
commits or not. So you should be able to have a trigger that on insert of
a
WDVolume row creates a corresponding sequence, then use that sequence
within
GetVolumeFileReference. Whether this is a good idea depends I'm sure on
how
many WDVolumes you'll have. I feel confident that dozens or hundreds would
be no problem; thousands I don't have any idea; millions I wouldn't try.
--
Scott Ribe
sc********@killerbytes.com
http://www.killerbytes.com/
(303) 665-7007 voice
---------------------------(end of broadcast)---------------------------
TIP 9: the planner will ignore your desire to choose an index scan if your
joining column's datatypes do not match

---------------------------(end of broadcast)---------------------------
TIP 3: if posting/reading through Usenet, please send an appropriate
subscribe-nomail command to ma*******@postgresql.org so that your
message can get through to the mailing list cleanly

Nov 12 '05 #8

P: n/a
On Thursday 04 December 2003 07:57, John Sidney-Woollett wrote:

I'm amazed that nested transactions are still not yet supported. Postgres
is a mature product (from my understanding), and I can see loads of
messages asking about nested transaction support.


Loads of messages asking for many other features too ;-)
It will get done sooner or later - the more assistance people offer the core
the sooner it'll be.

--
Richard Huxton
Archonet Ltd

---------------------------(end of broadcast)---------------------------
TIP 6: Have you searched our list archives?

http://archives.postgresql.org

Nov 12 '05 #9

P: n/a
Point taken.

I'm also interested in replication - seeing lots of messages about that
too! :)

John

Richard Huxton said:
Loads of messages asking for many other features too ;-)
It will get done sooner or later - the more assistance people offer the
core
the sooner it'll be.

--
Richard Huxton
Archonet Ltd

---------------------------(end of broadcast)---------------------------
TIP 4: Don't 'kill -9' the postmaster

Nov 12 '05 #10

P: n/a
On Wed, 3 Dec 2003 08:08:49 -0000 (GMT), "John Sidney-Woollett"
<jo****@wardbrook.com> wrote:
Issue - nested transactions This is an issue for us because some procedures make use of a function
which issues a row level lock on a table (select ... for update) in order
to read and then update a counter, and which then commits to release the
lock. The nested function returns the new counter value on return.
AFAICS nested transactions - at least in the way we plan to implement
them - won't help, because subtransaction commit will not release locks.
We see a subtransaction as part of the main transaction. If a
subtransaction commits but the main transaction aborts, the
subtransaction's effects are rolled back.

START TRANSACTION; -- main xact
...
START TRANSACTION; -- sub xact
UPDATE t SET n=n+1 WHERE i=42;

This locks the row with i=42, because if another transaction wants to
update this row, it cannot know whether to start with the old or the new
value of n before our transaction commits or rolls back.

COMMIT; --sub xact

Here we are still in the main transaction. Nothing has changed for
other backends, because they still don't know whether our main
transaction will succeed or fail. So we have to keep the lock...
Is there a simple/elegant solution to this problem?


Perhaps dblink? Just a thought, I don't have any personal experience
with it.

Servus
Manfred

---------------------------(end of broadcast)---------------------------
TIP 2: you can get off all lists at once with the unregister command
(send "unregister YourEmailAddressHere" to ma*******@postgresql.org)

Nov 12 '05 #11

P: n/a
It would be nice if nested transactions could be (optionally) decoupled
from their enclosing transaction.

John

Manfred Koizar said:
On Wed, 3 Dec 2003 08:08:49 -0000 (GMT), "John Sidney-Woollett"
<jo****@wardbrook.com> wrote:
Issue - nested transactions

This is an issue for us because some procedures make use of a function
which issues a row level lock on a table (select ... for update) in order
to read and then update a counter, and which then commits to release the
lock. The nested function returns the new counter value on return.


AFAICS nested transactions - at least in the way we plan to implement
them - won't help, because subtransaction commit will not release locks.
We see a subtransaction as part of the main transaction. If a
subtransaction commits but the main transaction aborts, the
subtransaction's effects are rolled back.

START TRANSACTION; -- main xact
...
START TRANSACTION; -- sub xact
UPDATE t SET n=n+1 WHERE i=42;

This locks the row with i=42, because if another transaction wants to
update this row, it cannot know whether to start with the old or the new
value of n before our transaction commits or rolls back.

COMMIT; --sub xact

Here we are still in the main transaction. Nothing has changed for
other backends, because they still don't know whether our main
transaction will succeed or fail. So we have to keep the lock...
Is there a simple/elegant solution to this problem?


Perhaps dblink? Just a thought, I don't have any personal experience
with it.

Servus
Manfred

---------------------------(end of broadcast)---------------------------
TIP 2: you can get off all lists at once with the unregister command
(send "unregister YourEmailAddressHere" to ma*******@postgresql.org)

---------------------------(end of broadcast)---------------------------
TIP 2: you can get off all lists at once with the unregister command
(send "unregister YourEmailAddressHere" to ma*******@postgresql.org)

Nov 12 '05 #12

P: n/a
On Thu, 4 Dec 2003 17:56:33 -0000 (GMT), "John Sidney-Woollett"
<jo****@wardbrook.com> wrote:
It would be nice if nested transactions could be (optionally) decoupled
from their enclosing transaction.


While I see your point how this could be useful for certain use cases,
unfortunately I don't have any idea how it could be implemented with
respect to MVCC.

Servus
Manfred

---------------------------(end of broadcast)---------------------------
TIP 7: don't forget to increase your free space map settings

Nov 12 '05 #13

This discussion thread is closed

Replies have been disabled for this discussion.