• Marc G. Fournier's avatar
    From: Dan McGuirk <mcguirk@indirect.com> · 159f8c63
    Marc G. Fournier authored
    Reply-To: hackers@hub.org, Dan McGuirk <mcguirk@indirect.com>
    To: hackers@hub.org
    Subject: [HACKERS] tmin writeback optimization
    
    I was doing some profiling of the backend, and noticed that during a certain
    benchmark I was running somewhere between 30% and 75% of the backend's CPU
    time was being spent in calls to TransactionIdDidCommit() from
    HeapTupleSatisfiesNow() or HeapTupleSatisfiesItself() to determine that
    changed rows' transactions had in fact been committed even though the rows'
    tmin values had not yet been set.
    
    When a query looks at a given row, it needs to figure out whether the
    transaction that changed the row has been committed and hence it should pay
    attention to the row, or whether on the other hand the transaction is still
    in progress or has been aborted and hence the row should be ignored.  If
    a tmin value is set, it is known definitively that the row's transaction
    has been committed.  However, if tmin is not set, the transaction
    referred to in xmin must be looked up in pg_log, and this is what the
    backend was spending a lot of time doing during my benchmark.
    
    So, implementing a method suggested by Vadim, I created the following
    patch that, the first time a query finds a committed row whose tmin value
    is not set, sets it, and marks the buffer where the row is stored as
    dirty.  (It works for tmax, too.)  This doesn't result in the boost in
    real time performance I was hoping for, however it does decrease backend
    CPU usage by up to two-thirds in certain situations, so it could be
    rather beneficial in high-concurrency settings.
    159f8c63
heapvalid.c 3.67 KB
/*-------------------------------------------------------------------------
 *
 * heapvalid.c--
 *    heap tuple qualification validity checking code
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    $Header: /cvsroot/pgsql/src/backend/access/common/Attic/heapvalid.c,v 1.13 1997/03/28 07:03:53 scrappy Exp $
 *
 *-------------------------------------------------------------------------
 */

#include <postgres.h>

#include <fmgr.h>
#include <access/heaptuple.h>
#include <access/valid.h>
#include <access/xact.h>
#include <storage/bufpage.h>
#include <utils/rel.h>
#include <utils/tqual.h>
#include <storage/bufmgr.h>

/* ----------------
 *	heap_keytest
 *
 *	Test a heap tuple with respect to a scan key.
 * ----------------
 */
bool
heap_keytest(HeapTuple t,
	     TupleDesc tupdesc,
	     int nkeys,
	     ScanKey keys)
{
    bool	isnull;
    Datum	atp;
    int		test;
    
    for (; nkeys--; keys++) {
	atp = (Datum)heap_getattr(t, InvalidBuffer,
				  keys->sk_attno, 
				  tupdesc,
				  &isnull);
	
	if (isnull)
	    /* XXX eventually should check if SK_ISNULL */
	    return false;
	
	if (keys->sk_flags & SK_ISNULL) {
	    return (false);
	}

	if (keys->sk_flags & SK_COMMUTE)
	    test = (long) FMGR_PTR2(keys->sk_func, keys->sk_procedure,
				    keys->sk_argument, atp);
	else
	    test = (long) FMGR_PTR2(keys->sk_func, keys->sk_procedure,
				    atp, keys->sk_argument);
	
	if (!test == !(keys->sk_flags & SK_NEGATE))
	    return false;
    }
    
    return true;
}

/* ----------------
 *	heap_tuple_satisfies
 *
 *  Returns a valid HeapTuple if it satisfies the timequal and keytest.
 *  Returns NULL otherwise.  Used to be heap_satisifies (sic) which
 *  returned a boolean.  It now returns a tuple so that we can avoid doing two
 *  PageGetItem's per tuple.
 *
 *	Complete check of validity including LP_CTUP and keytest.
 *	This should perhaps be combined with valid somehow in the
 *	future.  (Also, additional rule tests/time range tests.)
 *
 *  on 8/21/92 mao says:  i rearranged the tests here to do keytest before
 *  SatisfiesTimeQual.  profiling indicated that even for vacuumed relations,
 *  time qual checking was more expensive than key testing.  time qual is
 *  least likely to fail, too.  we should really add the time qual test to
 *  the restriction and optimize it in the normal way.  this has interactions
 *  with joey's expensive function work.
 * ----------------
 */
HeapTuple
heap_tuple_satisfies(ItemId itemId,
		     Relation relation,
		     Buffer buffer,
		     PageHeader disk_page,
		     TimeQual	qual,
		     int nKeys,
		     ScanKey key)
{
    HeapTuple	tuple, result;
    bool res;
    TransactionId old_tmin, old_tmax;

    if (! ItemIdIsUsed(itemId))
	return NULL;
    
    tuple = (HeapTuple) PageGetItem((Page) disk_page, itemId);
    
    if (key != NULL)
	res = heap_keytest(tuple, RelationGetTupleDescriptor(relation), 
			   nKeys, key);
    else
	res = TRUE;

    result = (HeapTuple)NULL;
    if (res) {
	if(relation->rd_rel->relkind == RELKIND_UNCATALOGED) {
	    result = tuple;
	} else {
	    old_tmin = tuple->t_tmin;
	    old_tmax = tuple->t_tmax;
	    res = HeapTupleSatisfiesTimeQual(tuple,qual);
	    if(tuple->t_tmin != old_tmin ||
	       tuple->t_tmax != old_tmax) {
		SetBufferCommitInfoNeedsSave(buffer);
	    }
	    if(res) {
		result = tuple;
	    }
	}
    }

    return result;
}

/*
 *  TupleUpdatedByCurXactAndCmd() -- Returns true if this tuple has
 *	already been updated once by the current transaction/command
 *	pair.
 */
bool
TupleUpdatedByCurXactAndCmd(HeapTuple t)
{
    if (TransactionIdEquals(t->t_xmax,
			    GetCurrentTransactionId()) &&
	t->t_cmax == GetCurrentCommandId())
	return true;
    
    return false;
}