From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 8 Apr 2007 01:26:33 +0000 (+0000)
Subject: Make CLUSTER MVCC-safe.  Heikki Linnakangas
X-Git-Tag: REL8_3_BETA1~858
X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=7b78474da35604152805b2fdd939326e1bc364da;p=postgresql

Make CLUSTER MVCC-safe.  Heikki Linnakangas
---

diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index f38ec4da75..3a712191a4 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -4,7 +4,7 @@
 #    Makefile for access/heap
 #
 # IDENTIFICATION
-#    $PostgreSQL: pgsql/src/backend/access/heap/Makefile,v 1.14 2007/01/20 17:16:10 petere Exp $
+#    $PostgreSQL: pgsql/src/backend/access/heap/Makefile,v 1.15 2007/04/08 01:26:27 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -12,7 +12,7 @@ subdir = src/backend/access/heap
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = heapam.o hio.o tuptoaster.o
+OBJS = heapam.o hio.o rewriteheap.o tuptoaster.o
 
 all: SUBSYS.o
 
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index f561e351f2..ee2be7cfdb 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/heap/heapam.c,v 1.231 2007/04/03 04:14:26 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/heap/heapam.c,v 1.232 2007/04/08 01:26:27 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -3299,6 +3299,51 @@ log_heap_move(Relation reln, Buffer oldbuf, ItemPointerData from,
 	return log_heap_update(reln, oldbuf, from, newbuf, newtup, true);
 }
 
+/*
+ * Perform XLogInsert of a HEAP_NEWPAGE record to WAL. Caller is responsible
+ * for writing the page to disk after calling this routine.
+ *
+ * Note: all current callers build pages in private memory and write them
+ * directly to smgr, rather than using bufmgr.  Therefore there is no need
+ * to pass a buffer ID to XLogInsert, nor to perform MarkBufferDirty within
+ * the critical section.
+ *
+ * Note: the NEWPAGE log record is used for both heaps and indexes, so do
+ * not do anything that assumes we are touching a heap.
+ */
+XLogRecPtr
+log_newpage(RelFileNode *rnode, BlockNumber blkno, Page page)
+{
+	xl_heap_newpage xlrec;
+	XLogRecPtr	recptr;
+	XLogRecData rdata[2];
+
+	/* NO ELOG(ERROR) from here till newpage op is logged */
+	START_CRIT_SECTION();
+
+	xlrec.node = *rnode;
+	xlrec.blkno = blkno;
+
+	rdata[0].data = (char *) &xlrec;
+	rdata[0].len = SizeOfHeapNewpage;
+	rdata[0].buffer = InvalidBuffer;
+	rdata[0].next = &(rdata[1]);
+
+	rdata[1].data = (char *) page;
+	rdata[1].len = BLCKSZ;
+	rdata[1].buffer = InvalidBuffer;
+	rdata[1].next = NULL;
+
+	recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_NEWPAGE, rdata);
+
+	PageSetLSN(page, recptr);
+	PageSetTLI(page, ThisTimeLineID);
+
+	END_CRIT_SECTION();
+
+	return recptr;
+}
+
 static void
 heap_xlog_clean(XLogRecPtr lsn, XLogRecord *record)
 {
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
new file mode 100644
index 0000000000..1f4bba8a86
--- /dev/null
+++ b/src/backend/access/heap/rewriteheap.c
@@ -0,0 +1,631 @@
+/*-------------------------------------------------------------------------
+ *
+ * rewriteheap.c
+ *	  Support functions to rewrite tables.
+ *
+ * These functions provide a facility to completely rewrite a heap, while
+ * preserving visibility information and update chains.
+ *
+ * INTERFACE
+ *
+ * The caller is responsible for creating the new heap, all catalog
+ * changes, supplying the tuples to be written to the new heap, and
+ * rebuilding indexes.  The caller must hold AccessExclusiveLock on the
+ * target table, because we assume no one else is writing into it.
+ *
+ * To use the facility:
+ *
+ * begin_heap_rewrite
+ * while (fetch next tuple)
+ * {
+ *     if (tuple is dead)
+ *         rewrite_heap_dead_tuple
+ *     else
+ *     {
+ *         // do any transformations here if required
+ *         rewrite_heap_tuple
+ *     }
+ * }
+ * end_heap_rewrite
+ *
+ * The contents of the new relation shouldn't be relied on until after
+ * end_heap_rewrite is called.
+ *
+ *
+ * IMPLEMENTATION
+ *
+ * This would be a fairly trivial affair, except that we need to maintain
+ * the ctid chains that link versions of an updated tuple together.
+ * Since the newly stored tuples will have tids different from the original
+ * ones, if we just copied t_ctid fields to the new table the links would
+ * be wrong.  When we are required to copy a (presumably recently-dead or
+ * delete-in-progress) tuple whose ctid doesn't point to itself, we have
+ * to substitute the correct ctid instead.
+ *
+ * For each ctid reference from A -> B, we might encounter either A first
+ * or B first.  (Note that a tuple in the middle of a chain is both A and B
+ * of different pairs.)
+ *
+ * If we encounter A first, we'll store the tuple in the unresolved_tups
+ * hash table. When we later encounter B, we remove A from the hash table,
+ * fix the ctid to point to the new location of B, and insert both A and B
+ * to the new heap.
+ *
+ * If we encounter B first, we can insert B to the new heap right away.
+ * We then add an entry to the old_new_tid_map hash table showing B's
+ * original tid (in the old heap) and new tid (in the new heap).
+ * When we later encounter A, we get the new location of B from the table,
+ * and can write A immediately with the correct ctid.
+ *
+ * Entries in the hash tables can be removed as soon as the later tuple
+ * is encountered.  That helps to keep the memory usage down.  At the end,
+ * both tables are usually empty; we should have encountered both A and B
+ * of each pair.  However, it's possible for A to be RECENTLY_DEAD and B
+ * entirely DEAD according to HeapTupleSatisfiesVacuum, because the test
+ * for deadness using OldestXmin is not exact.  In such a case we might
+ * encounter B first, and skip it, and find A later.  Then A would be added
+ * to unresolved_tups, and stay there until end of the rewrite.  Since
+ * this case is very unusual, we don't worry about the memory usage.
+ *
+ * Using in-memory hash tables means that we use some memory for each live
+ * update chain in the table, from the time we find one end of the
+ * reference until we find the other end.  That shouldn't be a problem in
+ * practice, but if you do something like an UPDATE without a where-clause
+ * on a large table, and then run CLUSTER in the same transaction, you
+ * could run out of memory.  It doesn't seem worthwhile to add support for
+ * spill-to-disk, as there shouldn't be that many RECENTLY_DEAD tuples in a
+ * table under normal circumstances.  Furthermore, in the typical scenario
+ * of CLUSTERing on an unchanging key column, we'll see all the versions
+ * of a given tuple together anyway, and so the peak memory usage is only
+ * proportional to the number of RECENTLY_DEAD versions of a single row, not
+ * in the whole table.  Note that if we do fail halfway through a CLUSTER,
+ * the old table is still valid, so failure is not catastrophic.
+ *
+ * We can't use the normal heap_insert function to insert into the new
+ * heap, because heap_insert overwrites the visibility information.
+ * We use a special-purpose raw_heap_insert function instead, which
+ * is optimized for bulk inserting a lot of tuples, knowing that we have
+ * exclusive access to the heap.  raw_heap_insert builds new pages in
+ * local storage.  When a page is full, or at the end of the process,
+ * we insert it to WAL as a single record and then write it to disk
+ * directly through smgr.  Note, however, that any data sent to the new
+ * heap's TOAST table will go through the normal bufmgr.
+ *
+ *
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  $PostgreSQL: pgsql/src/backend/access/heap/rewriteheap.c,v 1.1 2007/04/08 01:26:27 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/rewriteheap.h"
+#include "access/transam.h"
+#include "access/tuptoaster.h"
+#include "storage/smgr.h"
+#include "utils/memutils.h"
+
+
+/*
+ * State associated with a rewrite operation. This is opaque to the user
+ * of the rewrite facility.
+ */
+typedef struct RewriteStateData
+{
+	Relation		rs_new_rel;			/* destination heap */
+	Page			rs_buffer;			/* page currently being built */
+	BlockNumber		rs_blockno;			/* block where page will go */
+	bool			rs_buffer_valid;	/* T if any tuples in buffer */
+	bool			rs_use_wal;			/* must we WAL-log inserts? */
+	TransactionId	rs_oldest_xmin;		/* oldest xmin used by caller to
+										 * determine tuple visibility */
+	MemoryContext	rs_cxt;				/* for hash tables and entries and
+										 * tuples in them */
+	HTAB		   *rs_unresolved_tups;	/* unmatched A tuples */
+	HTAB		   *rs_old_new_tid_map;	/* unmatched B tuples */
+} RewriteStateData;
+
+/*
+ * The lookup keys for the hash tables are tuple TID and xmin (we must check
+ * both to avoid false matches from dead tuples).  Beware that there is
+ * probably some padding space in this struct; it must be zeroed out for
+ * correct hashtable operation.
+ */
+typedef struct
+{
+	TransactionId	xmin;		/* tuple xmin */
+	ItemPointerData tid;		/* tuple location in old heap */
+} TidHashKey;
+
+/*
+ * Entry structures for the hash tables
+ */
+typedef struct
+{
+	TidHashKey		key;		/* expected xmin/old location of B tuple */
+	ItemPointerData old_tid;	/* A's location in the old heap */
+	HeapTuple tuple;			/* A's tuple contents */
+} UnresolvedTupData;
+
+typedef UnresolvedTupData *UnresolvedTup;
+
+typedef struct
+{
+	TidHashKey		key;		/* actual xmin/old location of B tuple */
+	ItemPointerData new_tid;	/* where we put it in the new heap */
+} OldToNewMappingData;
+
+typedef OldToNewMappingData *OldToNewMapping;
+
+
+/* prototypes for internal functions */
+static void raw_heap_insert(RewriteState state, HeapTuple tup);
+
+
+/*
+ * Begin a rewrite of a table
+ *
+ * new_heap		new, locked heap relation to insert tuples to
+ * oldest_xmin	xid used by the caller to determine which tuples are dead
+ * use_wal		should the inserts to the new heap be WAL-logged?
+ *
+ * Returns an opaque RewriteState, allocated in current memory context,
+ * to be used in subsequent calls to the other functions.
+ */
+RewriteState
+begin_heap_rewrite(Relation new_heap, TransactionId oldest_xmin,
+				   bool use_wal)
+{
+	RewriteState state;
+	MemoryContext rw_cxt;
+	MemoryContext old_cxt;
+	HASHCTL		hash_ctl;
+
+	/*
+	 * To ease cleanup, make a separate context that will contain
+	 * the RewriteState struct itself plus all subsidiary data.
+	 */
+	rw_cxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "Table rewrite",
+								   ALLOCSET_DEFAULT_MINSIZE,
+								   ALLOCSET_DEFAULT_INITSIZE,
+								   ALLOCSET_DEFAULT_MAXSIZE);
+	old_cxt = MemoryContextSwitchTo(rw_cxt);
+
+	/* Create and fill in the state struct */
+	state = palloc0(sizeof(RewriteStateData));
+
+	state->rs_new_rel = new_heap;
+	state->rs_buffer = (Page) palloc(BLCKSZ);
+	/* new_heap needn't be empty, just locked */
+	state->rs_blockno = RelationGetNumberOfBlocks(new_heap);
+	/* Note: we assume RelationGetNumberOfBlocks did RelationOpenSmgr for us */
+	state->rs_buffer_valid = false;
+	state->rs_use_wal = use_wal;
+	state->rs_oldest_xmin = oldest_xmin;
+	state->rs_cxt = rw_cxt;
+
+	/* Initialize hash tables used to track update chains */
+	memset(&hash_ctl, 0, sizeof(hash_ctl));
+	hash_ctl.keysize = sizeof(TidHashKey);
+	hash_ctl.entrysize = sizeof(UnresolvedTupData);
+	hash_ctl.hcxt = state->rs_cxt;
+	hash_ctl.hash = tag_hash;
+
+	state->rs_unresolved_tups =
+		hash_create("Rewrite / Unresolved ctids",
+					128, /* arbitrary initial size */
+					&hash_ctl,
+					HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+
+	hash_ctl.entrysize = sizeof(OldToNewMappingData);
+
+	state->rs_old_new_tid_map =
+		hash_create("Rewrite / Old to new tid map",
+					128, /* arbitrary initial size */
+					&hash_ctl,
+					HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+
+	MemoryContextSwitchTo(old_cxt);
+
+	return state;
+}
+
+/*
+ * End a rewrite.
+ *
+ * state and any other resources are freed.
+ */
+void
+end_heap_rewrite(RewriteState state)
+{
+	HASH_SEQ_STATUS seq_status;
+	UnresolvedTup unresolved;
+
+	/*
+	 * Write any remaining tuples in the UnresolvedTups table. If we have
+	 * any left, they should in fact be dead, but let's err on the safe side.
+	 *
+	 * XXX this really is a waste of code no?
+	 */
+	hash_seq_init(&seq_status, state->rs_unresolved_tups);
+
+	while ((unresolved = hash_seq_search(&seq_status)) != NULL)
+	{
+		ItemPointerSetInvalid(&unresolved->tuple->t_data->t_ctid);
+		raw_heap_insert(state, unresolved->tuple);
+	}
+
+	/* Write the last page, if any */
+	if (state->rs_buffer_valid)
+	{
+		if (state->rs_use_wal)
+			log_newpage(&state->rs_new_rel->rd_node,
+						state->rs_blockno,
+						state->rs_buffer);
+		smgrextend(state->rs_new_rel->rd_smgr, state->rs_blockno,
+				   (char *) state->rs_buffer, true);
+	}
+
+	/*
+	 * If not WAL-logging, must fsync before commit.  We use heap_sync
+	 * to ensure that the toast table gets fsync'd too.
+	 */
+	if (!state->rs_use_wal)
+		heap_sync(state->rs_new_rel);
+
+	/* Deleting the context frees everything */
+	MemoryContextDelete(state->rs_cxt);
+}
+
+/*
+ * Add a tuple to the new heap.
+ *
+ * Visibility information is copied from the original tuple.
+ *
+ * state		opaque state as returned by begin_heap_rewrite
+ * old_tuple	original tuple in the old heap
+ * new_tuple	new, rewritten tuple to be inserted to new heap
+ */
+void
+rewrite_heap_tuple(RewriteState state,
+				   HeapTuple old_tuple, HeapTuple new_tuple)
+{
+	MemoryContext old_cxt;
+	ItemPointerData old_tid;
+	TidHashKey hashkey;
+	bool found;
+	bool free_new;
+
+	old_cxt = MemoryContextSwitchTo(state->rs_cxt);
+
+	/*
+	 * Copy the original tuple's visibility information into new_tuple.
+	 *
+	 * XXX we might later need to copy some t_infomask2 bits, too?
+	 */
+	memcpy(&new_tuple->t_data->t_choice.t_heap,
+		   &old_tuple->t_data->t_choice.t_heap,
+		   sizeof(HeapTupleFields));
+
+	new_tuple->t_data->t_infomask &= ~HEAP_XACT_MASK;
+	new_tuple->t_data->t_infomask |=
+		old_tuple->t_data->t_infomask & HEAP_XACT_MASK;
+
+	/*
+	 * Invalid ctid means that ctid should point to the tuple itself.
+	 * We'll override it later if the tuple is part of an update chain.
+	 */
+	ItemPointerSetInvalid(&new_tuple->t_data->t_ctid);
+
+	/*
+	 * If the tuple has been updated, check the old-to-new mapping hash table.
+	 */
+	if (!(old_tuple->t_data->t_infomask & (HEAP_XMAX_INVALID |
+										   HEAP_IS_LOCKED)) &&
+		!(ItemPointerEquals(&(old_tuple->t_self),
+							&(old_tuple->t_data->t_ctid))))
+	{
+		OldToNewMapping mapping;
+
+		memset(&hashkey, 0, sizeof(hashkey));
+		hashkey.xmin = HeapTupleHeaderGetXmax(old_tuple->t_data);
+		hashkey.tid = old_tuple->t_data->t_ctid;
+
+		mapping = (OldToNewMapping)
+			hash_search(state->rs_old_new_tid_map, &hashkey,
+						HASH_FIND, NULL);
+
+		if (mapping != NULL)
+		{
+			/*
+			 * We've already copied the tuple that t_ctid points to, so we
+			 * can set the ctid of this tuple to point to the new location,
+			 * and insert it right away.
+			 */
+			new_tuple->t_data->t_ctid = mapping->new_tid;
+
+			/* We don't need the mapping entry anymore */
+			hash_search(state->rs_old_new_tid_map, &hashkey,
+						HASH_REMOVE, &found);
+			Assert(found);
+		}
+		else
+		{
+			/*
+			 * We haven't seen the tuple t_ctid points to yet. Stash this
+			 * tuple into unresolved_tups to be written later.
+			 */
+			UnresolvedTup unresolved;
+
+			unresolved = hash_search(state->rs_unresolved_tups, &hashkey,
+									 HASH_ENTER, &found);
+			Assert(!found);
+
+			unresolved->old_tid = old_tuple->t_self;
+			unresolved->tuple = heap_copytuple(new_tuple);
+
+			/*
+			 * We can't do anything more now, since we don't know where the
+			 * tuple will be written.
+			 */
+			MemoryContextSwitchTo(old_cxt);
+			return;
+		}
+	}
+
+	/*
+	 * Now we will write the tuple, and then check to see if it is the
+	 * B tuple in any new or known pair.  When we resolve a known pair,
+	 * we will be able to write that pair's A tuple, and then we have to
+	 * check if it resolves some other pair.  Hence, we need a loop here.
+	 */
+	old_tid = old_tuple->t_self;
+	free_new = false;
+
+	for (;;)
+	{
+		ItemPointerData new_tid;
+
+		/* Insert the tuple and find out where it's put in new_heap */
+		raw_heap_insert(state, new_tuple);
+		new_tid = new_tuple->t_self;
+
+		/*
+		 * If the tuple is the updated version of a row, and the prior
+		 * version wouldn't be DEAD yet, then we need to either resolve
+		 * the prior version (if it's waiting in rs_unresolved_tups),
+		 * or make an entry in rs_old_new_tid_map (so we can resolve it
+		 * when we do see it).  The previous tuple's xmax would equal this
+		 * one's xmin, so it's RECENTLY_DEAD if and only if the xmin is
+		 * not before OldestXmin.
+		 */
+		if ((new_tuple->t_data->t_infomask & HEAP_UPDATED) &&
+			!TransactionIdPrecedes(HeapTupleHeaderGetXmin(new_tuple->t_data),
+								   state->rs_oldest_xmin))
+		{
+			/*
+			 * Okay, this is B in an update pair.  See if we've seen A.
+			 */
+			UnresolvedTup unresolved;
+
+			memset(&hashkey, 0, sizeof(hashkey));
+			hashkey.xmin = HeapTupleHeaderGetXmin(new_tuple->t_data);
+			hashkey.tid = old_tid;
+
+			unresolved = hash_search(state->rs_unresolved_tups, &hashkey,
+									 HASH_FIND, NULL);
+
+			if (unresolved != NULL)
+			{
+				/*
+				 * We have seen and memorized the previous tuple already.
+				 * Now that we know where we inserted the tuple its t_ctid
+				 * points to, fix its t_ctid and insert it to the new heap.
+				 */
+				if (free_new)
+					heap_freetuple(new_tuple);
+				new_tuple = unresolved->tuple;
+				free_new = true;
+				old_tid = unresolved->old_tid;
+				new_tuple->t_data->t_ctid = new_tid;
+
+				/*
+				 * We don't need the hash entry anymore, but don't free
+				 * its tuple just yet.
+				 */
+				hash_search(state->rs_unresolved_tups, &hashkey,
+							HASH_REMOVE, &found);
+				Assert(found);
+
+				/* loop back to insert the previous tuple in the chain */
+				continue;
+			}
+			else
+			{
+				/*
+				 * Remember the new tid of this tuple. We'll use it to set
+				 * the ctid when we find the previous tuple in the chain.
+				 */
+				OldToNewMapping mapping;
+
+				mapping = hash_search(state->rs_old_new_tid_map, &hashkey,
+									  HASH_ENTER, &found);
+				Assert(!found);
+
+				mapping->new_tid = new_tid;
+			}
+		}
+
+		/* Done with this (chain of) tuples, for now */
+		if (free_new)
+			heap_freetuple(new_tuple);
+		break;
+	}
+
+	MemoryContextSwitchTo(old_cxt);
+}
+
+/*
+ * Register a dead tuple with an ongoing rewrite. Dead tuples are not
+ * copied to the new table, but we still make note of them so that we
+ * can release some resources earlier.
+ */
+void
+rewrite_heap_dead_tuple(RewriteState state, HeapTuple old_tuple)
+{
+	/*
+	 * If we have already seen an earlier tuple in the update chain that
+	 * points to this tuple, let's forget about that earlier tuple. It's
+	 * in fact dead as well, our simple xmax < OldestXmin test in
+	 * HeapTupleSatisfiesVacuum just wasn't enough to detect it. It
+	 * happens when xmin of a tuple is greater than xmax, which sounds
+	 * counter-intuitive but is perfectly valid.
+	 *
+	 * We don't bother to try to detect the situation the other way
+	 * round, when we encounter the dead tuple first and then the
+	 * recently dead one that points to it. If that happens, we'll
+	 * have some unmatched entries in the UnresolvedTups hash table
+	 * at the end. That can happen anyway, because a vacuum might
+	 * have removed the dead tuple in the chain before us.
+	 */
+	UnresolvedTup unresolved;
+	TidHashKey hashkey;
+	bool found;
+
+	memset(&hashkey, 0, sizeof(hashkey));
+	hashkey.xmin = HeapTupleHeaderGetXmin(old_tuple->t_data);
+	hashkey.tid = old_tuple->t_self;
+
+	unresolved = hash_search(state->rs_unresolved_tups, &hashkey,
+							 HASH_FIND, NULL);
+
+	if (unresolved != NULL)
+	{
+		/* Need to free the contained tuple as well as the hashtable entry */
+		heap_freetuple(unresolved->tuple);
+		hash_search(state->rs_unresolved_tups, &hashkey,
+					HASH_REMOVE, &found);
+		Assert(found);
+	}
+}
+
+/*
+ * Insert a tuple to the new relation.  This has to track heap_insert
+ * and its subsidiary functions!
+ *
+ * t_self of the tuple is set to the new TID of the tuple. If t_ctid of the
+ * tuple is invalid on entry, it's replaced with the new TID as well (in
+ * the inserted data only, not in the caller's copy).
+ */
+static void
+raw_heap_insert(RewriteState state, HeapTuple tup)
+{
+	Page			page = state->rs_buffer;
+	Size			pageFreeSpace, saveFreeSpace;
+	Size			len;
+	OffsetNumber	newoff;
+	HeapTuple		heaptup;
+
+	/*
+	 * If the new tuple is too big for storage or contains already toasted
+	 * out-of-line attributes from some other relation, invoke the toaster.
+	 *
+	 * Note: below this point, heaptup is the data we actually intend to store
+	 * into the relation; tup is the caller's original untoasted data.
+	 */
+	if (state->rs_new_rel->rd_rel->relkind == RELKIND_TOASTVALUE)
+	{
+		/* toast table entries should never be recursively toasted */
+		Assert(!HeapTupleHasExternal(tup));
+		heaptup = tup;
+	}
+	else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD)
+		heaptup = toast_insert_or_update(state->rs_new_rel, tup, NULL,
+										 state->rs_use_wal, false);
+	else
+		heaptup = tup;
+
+	len = MAXALIGN(heaptup->t_len);		/* be conservative */
+
+	/*
+	 * If we're gonna fail for oversize tuple, do it right away
+	 */
+	if (len > MaxHeapTupleSize)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+				 errmsg("row is too big: size %lu, maximum size %lu",
+						(unsigned long) len,
+						(unsigned long) MaxHeapTupleSize)));
+
+	/* Compute desired extra freespace due to fillfactor option */
+	saveFreeSpace = RelationGetTargetPageFreeSpace(state->rs_new_rel,
+												   HEAP_DEFAULT_FILLFACTOR);
+
+	/* Now we can check to see if there's enough free space already. */
+	if (state->rs_buffer_valid)
+	{
+		pageFreeSpace = PageGetFreeSpace(page);
+
+		if (len + saveFreeSpace > pageFreeSpace)
+		{
+			/* Doesn't fit, so write out the existing page */
+
+			/* XLOG stuff */
+			if (state->rs_use_wal)
+				log_newpage(&state->rs_new_rel->rd_node,
+							state->rs_blockno,
+							page);
+
+			/*
+			 * Now write the page. We say isTemp = true even if it's not a
+			 * temp table, because there's no need for smgr to schedule an
+			 * fsync for this write; we'll do it ourselves before committing.
+			 */
+			smgrextend(state->rs_new_rel->rd_smgr, state->rs_blockno,
+					   (char *) page, true);
+
+			state->rs_blockno++;
+			state->rs_buffer_valid = false;
+		}
+	}
+
+	if (!state->rs_buffer_valid)
+	{
+		/* Initialize a new empty page */
+		PageInit(page, BLCKSZ, 0);
+		state->rs_buffer_valid = true;
+	}
+
+	/* And now we can insert the tuple into the page */
+	newoff = PageAddItem(page, (Item) heaptup->t_data, len,
+						 InvalidOffsetNumber, LP_USED);
+	if (newoff == InvalidOffsetNumber)
+		elog(ERROR, "failed to add tuple");
+
+	/* Update caller's t_self to the actual position where it was stored */
+	ItemPointerSet(&(tup->t_self), state->rs_blockno, newoff);
+
+	/*
+	 * Insert the correct position into CTID of the stored tuple, too,
+	 * if the caller didn't supply a valid CTID.
+	 */
+	if(!ItemPointerIsValid(&tup->t_data->t_ctid))
+	{
+		ItemId			newitemid;
+		HeapTupleHeader onpage_tup;
+
+		newitemid = PageGetItemId(page, newoff);
+		onpage_tup = (HeapTupleHeader) PageGetItem(page, newitemid);
+
+		onpage_tup->t_ctid = tup->t_self;
+	}
+
+	/* If heaptup is a private copy, release it. */
+	if (heaptup != tup)
+		heap_freetuple(heaptup);
+}
diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index a917e27a76..148b8e3266 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -57,13 +57,14 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/nbtree/nbtsort.c,v 1.110 2007/01/09 02:14:10 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/nbtree/nbtsort.c,v 1.111 2007/04/08 01:26:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/nbtree.h"
 #include "miscadmin.h"
 #include "storage/smgr.h"
@@ -265,32 +266,7 @@ _bt_blwritepage(BTWriteState *wstate, Page page, BlockNumber blkno)
 	if (wstate->btws_use_wal)
 	{
 		/* We use the heap NEWPAGE record type for this */
-		xl_heap_newpage xlrec;
-		XLogRecPtr	recptr;
-		XLogRecData rdata[2];
-
-		/* NO ELOG(ERROR) from here till newpage op is logged */
-		START_CRIT_SECTION();
-
-		xlrec.node = wstate->index->rd_node;
-		xlrec.blkno = blkno;
-
-		rdata[0].data = (char *) &xlrec;
-		rdata[0].len = SizeOfHeapNewpage;
-		rdata[0].buffer = InvalidBuffer;
-		rdata[0].next = &(rdata[1]);
-
-		rdata[1].data = (char *) page;
-		rdata[1].len = BLCKSZ;
-		rdata[1].buffer = InvalidBuffer;
-		rdata[1].next = NULL;
-
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_NEWPAGE, rdata);
-
-		PageSetLSN(page, recptr);
-		PageSetTLI(page, ThisTimeLineID);
-
-		END_CRIT_SECTION();
+		log_newpage(&wstate->index->rd_node, blkno, page);
 	}
 	else
 	{
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index ac771b77a6..656d7d710f 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.158 2007/03/29 00:15:37 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.159 2007/04/08 01:26:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,6 +19,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/rewriteheap.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
@@ -29,13 +30,14 @@
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "miscadmin.h"
+#include "storage/procarray.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
-#include "utils/syscache.h"
 #include "utils/relcache.h"
+#include "utils/syscache.h"
 
 
 /*
@@ -76,7 +78,7 @@ static List *get_tables_to_cluster(MemoryContext cluster_context);
  *
  * The single-relation case does not have any such overhead.
  *
- * We also allow a relation being specified without index.	In that case,
+ * We also allow a relation to be specified without index.  In that case,
  * the indisclustered bit will be looked up, and an ERROR will be thrown
  * if there is no index with the bit set.
  *---------------------------------------------------------------------------
@@ -650,11 +652,12 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
 	TupleDesc	newTupDesc;
 	int			natts;
 	Datum	   *values;
-	char	   *nulls;
+	bool	   *isnull;
 	IndexScanDesc scan;
 	HeapTuple	tuple;
-	CommandId	mycid = GetCurrentCommandId();
 	bool		use_wal;
+	TransactionId OldestXmin;
+	RewriteState rwstate;
 
 	/*
 	 * Open the relations we need.
@@ -671,84 +674,137 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
 	newTupDesc = RelationGetDescr(NewHeap);
 	Assert(newTupDesc->natts == oldTupDesc->natts);
 
-	/* Preallocate values/nulls arrays */
+	/* Preallocate values/isnull arrays */
 	natts = newTupDesc->natts;
-	values = (Datum *) palloc0(natts * sizeof(Datum));
-	nulls = (char *) palloc(natts * sizeof(char));
-	memset(nulls, 'n', natts * sizeof(char));
+	values = (Datum *) palloc(natts * sizeof(Datum));
+	isnull = (bool *) palloc(natts * sizeof(bool));
 
 	/*
 	 * We need to log the copied data in WAL iff WAL archiving is enabled AND
-	 * it's not a temp rel.  (Since we know the target relation is new and
-	 * can't have any FSM data, we can always tell heap_insert to ignore FSM,
-	 * even when using WAL.)
+	 * it's not a temp rel.
 	 */
 	use_wal = XLogArchivingActive() && !NewHeap->rd_istemp;
 
 	/* use_wal off requires rd_targblock be initially invalid */
 	Assert(NewHeap->rd_targblock == InvalidBlockNumber);
 
+	/* Get the cutoff xmin we'll use to weed out dead tuples */
+	OldestXmin = GetOldestXmin(OldHeap->rd_rel->relisshared, true);
+
+	/* Initialize the rewrite operation */
+	rwstate = begin_heap_rewrite(NewHeap, OldestXmin, use_wal);
+
 	/*
-	 * Scan through the OldHeap on the OldIndex and copy each tuple into the
-	 * NewHeap.
+	 * Scan through the OldHeap in OldIndex order and copy each tuple into the
+	 * NewHeap.  To ensure we see recently-dead tuples that still need to be
+	 * copied, we scan with SnapshotAny and use HeapTupleSatisfiesVacuum
+	 * for the visibility test.
 	 */
 	scan = index_beginscan(OldHeap, OldIndex,
-						   SnapshotNow, 0, (ScanKey) NULL);
+						   SnapshotAny, 0, (ScanKey) NULL);
 
 	while ((tuple = index_getnext(scan, ForwardScanDirection)) != NULL)
 	{
+		HeapTuple	copiedTuple;
+		bool		isdead;
+		int			i;
+
+		CHECK_FOR_INTERRUPTS();
+
+		LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
+
+		switch (HeapTupleSatisfiesVacuum(tuple->t_data, OldestXmin,
+										 scan->xs_cbuf))
+		{
+			case HEAPTUPLE_DEAD:
+				/* Definitely dead */
+				isdead = true;
+				break;
+			case HEAPTUPLE_LIVE:
+			case HEAPTUPLE_RECENTLY_DEAD:
+				/* Live or recently dead, must copy it */
+				isdead = false;
+				break;
+			case HEAPTUPLE_INSERT_IN_PROGRESS:
+				/*
+				 * We should not see this unless it's been inserted earlier
+				 * in our own transaction.
+				 */
+				if (!TransactionIdIsCurrentTransactionId(
+					HeapTupleHeaderGetXmin(tuple->t_data)))
+					elog(ERROR, "concurrent insert in progress");
+				/* treat as live */
+				isdead = false;
+				break;
+			case HEAPTUPLE_DELETE_IN_PROGRESS:
+				/*
+				 * We should not see this unless it's been deleted earlier
+				 * in our own transaction.
+				 */
+				Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI));
+				if (!TransactionIdIsCurrentTransactionId(
+					HeapTupleHeaderGetXmax(tuple->t_data)))
+					elog(ERROR, "concurrent delete in progress");
+				/* treat as recently dead */
+				isdead = false;
+				break;
+			default:
+				elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
+				isdead = false;		/* keep compiler quiet */
+				break;
+		}
+
+		LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
+
+		if (isdead)
+		{
+			/* heap rewrite module still needs to see it... */
+			rewrite_heap_dead_tuple(rwstate, tuple);
+			continue;
+		}
+
 		/*
-		 * We cannot simply pass the tuple to heap_insert(), for several
-		 * reasons:
-		 *
-		 * 1. heap_insert() will overwrite the commit-status fields of the
-		 * tuple it's handed.  This would trash the source relation, which is
-		 * bad news if we abort later on.  (This was a bug in releases thru
-		 * 7.0)
+		 * We cannot simply copy the tuple as-is, for several reasons:
 		 *
-		 * 2. We'd like to squeeze out the values of any dropped columns, both
+		 * 1. We'd like to squeeze out the values of any dropped columns, both
 		 * to save space and to ensure we have no corner-case failures. (It's
 		 * possible for example that the new table hasn't got a TOAST table
 		 * and so is unable to store any large values of dropped cols.)
 		 *
-		 * 3. The tuple might not even be legal for the new table; this is
+		 * 2. The tuple might not even be legal for the new table; this is
 		 * currently only known to happen as an after-effect of ALTER TABLE
 		 * SET WITHOUT OIDS.
 		 *
 		 * So, we must reconstruct the tuple from component Datums.
 		 */
-		HeapTuple	copiedTuple;
-		int			i;
-
-		heap_deformtuple(tuple, oldTupDesc, values, nulls);
+		heap_deform_tuple(tuple, oldTupDesc, values, isnull);
 
 		/* Be sure to null out any dropped columns */
 		for (i = 0; i < natts; i++)
 		{
 			if (newTupDesc->attrs[i]->attisdropped)
-				nulls[i] = 'n';
+				isnull[i] = true;
 		}
 
-		copiedTuple = heap_formtuple(newTupDesc, values, nulls);
+		copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
 
 		/* Preserve OID, if any */
 		if (NewHeap->rd_rel->relhasoids)
 			HeapTupleSetOid(copiedTuple, HeapTupleGetOid(tuple));
 
-		heap_insert(NewHeap, copiedTuple, mycid, use_wal, false);
+		/* The heap rewrite module does the rest */
+		rewrite_heap_tuple(rwstate, tuple, copiedTuple);
 
 		heap_freetuple(copiedTuple);
-
-		CHECK_FOR_INTERRUPTS();
 	}
 
 	index_endscan(scan);
 
-	pfree(values);
-	pfree(nulls);
+	/* Write out any remaining tuples, and fsync if needed */
+	end_heap_rewrite(rwstate);
 
-	if (!use_wal)
-		heap_sync(NewHeap);
+	pfree(values);
+	pfree(isnull);
 
 	index_close(OldIndex, NoLock);
 	heap_close(OldHeap, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 25e53a3dc4..8043799b88 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.218 2007/03/19 23:38:29 wieck Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.219 2007/04/08 01:26:32 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -5857,34 +5857,7 @@ copy_relation_data(Relation rel, SMgrRelation dst)
 
 		/* XLOG stuff */
 		if (use_wal)
-		{
-			xl_heap_newpage xlrec;
-			XLogRecPtr	recptr;
-			XLogRecData rdata[2];
-
-			/* NO ELOG(ERROR) from here till newpage op is logged */
-			START_CRIT_SECTION();
-
-			xlrec.node = dst->smgr_rnode;
-			xlrec.blkno = blkno;
-
-			rdata[0].data = (char *) &xlrec;
-			rdata[0].len = SizeOfHeapNewpage;
-			rdata[0].buffer = InvalidBuffer;
-			rdata[0].next = &(rdata[1]);
-
-			rdata[1].data = (char *) page;
-			rdata[1].len = BLCKSZ;
-			rdata[1].buffer = InvalidBuffer;
-			rdata[1].next = NULL;
-
-			recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_NEWPAGE, rdata);
-
-			PageSetLSN(page, recptr);
-			PageSetTLI(page, ThisTimeLineID);
-
-			END_CRIT_SECTION();
-		}
+			log_newpage(&dst->smgr_rnode, blkno, page);
 
 		/*
 		 * Now write the page.	We say isTemp = true even if it's not a temp
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 1fbb713b55..5ea66e7467 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/access/heapam.h,v 1.122 2007/04/06 04:21:43 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/access/heapam.h,v 1.123 2007/04/08 01:26:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -194,6 +194,7 @@ extern XLogRecPtr log_heap_clean(Relation reln, Buffer buffer,
 extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer,
 								  TransactionId cutoff_xid,
 								  OffsetNumber *offsets, int offcnt);
+extern XLogRecPtr log_newpage(RelFileNode *rnode, BlockNumber blk, Page page);
 
 /* in common/heaptuple.c */
 extern Size heap_compute_data_size(TupleDesc tupleDesc,
diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h
new file mode 100644
index 0000000000..e8f53246a8
--- /dev/null
+++ b/src/include/access/rewriteheap.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * rewriteheap.h
+ *	  Declarations for heap rewrite support functions
+ *
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/include/access/rewriteheap.h,v 1.1 2007/04/08 01:26:33 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REWRITE_HEAP_H
+#define REWRITE_HEAP_H
+
+#include "access/htup.h"
+#include "utils/rel.h"
+
+/* struct definition is private to rewriteheap.c */
+typedef struct RewriteStateData *RewriteState;
+
+extern RewriteState begin_heap_rewrite(Relation NewHeap,
+									   TransactionId OldestXmin, bool use_wal);
+extern void end_heap_rewrite(RewriteState state);
+extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple,
+							   HeapTuple newTuple);
+extern void rewrite_heap_dead_tuple(RewriteState state, HeapTuple oldTuple);
+
+#endif /* REWRITE_HEAP_H */
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index a9fca4d8fe..0eb83369fb 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -382,8 +382,60 @@ SELECT * FROM clstr_1;
  2
 (2 rows)
 
+-- Test MVCC-safety of cluster. There isn't much we can do to verify the
+-- results with a single backend...
+CREATE TABLE clustertest (key int PRIMARY KEY);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "clustertest_pkey" for table "clustertest"
+INSERT INTO clustertest VALUES (10);
+INSERT INTO clustertest VALUES (20);
+INSERT INTO clustertest VALUES (30);
+INSERT INTO clustertest VALUES (40);
+INSERT INTO clustertest VALUES (50);
+-- Use a transaction so that updates are not committed when CLUSTER sees 'em
+BEGIN;
+-- Test update where the old row version is found first in the scan
+UPDATE clustertest SET key = 100 WHERE key = 10;
+-- Test update where the new row version is found first in the scan
+UPDATE clustertest SET key = 35 WHERE key = 40;
+-- Test longer update chain 
+UPDATE clustertest SET key = 60 WHERE key = 50;
+UPDATE clustertest SET key = 70 WHERE key = 60;
+UPDATE clustertest SET key = 80 WHERE key = 70;
+SELECT * FROM clustertest;
+ key 
+-----
+  20
+  30
+ 100
+  35
+  80
+(5 rows)
+
+CLUSTER clustertest_pkey ON clustertest;
+SELECT * FROM clustertest;
+ key 
+-----
+  20
+  30
+  35
+  80
+ 100
+(5 rows)
+
+COMMIT;
+SELECT * FROM clustertest;
+ key 
+-----
+  20
+  30
+  35
+  80
+ 100
+(5 rows)
+
 -- clean up
 \c -
+DROP TABLE clustertest;
 DROP TABLE clstr_1;
 DROP TABLE clstr_2;
 DROP TABLE clstr_3;
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index 81a52c23df..8f4fc58912 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -153,8 +153,42 @@ INSERT INTO clstr_1 VALUES (1);
 CLUSTER clstr_1;
 SELECT * FROM clstr_1;
 
+-- Test MVCC-safety of cluster. There isn't much we can do to verify the
+-- results with a single backend...
+
+CREATE TABLE clustertest (key int PRIMARY KEY);
+
+INSERT INTO clustertest VALUES (10);
+INSERT INTO clustertest VALUES (20);
+INSERT INTO clustertest VALUES (30);
+INSERT INTO clustertest VALUES (40);
+INSERT INTO clustertest VALUES (50);
+
+-- Use a transaction so that updates are not committed when CLUSTER sees 'em
+BEGIN;
+
+-- Test update where the old row version is found first in the scan
+UPDATE clustertest SET key = 100 WHERE key = 10;
+
+-- Test update where the new row version is found first in the scan
+UPDATE clustertest SET key = 35 WHERE key = 40;
+
+-- Test longer update chain 
+UPDATE clustertest SET key = 60 WHERE key = 50;
+UPDATE clustertest SET key = 70 WHERE key = 60;
+UPDATE clustertest SET key = 80 WHERE key = 70;
+
+SELECT * FROM clustertest;
+CLUSTER clustertest_pkey ON clustertest;
+SELECT * FROM clustertest;
+
+COMMIT;
+
+SELECT * FROM clustertest;
+
 -- clean up
 \c -
+DROP TABLE clustertest;
 DROP TABLE clstr_1;
 DROP TABLE clstr_2;
 DROP TABLE clstr_3;