Tuesday, September 16, 2008

Re: [HACKERS] Subtransaction commits and Hot Standby

Index: src/backend/access/transam/transam.c
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/backend/access/transam/transam.c,v
retrieving revision 1.76
diff -c -r1.76 transam.c
*** src/backend/access/transam/transam.c 26 Mar 2008 18:48:59 -0000 1.76
--- src/backend/access/transam/transam.c 16 Sep 2008 16:55:30 -0000
***************
*** 287,317 ****
return false;
}

-
- /*
- * TransactionIdCommit
- * Commits the transaction associated with the identifier.
- *
- * Note:
- * Assumes transaction identifier is valid.
- */
- void
- TransactionIdCommit(TransactionId transactionId)
- {
- TransactionLogUpdate(transactionId, TRANSACTION_STATUS_COMMITTED,
- InvalidXLogRecPtr);
- }
-
- /*
- * TransactionIdAsyncCommit
- * Same as above, but for async commits. The commit record LSN is needed.
- */
- void
- TransactionIdAsyncCommit(TransactionId transactionId, XLogRecPtr lsn)
- {
- TransactionLogUpdate(transactionId, TRANSACTION_STATUS_COMMITTED, lsn);
- }
-
/*
* TransactionIdAbort
* Aborts the transaction associated with the identifier.
--- 287,292 ----
***************
*** 328,359 ****
}

/*
- * TransactionIdSubCommit
- * Marks the subtransaction associated with the identifier as
- * sub-committed.
- *
- * Note:
- * No async version of this is needed.
- */
- void
- TransactionIdSubCommit(TransactionId transactionId)
- {
- TransactionLogUpdate(transactionId, TRANSACTION_STATUS_SUB_COMMITTED,
- InvalidXLogRecPtr);
- }
-
- /*
* TransactionIdCommitTree
* Marks all the given transaction ids as committed.
*
* The caller has to be sure that this is used only to mark subcommitted
* subtransactions as committed, and only *after* marking the toplevel
* parent as committed. Otherwise there is a race condition against
! * TransactionIdDidCommit.
*/
void
! TransactionIdCommitTree(int nxids, TransactionId *xids)
{
if (nxids > 0)
TransactionLogMultiUpdate(nxids, xids, TRANSACTION_STATUS_COMMITTED,
InvalidXLogRecPtr);
--- 303,335 ----
}

/*
* TransactionIdCommitTree
* Marks all the given transaction ids as committed.
*
* The caller has to be sure that this is used only to mark subcommitted
* subtransactions as committed, and only *after* marking the toplevel
* parent as committed. Otherwise there is a race condition against
! * TransactionIdDidCommit since we do not apply changes atomically, yet.
*/
void
! TransactionIdCommitTree(TransactionId commitxid, int nxids, TransactionId *xids)
{
+ /*
+ * Mark top level transaction id as committed first, to avoid
+ * race conditions with TransactionIdDidCommit
+ */
+ TransactionLogUpdate(commitxid, TRANSACTION_STATUS_COMMITTED,
+ InvalidXLogRecPtr);
+
+ /*
+ * If there is more than one subcommit, then we need to mark them
+ * subcommitted first to ensure there is no race condition where
+ * we might see a subtransaction as still in progress when it is
+ * now committed.
+ */
+ if (nxids > 1)
+ TransactionLogMultiUpdate(nxids, xids, TRANSACTION_STATUS_SUB_COMMITTED,
+ InvalidXLogRecPtr);
if (nxids > 0)
TransactionLogMultiUpdate(nxids, xids, TRANSACTION_STATUS_COMMITTED,
InvalidXLogRecPtr);
***************
*** 364,371 ****
* Same as above, but for async commits. The commit record LSN is needed.
*/
void
! TransactionIdAsyncCommitTree(int nxids, TransactionId *xids, XLogRecPtr lsn)
{
if (nxids > 0)
TransactionLogMultiUpdate(nxids, xids, TRANSACTION_STATUS_COMMITTED,
lsn);
--- 340,363 ----
* Same as above, but for async commits. The commit record LSN is needed.
*/
void
! TransactionIdAsyncCommitTree(TransactionId commitxid, int nxids, TransactionId *xids, XLogRecPtr lsn)
{
+ /*
+ * Mark top level transaction id as committed first, to avoid
+ * race conditions with TransactionIdDidCommit
+ */
+ TransactionLogUpdate(commitxid, TRANSACTION_STATUS_COMMITTED,
+ lsn);
+
+ /*
+ * If there is more than one subcommit, then we need to mark them
+ * subcommitted first to ensure there is no race condition where
+ * we might see a subtransaction as still in progress when it is
+ * now committed.
+ */
+ if (nxids > 1)
+ TransactionLogMultiUpdate(nxids, xids, TRANSACTION_STATUS_SUB_COMMITTED,
+ lsn);
if (nxids > 0)
TransactionLogMultiUpdate(nxids, xids, TRANSACTION_STATUS_COMMITTED,
lsn);
Index: src/backend/access/transam/twophase.c
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/backend/access/transam/twophase.c,v
retrieving revision 1.45
diff -c -r1.45 twophase.c
*** src/backend/access/transam/twophase.c 11 Aug 2008 11:05:10 -0000 1.45
--- src/backend/access/transam/twophase.c 16 Sep 2008 16:47:21 -0000
***************
*** 1745,1753 ****
XLogFlush(recptr);

/* Mark the transaction committed in pg_clog */
! TransactionIdCommit(xid);
! /* to avoid race conditions, the parent must commit first */
! TransactionIdCommitTree(nchildren, children);

/* Checkpoint can proceed now */
MyProc->inCommit = false;
--- 1745,1751 ----
XLogFlush(recptr);

/* Mark the transaction committed in pg_clog */
! TransactionIdCommitTree(xid, nchildren, children);

/* Checkpoint can proceed now */
MyProc->inCommit = false;
Index: src/backend/access/transam/xact.c
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/backend/access/transam/xact.c,v
retrieving revision 1.265
diff -c -r1.265 xact.c
*** src/backend/access/transam/xact.c 11 Aug 2008 11:05:10 -0000 1.265
--- src/backend/access/transam/xact.c 16 Sep 2008 16:40:17 -0000
***************
*** 254,260 ****
static TransactionId RecordTransactionAbort(bool isSubXact);
static void StartTransaction(void);

- static void RecordSubTransactionCommit(void);
static void StartSubTransaction(void);
static void CommitSubTransaction(void);
static void AbortSubTransaction(void);
--- 254,259 ----
***************
*** 952,962 ****
* Now we may update the CLOG, if we wrote a COMMIT record above
*/
if (markXidCommitted)
! {
! TransactionIdCommit(xid);
! /* to avoid race conditions, the parent must commit first */
! TransactionIdCommitTree(nchildren, children);
! }
}
else
{
--- 951,957 ----
* Now we may update the CLOG, if we wrote a COMMIT record above
*/
if (markXidCommitted)
! TransactionIdCommitTree(xid, nchildren, children);
}
else
{
***************
*** 974,984 ****
* flushed before the CLOG may be updated.
*/
if (markXidCommitted)
! {
! TransactionIdAsyncCommit(xid, XactLastRecEnd);
! /* to avoid race conditions, the parent must commit first */
! TransactionIdAsyncCommitTree(nchildren, children, XactLastRecEnd);
! }
}

/*
--- 969,975 ----
* flushed before the CLOG may be updated.
*/
if (markXidCommitted)
! TransactionIdAsyncCommitTree(xid, nchildren, children, XactLastRecEnd);
}

/*
***************
*** 1156,1191 ****
s->maxChildXids = 0;
}

- /*
- * RecordSubTransactionCommit
- */
- static void
- RecordSubTransactionCommit(void)
- {
- TransactionId xid = GetCurrentTransactionIdIfAny();
-
- /*
- * We do not log the subcommit in XLOG; it doesn't matter until the
- * top-level transaction commits.
- *
- * We must mark the subtransaction subcommitted in the CLOG if it had a
- * valid XID assigned. If it did not, nobody else will ever know about
- * the existence of this subxact. We don't have to deal with deletions
- * scheduled for on-commit here, since they'll be reassigned to our parent
- * (who might still abort).
- */
- if (TransactionIdIsValid(xid))
- {
- /* XXX does this really need to be a critical section? */
- START_CRIT_SECTION();
-
- /* Record subtransaction subcommit */
- TransactionIdSubCommit(xid);
-
- END_CRIT_SECTION();
- }
- }
-
/* ----------------------------------------------------------------
* AbortTransaction stuff
* ----------------------------------------------------------------
--- 1147,1152 ----
***************
*** 3791,3799 ****
/* Must CCI to ensure commands of subtransaction are seen as done */
CommandCounterIncrement();

- /* Mark subtransaction as subcommitted */
- RecordSubTransactionCommit();
-
/* Post-commit cleanup */
if (TransactionIdIsValid(s->transactionId))
AtSubCommit_childXids();
--- 3752,3757 ----
***************
*** 4259,4269 ****
TransactionId max_xid;
int i;

- TransactionIdCommit(xid);
-
/* Mark committed subtransactions as committed */
sub_xids = (TransactionId *) &(xlrec->xnodes[xlrec->nrels]);
! TransactionIdCommitTree(xlrec->nsubxacts, sub_xids);

/* Make sure nextXid is beyond any XID mentioned in the record */
max_xid = xid;
--- 4217,4225 ----
TransactionId max_xid;
int i;

/* Mark committed subtransactions as committed */
sub_xids = (TransactionId *) &(xlrec->xnodes[xlrec->nrels]);
! TransactionIdCommitTree(xid, xlrec->nsubxacts, sub_xids);

/* Make sure nextXid is beyond any XID mentioned in the record */
max_xid = xid;
Index: src/include/access/transam.h
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/include/access/transam.h,v
retrieving revision 1.65
diff -c -r1.65 transam.h
*** src/include/access/transam.h 11 Mar 2008 20:20:35 -0000 1.65
--- src/include/access/transam.h 16 Sep 2008 17:05:12 -0000
***************
*** 139,150 ****
extern bool TransactionIdDidCommit(TransactionId transactionId);
extern bool TransactionIdDidAbort(TransactionId transactionId);
extern bool TransactionIdIsKnownCompleted(TransactionId transactionId);
- extern void TransactionIdCommit(TransactionId transactionId);
- extern void TransactionIdAsyncCommit(TransactionId transactionId, XLogRecPtr lsn);
extern void TransactionIdAbort(TransactionId transactionId);
! extern void TransactionIdSubCommit(TransactionId transactionId);
! extern void TransactionIdCommitTree(int nxids, TransactionId *xids);
! extern void TransactionIdAsyncCommitTree(int nxids, TransactionId *xids, XLogRecPtr lsn);
extern void TransactionIdAbortTree(int nxids, TransactionId *xids);
extern bool TransactionIdPrecedes(TransactionId id1, TransactionId id2);
extern bool TransactionIdPrecedesOrEquals(TransactionId id1, TransactionId id2);
--- 139,147 ----
extern bool TransactionIdDidCommit(TransactionId transactionId);
extern bool TransactionIdDidAbort(TransactionId transactionId);
extern bool TransactionIdIsKnownCompleted(TransactionId transactionId);
extern void TransactionIdAbort(TransactionId transactionId);
! extern void TransactionIdCommitTree(TransactionId commitxid, int nxids, TransactionId *xids);
! extern void TransactionIdAsyncCommitTree(TransactionId commitxid, int nxids, TransactionId *xids, XLogRecPtr lsn);
extern void TransactionIdAbortTree(int nxids, TransactionId *xids);
extern bool TransactionIdPrecedes(TransactionId id1, TransactionId id2);
extern bool TransactionIdPrecedesOrEquals(TransactionId id1, TransactionId id2);
On Tue, 2008-09-16 at 10:11 -0400, Alvaro Herrera wrote:

> >> Right now we lock and unlock the clog for each committed subtransaction
> >> at commit time, which is wasteful. A better scheme:
> >> pre-scan the list of xids to derive list of pages
> >> if we have just a single page to update
> >> {
> >> update all entries on page in one action
> >> }
> >> else
> >> {
> >> loop thru xids marking them all as subcommitted
> >> mark top level transaction committed
> >> loop thus xids again marking them all as committed
> >> }
>
> > Hmm, I don't see anything immediately wrong with that.
>
> Neither do I.
>
> I wonder if the improved clog API required to mark multiple transactions
> as committed at once would be also useful to TransactionIdCommitTree
> which is used in regular transaction commit.

I enclose a patch to transform what we have now into what I think is
possible. If we agree this is possible, then I will do further work to
optimise transam.c (using clog.c changes also). So this is an
"intermediate" or precursor patch for discussion only.

backend/access/transam/transam.c | 78 ++++++++++++-----------------!
backend/access/transam/twophase.c | 4 !
backend/access/transam/xact.c | 50 -----------------!!!!!!!
include/access/transam.h | 7 !!!
4 files changed, 32 insertions(+), 78 deletions(-), 29 modifications(!)

--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Training, Services and Support

No comments: