checksum_92.diff

text/plain

Filename: checksum_92.diff
Type: text/plain
Part: 0
Message: [WIP] Double-write with Fast Checksums

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: context
File+
src/backend/access/heap/heapam.c 7 0
src/backend/access/heap/rewriteheap.c 1 0
src/backend/access/transam/xlog.c 8 0
src/backend/commands/copy.c 1 0
src/backend/commands/tablecmds.c 3 0
src/backend/executor/execMain.c 1 0
src/backend/postmaster/postmaster.c 16 0
src/backend/storage/buffer/bufmgr.c 469 0
src/backend/storage/buffer/README.doublewrites 193 0
src/backend/storage/file/fd.c 225 0
src/backend/storage/lmgr/lwlock.c 4 0
src/backend/storage/smgr/md.c 49 0
src/backend/storage/smgr/smgr.c 390 0
src/backend/utils/misc/guc.c 42 0
src/backend/utils/misc/postgresql.conf.sample 4 0
src/backend/utils/resowner/resowner.c 5 0
src/include/access/heapam.h 1 0
src/include/c.h 2 0
src/include/storage/bufmgr.h 1 0
src/include/storage/bufpage.h 3 0
src/include/storage/fd.h 9 0
src/include/storage/lwlock.h 1 0
src/include/storage/smgr.h 55 0
*** a/src/backend/access/heap/heapam.c
--- b/src/backend/access/heap/heapam.c
***************
*** 5722,5737 **** heap2_desc(StringInfo buf, uint8 xl_info, char *rec)
   * That behavior might change someday, but in any case it's likely that
   * any fsync decisions required would be per-index and hence not appropriate
   * to be done here.)
   */
  void
! heap_sync(Relation rel)
  {
  	/* non-WAL-logged tables never need fsync */
  	if (!RelationNeedsWAL(rel))
  		return;
  
  	/* main heap */
! 	FlushRelationBuffers(rel);
  	/* FlushRelationBuffers will have opened rd_smgr */
  	smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
  
--- 5722,5741 ----
   * That behavior might change someday, but in any case it's likely that
   * any fsync decisions required would be per-index and hence not appropriate
   * to be done here.)
+  *
+  * If needsDoubleWrite is true, the changed buffers should use
+  * double-writing if that option is enabled.  Currently, relation changes
+  * that were not WAL-logged do not need double writes.
   */
  void
! heap_sync(Relation rel, bool needsDoubleWrite)
  {
  	/* non-WAL-logged tables never need fsync */
  	if (!RelationNeedsWAL(rel))
  		return;
  
  	/* main heap */
! 	FlushRelationBuffers(rel, needsDoubleWrite);
  	/* FlushRelationBuffers will have opened rd_smgr */
  	smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
  
***************
*** 5743,5749 **** heap_sync(Relation rel)
  		Relation	toastrel;
  
  		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
! 		FlushRelationBuffers(toastrel);
  		smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
  		heap_close(toastrel, AccessShareLock);
  	}
--- 5747,5753 ----
  		Relation	toastrel;
  
  		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
! 		FlushRelationBuffers(toastrel, needsDoubleWrite);
  		smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
  		heap_close(toastrel, AccessShareLock);
  	}
*** a/src/backend/access/heap/rewriteheap.c
--- b/src/backend/access/heap/rewriteheap.c
***************
*** 288,294 **** end_heap_rewrite(RewriteState state)
  	 * wrote before the checkpoint.
  	 */
  	if (RelationNeedsWAL(state->rs_new_rel))
! 		heap_sync(state->rs_new_rel);
  
  	/* Deleting the context frees everything */
  	MemoryContextDelete(state->rs_cxt);
--- 288,294 ----
  	 * wrote before the checkpoint.
  	 */
  	if (RelationNeedsWAL(state->rs_new_rel))
! 		heap_sync(state->rs_new_rel, state->rs_use_wal);
  
  	/* Deleting the context frees everything */
  	MemoryContextDelete(state->rs_cxt);
*** a/src/backend/access/transam/xlog.c
--- b/src/backend/access/transam/xlog.c
***************
*** 6256,6261 **** StartupXLOG(void)
--- 6256,6269 ----
  		InRecovery = true;
  	}
  
+ 	/*
+ 	 * If double write file exists, see if there are any pages to be recovered
+ 	 * because of torn writes.	This must be done whether or not double_writes
+ 	 * or full_page_writes is currently enabled, in order to recover any torn
+ 	 * pages if double_writes was enabled during last crash.
+ 	 */
+ 	RecoverDoubleWriteFile();
+ 
  	/* REDO */
  	if (InRecovery)
  	{
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2138,2144 **** CopyFrom(CopyState cstate)
  	 * indexes since those use WAL anyway)
  	 */
  	if (hi_options & HEAP_INSERT_SKIP_WAL)
! 		heap_sync(cstate->rel);
  
  	return processed;
  }
--- 2138,2144 ----
  	 * indexes since those use WAL anyway)
  	 */
  	if (hi_options & HEAP_INSERT_SKIP_WAL)
! 		heap_sync(cstate->rel, false);
  
  	return processed;
  }
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 3759,3765 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
  
  		/* If we skipped writing WAL, then we need to sync the heap. */
  		if (hi_options & HEAP_INSERT_SKIP_WAL)
! 			heap_sync(newrel);
  
  		heap_close(newrel, NoLock);
  	}
--- 3759,3765 ----
  
  		/* If we skipped writing WAL, then we need to sync the heap. */
  		if (hi_options & HEAP_INSERT_SKIP_WAL)
! 			heap_sync(newrel, false);
  
  		heap_close(newrel, NoLock);
  	}
***************
*** 8384,8390 **** ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
  	 * in shared buffers.  We assume no new changes will be made while we are
  	 * holding exclusive lock on the rel.
  	 */
! 	FlushRelationBuffers(rel);
  
  	/*
  	 * Relfilenodes are not unique across tablespaces, so we need to allocate
--- 8384,8390 ----
  	 * in shared buffers.  We assume no new changes will be made while we are
  	 * holding exclusive lock on the rel.
  	 */
! 	FlushRelationBuffers(rel, true);
  
  	/*
  	 * Relfilenodes are not unique across tablespaces, so we need to allocate
***************
*** 8486,8492 **** copy_relation_data(SMgrRelation src, SMgrRelation dst,
  		/* If we got a cancel signal during the copy of the data, quit */
  		CHECK_FOR_INTERRUPTS();
  
! 		smgrread(src, forkNum, blkno, buf);
  
  		/* XLOG stuff */
  		if (use_wal)
--- 8486,8492 ----
  		/* If we got a cancel signal during the copy of the data, quit */
  		CHECK_FOR_INTERRUPTS();
  
! 		smgrread(src, forkNum, blkno, buf, NULL);
  
  		/* XLOG stuff */
  		if (use_wal)
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 2690,2696 **** CloseIntoRel(QueryDesc *queryDesc)
  
  		/* If we skipped using WAL, must heap_sync before commit */
  		if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
! 			heap_sync(myState->rel);
  
  		/* close rel, but keep lock until commit */
  		heap_close(myState->rel, NoLock);
--- 2690,2696 ----
  
  		/* If we skipped using WAL, must heap_sync before commit */
  		if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
! 			heap_sync(myState->rel, false);
  
  		/* close rel, but keep lock until commit */
  		heap_close(myState->rel, NoLock);
*** a/src/backend/postmaster/postmaster.c
--- b/src/backend/postmaster/postmaster.c
***************
*** 314,319 **** extern char *optarg;
--- 314,323 ----
  extern int	optind,
  			opterr;
  
+ extern int	page_checksum;
+ extern int	doubleWrites;
+ extern int	batched_buffer_writes;
+ 
  #ifdef HAVE_INT_OPTRESET
  extern int	optreset;			/* might not be declared by system headers */
  #endif
***************
*** 765,770 **** PostmasterMain(int argc, char *argv[])
--- 769,786 ----
  		ereport(ERROR,
  				(errmsg("WAL streaming (max_wal_senders > 0) requires wal_level \"archive\" or \"hot_standby\"")));
  
+ 	/* The double-write option currently requires data page checksums. */
+ 	if (doubleWrites && !page_checksum)
+ 		ereport(ERROR,
+ 				(errmsg("page_checksum must be enabled if double_writes is enabled")));
+ 
+ 	/*
+ 	 * The double-write option requires batching of buffer writes, so force
+ 	 * batched_buffer_writes to a default 32 if not already set.
+ 	 */
+ 	if (doubleWrites && batched_buffer_writes == 0)
+ 		batched_buffer_writes = 32;
+ 
  	/*
  	 * Other one-time internal sanity checks can go here, if they are fast.
  	 * (Put any slow processing further down, after postmaster.pid creation.)
*** /dev/null
--- b/src/backend/storage/buffer/README.doublewrites
***************
*** 0 ****
--- 1,193 ----
+ DOUBLE WRITES
+ 
+ The "double_write" GUC option enables the use of double write
+ functionality, which can be used as a replacement for full page writes.
+ The idea of this option is to handle the problem of torn writes for
+ buffer pages by writing (almost) all buffers twice, once to a
+ double-write file and once to the data file.  The two writes are done in
+ a strictly sequential order, ensuring that the buffer is successfully
+ written to the double-write file before being written to the data file.
+ The "double_write" option (like full page writes) is only a crash
+ recovery feature -- it does not affect the database contents unless there
+ is a crash.
+ 
+ The double-write file has checksummed contents, and in our
+ implementation, the pages in the data files are also be checksummed,
+ though this is not strictly required.  If a crash occurs while buffer
+ writes (using double writes) are in progress, then a buffer page may have
+ a torn page in either the double-write file or in its data file.
+ However, because of the ordering of writes, there can only be a torn page
+ for that buffer in the double-write file or the data file, not both.
+ Therefore, during crash recovery, we can scan the double-write file.  If
+ a page in the double-write file has a correct checksum, and the
+ corresponding page in the data file has an incorrect checksum, then we
+ can restore the torn data page from the double-write file.  Any pages in
+ the double-write file that have incorrect checksum are ignored (since
+ they are likely torn pages).
+ 
+ The net result is that "double_write" option fixes all torn data pages,
+ and can therefore be used in place of "full_page_writes".  Using
+ double_writes typically improves performance, as compared to
+ "full_page_writes", and also greatly reduces the size of the WAL log.
+ 
+ As currently written, the double_write option makes use of checksums on
+ the data pages.  Double writes only strictly require that the pages in
+ the double-write file be checksummed, and we could fairly easily change
+ the implementation to make data checksums optional.  However, if data
+ checksums are used, then Postgres can provide more useful messages on
+ exactly when torn pages have occurred.  It is very likely that a torn
+ page happened if, during recovery, the checksum of a data page is
+ incorrect, but a copy of the page with a valid checksum is in the
+ double-write file.  If there are no data checksums, then Postgres would
+ would still copy any valid page in the double-write file to the
+ appropriate page in a data file, but it cannot actually know if a torn
+ page occurred.
+ 
+ 
+ BATCHING WRITES FOR EFFICIENCY
+ 
+ Our implementation achieves efficiency for double writes by having the
+ checkpointer and bgwriter batch writes of multiple pages together.
+ Currently, there is an option "batched_buffer_writes" that specifies how
+ many buffers to batch at a time.  If double_writes is enabled,
+ batched_buffer_writes is forced to a default value of 32 if it is not
+ already set.  We could choose to completely remove batched_buffer_writes
+ as an option.
+ 
+ In order to batch multiple buffer writes without copying, the
+ checkpointer/bgwriter must acquire multiple buffer locks simultaneously
+ as it is building up the batch.  Because it is acquiring multiple buffer
+ locks, there is the possibility of deadlock.  We avoid the possibility of
+ deadlock by doing a conditional acquire whenever we are acquiring the
+ buffer content_lock for a batch.  If the conditional acquire fails (and
+ we already have acquired some buffer locks), then we push out the current
+ batch immediately, so that we can release those buffer locks.  We are
+ then guaranteed that we cannot deadlock as we again try to acquire the
+ lock.  The situation of a failed conditional acquire almost never happens
+ in practice.  The maximum number of batched buffers is set at
+ MAX_BATCHED_WRITES.  The MAX_SIMUL_LWLOCKS limit in lwlock.c must be
+ increased to be at least 2 * MAX_BATCHED_WRITES, since the
+ checkpointer/bgwriter holds two lwlocks for each buffer that it is
+ batching.
+ 
+ The actual batch writes are done using writev(), which might have to be
+ replaced with equivalent code, if this is a portability issue.  A struct
+ iocb structure is currently used for bookkeeping during the low-level
+ batching, since it is compatible with an async IO approach as well (not
+ included).
+ 
+ Given the batching functionality, double writes by the
+ checkpointer/bgwriter is implemented efficiently by writing a batch of
+ pages to the double-write file and fsyncing, and then writing the pages
+ to the appropriate data files, and then fsyncing all the necessary data
+ files.  While the data fsyncing might be viewed as expensive, it does
+ help eliminate a lot of the fsync overhead at the end of checkpoints.
+ FlushRelationBuffers() and FlushDatabaseBuffers() can be similarly
+ batched.  The maximum size of the double-write file is MAX_BATCHED_WRITES
+ * BLCKSZ.
+ 
+ Note that direct IO (the O_DIRECT option in Linux) could be used for the
+ double-write file, if there were no portability concerns.  In that case,
+ we write the whole set of buffers to the double-write file in a single
+ direct write, and no fsync in necessary.  We would avoid copying
+ overhead, and any use of the buffer cache.  We would in that case need to
+ make sure that the double-write file is fully-allocated ahead of time
+ (which is the only case when Linux actually guarantees that IO will be
+ direct to disk).  Or we could do the fsync just to guarantee that that
+ the IO makes it to disk.
+ 
+ The fsyncing of the data files is necessary to be sure that the
+ double-write file can be reused for the next batch of buffers.  You must
+ be sure that the buffers have been successfully written to the data files
+ on disk before reusing the double-write file.  Strictly speaking, the
+ data files only need to be fsync'ed by the time that the double-write
+ file must be used again.  However, there is probably not much to be
+ gained by delaying the fsync unless multiple double-write files are used
+ (see below).
+ 
+ We have some other code (not included) that sorts buffers to be
+ checkpointed in file/block order -- this can reduce fsync overhead
+ further by ensuring that each batch writes to only one or a few data
+ files, and potentially sequential or nearby locations in the data files.
+ 
+ 
+ DOUBLE-WRITE FILE
+ 
+ When we write the double-write file, we include a header that identifies
+ each block in the double-write file and a checksum for each block (and is
+ checksummed itself).  We use this header during recovery to determine the
+ contents of the double-write file and whether each block was fully
+ written (i.e. not torn).  The length of the double-write header is set as
+ 4096 bytes, and must be large enough to hold information on
+ MAX_BATCHED_WRITES blocks.  (See Assert() in smgrbwrite().)
+ 
+ 
+ WHEN TO DO DOUBLE WRITES
+ 
+ Double writes must be done for any page which might be used after
+ recovery even if there was a full crash while writing the page.  This
+ includes all writes to such pages in a checkpoint, not just the first
+ write.  Pages in temporary tables and some unlogged operations do not
+ require double writes.  This is controlled by flags in heap_sync() and
+ FlushRelationBuffers().
+ 
+ We do have to do the same double write for dirty buffer evictions by
+ individual backends.  This could be expensive, if there are a lot of
+ dirty buffer evictions (i.e. where the checkpoint/bgwriter can generate
+ enough clean pages for the backends).
+ 
+ There is currently just one double-write file, which is protected by a
+ global lock DoubleWriteLock.  When the checkpointer, bgwriter, or a
+ backend wants to double-write a batch of buffers, it acquires
+ DoubleWriteLock, double-writes the batch, and releases the lock.  In
+ order to avoid contention (when there are many dirty buffer evictions),
+ it might be useful to have one double write file for each backend and for
+ the checkpointer and bgwriter.  In that case, the DoubleWriteLock would
+ no longer be needed, but all double-write files would have to be examined
+ during recovery.
+ 
+ 
+ PERFORMANCE
+ 
+ We have seen significant performance gains for OLTP runs with sufficient
+ buffer cache size.  In these runs, there are few dirty evictions, and the
+ checkpointer is doing almost all of the buffer writes.  In that case, the
+ number of writes is not being greatly increased.  We are doing each
+ buffer write twice, but we are eliminating the full page write that goes to
+ WAL log for each modification of a buffer in a checkpoint.  Also, the
+ double-write file is written sequentially in a single write, so it is
+ highly efficient.  Though we are doing many fsyncs, we are doing them in
+ the context of the checkpointer (and possibly in the bgwriter).
+ Meanwhile, we have removed the latency added to each transaction when the
+ backend must do a full page write to the WAL log for the first
+ modification of a buffer.
+ 
+ OPTIONS
+ 
+ The three options in this patch are double_writes,
+ double_write_directory, and batched_buffer_writes.
+ 
+ double_writes controls whether double writes are used for buffer writes.
+ It requires that page_checksum is enabled, and that batched_buffer_writes
+ is non-zero.  As mentioned above, we could just force
+ batched_buffer_writes to a default value (32) if double_writes is
+ enabled.  Generally, the user would turn off full_page_writes is
+ double_writes is enabled.
+ 
+ double_write_directory controls the directory where the double-write file
+ is located.  If it is not set, then the double-write file is in the base
+ directory.  This option could be used to put the double-write file on some
+ high-speed media (such as SSD).
+ 
+ The batched_buffer_writes option controls whether buffer writes are
+ batched, and the maximum number of buffers that are batched at one time.
+ 
+ 
+  - Explain may be slower on ext2/ext3 because of all the fsyncs.  (We
+    find it is OK, especially if logs and data are on separate ext3s.)
+ 
+ TODO:
+  - fix resowner.c code
+  - Make fd.c code cleaer (don't use struct iocb)
+  - add back in ioseq code (sorting buffers before syncing)
+  - changes to remember if individual buffer doesn't need double-write?
*** a/src/backend/storage/buffer/bufmgr.c
--- b/src/backend/storage/buffer/bufmgr.c
***************
*** 45,50 ****
--- 45,51 ----
  #include "storage/proc.h"
  #include "storage/smgr.h"
  #include "storage/standby.h"
+ #include "utils/memutils.h"
  #include "utils/rel.h"
  #include "utils/resowner.h"
  #include "utils/timestamp.h"
***************
*** 75,82 **** double		bgwriter_lru_multiplier = 2.0;
   */
  int			target_prefetch_pages = 0;
  
  /* local state for StartBufferIO and related functions */
! static volatile BufferDesc *InProgressBuf = NULL;
  static bool IsForInput;
  
  /* local state for LockBufferForCleanup */
--- 76,87 ----
   */
  int			target_prefetch_pages = 0;
  
+ extern bool doubleWrites;
+ 
  /* local state for StartBufferIO and related functions */
! /* Stack of buffers on which we have called StartBufferIO(). */
! static volatile BufferDesc *InProgressBuf[MAX_BATCHED_WRITES];
! static int	InProgressIndex = 0;
  static bool IsForInput;
  
  /* local state for LockBufferForCleanup */
***************
*** 107,112 **** static volatile BufferDesc *BufferAlloc(SMgrRelation smgr,
--- 112,125 ----
  static void FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln);
  static void AtProcExit_Buffers(int code, Datum arg);
  
+ static void FlushBatch(bool doUnpin);
+ static int	SyncOneBufferPrepare(int buf_id, bool skip_recently_used);
+ static int	SyncOneBufferBatched(int buf_id, bool doBatch);
+ static bool FlushBufferPrepare(int buf_id, volatile BufferDesc *buf,
+ 				   char *callerBuf,
+ 				   SMgrRelation reln);
+ static void CheckDoubleWriteFile(void);
+ 
  
  /*
   * PrefetchBuffer -- initiate asynchronous read of a block of a relation
***************
*** 437,443 **** ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
  			MemSet((char *) bufBlock, 0, BLCKSZ);
  		else
  		{
! 			smgrread(smgr, forkNum, blockNum, (char *) bufBlock);
  
  			/* check for garbage data */
  			if (!PageHeaderIsValid((PageHeader) bufBlock))
--- 450,456 ----
  			MemSet((char *) bufBlock, 0, BLCKSZ);
  		else
  		{
! 			smgrread(smgr, forkNum, blockNum, (char *) bufBlock, NULL);
  
  			/* check for garbage data */
  			if (!PageHeaderIsValid((PageHeader) bufBlock))
***************
*** 651,658 **** BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
  												smgr->smgr_rnode.node.dbNode,
  											  smgr->smgr_rnode.node.relNode);
  
! 				FlushBuffer(buf, NULL);
! 				LWLockRelease(buf->content_lock);
  
  				TRACE_POSTGRESQL_BUFFER_WRITE_DIRTY_DONE(forkNum, blockNum,
  											   smgr->smgr_rnode.node.spcNode,
--- 664,693 ----
  												smgr->smgr_rnode.node.dbNode,
  											  smgr->smgr_rnode.node.relNode);
  
! 				if (doubleWrites)
! 				{
! 					CheckDoubleWriteFile();
! 
! 					/*
! 					 * Make a one-element batch, so we can do the double
! 					 * writes.	We are serializing on the double write file in
! 					 * FlushBatch().  We could also have a small double write
! 					 * file for each backend.
! 					 */
! 					if (FlushBufferPrepare(buf->buf_id, buf, NULL, NULL))
! 					{
! 						FlushBatch(false);
! 					}
! 					else
! 					{
! 						LWLockRelease(buf->content_lock);
! 					}
! 				}
! 				else
! 				{
! 					FlushBuffer(buf, NULL);
! 					LWLockRelease(buf->content_lock);
! 				}
  
  				TRACE_POSTGRESQL_BUFFER_WRITE_DIRTY_DONE(forkNum, blockNum,
  											   smgr->smgr_rnode.node.spcNode,
***************
*** 1165,1170 **** UnpinBuffer(volatile BufferDesc *buf, bool fixOwner)
--- 1200,1310 ----
  	}
  }
  
+ extern bool page_checksum;
+ extern int	batched_buffer_writes;
+ 
+ /* File to which double writes are going */
+ File		doubleWriteFile = 0;
+ 
+ /*
+  * Pointer to array of buffers in to which to do checksumming of
+  * batched buffers.
+  */
+ char	   *origCksumBuf,
+ 		   *cksumBuf;
+ 
+ /* Array describing the buffer writes that are being batched. */
+ struct SMgrWriteList writeList[MAX_BATCHED_WRITES];
+ 
+ /* Number of buffers in the current batch so far. */
+ int			writeIndex;
+ 
+ 
+ 
+ /*
+  * Allocate the buffers used for checksumming a batch of writes.
+  */
+ static void
+ AllocCksumBuf()
+ {
+ 	if (batched_buffer_writes > 0 && page_checksum && cksumBuf == NULL)
+ 	{
+ 		origCksumBuf = (char *) palloc(batched_buffer_writes * BLCKSZ + ALIGNOF_BUFFER);
+ 		cksumBuf = (char *) TYPEALIGN(ALIGNOF_BUFFER, origCksumBuf);
+ 	}
+ }
+ 
+ /*
+  * Release the buffers used for checksumming a batch of writes.
+  */
+ static void
+ FreeCksumBuf()
+ {
+ 	if (cksumBuf != NULL)
+ 	{
+ 		pfree(origCksumBuf);
+ 		cksumBuf = origCksumBuf = NULL;
+ 	}
+ }
+ 
+ /*
+  * If double_writes is on, and the double-write file is not yet open in
+  * this process, open up the double-write file.
+  */
+ static void
+ CheckDoubleWriteFile()
+ {
+ 	if (doubleWrites)
+ 	{
+ 		if (doubleWriteFile == 0)
+ 		{
+ 			char	   *name = DoubleWriteFileName();
+ 
+ 			doubleWriteFile = PathNameOpenFile(name,
+ 											   O_RDWR | O_CREAT,
+ 											   S_IRUSR | S_IWUSR);
+ 			if (doubleWriteFile < 0)
+ 				elog(PANIC, "Couldn't open double-write file %s", name);
+ 			pfree(name);
+ 		}
+ 	}
+ }
+ 
+ /*
+  * Write out the current batch of buffers, doing a double write if
+  * required, and then release and unlock the buffers in the batch.
+  * Only unpin the buffers if doUnpin is true.
+  */
+ static void
+ FlushBatch(bool doUnpin)
+ {
+ 	int			j;
+ 
+ 	if (doubleWrites)
+ 		LWLockAcquire(DoubleWriteLock, LW_EXCLUSIVE);
+ 	/* XXX should we set an error context, like FlushBuffer()? */
+ 	smgrbwrite(writeIndex, writeList, doubleWriteFile);
+ 	if (doubleWrites)
+ 		LWLockRelease(DoubleWriteLock);
+ 
+ 
+ 	/*
+ 	 * Release buffers in reverse order, so InProgressBuf[] is managed
+ 	 * efficiently.
+ 	 */
+ 	for (j = writeIndex - 1; j >= 0; j--)
+ 	{
+ 		volatile BufferDesc *bufHdr = &BufferDescriptors[writeList[j].buf_id];
+ 
+ 		pgBufferUsage.shared_blks_written++;
+ 		TerminateBufferIO(bufHdr, true, 0);
+ 		LWLockRelease(bufHdr->content_lock);
+ 		if (doUnpin)
+ 			UnpinBuffer(bufHdr, true);
+ 	}
+ 	writeIndex = 0;
+ }
+ 
  /*
   * BufferSync -- Write out all dirty buffers in the pool.
   *
***************
*** 1183,1188 **** BufferSync(int flags)
--- 1323,1338 ----
  	int			num_written;
  	int			mask = BM_DIRTY;
  
+ #define DBLWRITE_DEBUG
+ #ifdef DBLWRITE_DEBUG
+ 	int64		targetTime,
+ 				now,
+ 				start;
+ 	int			cur_num_written;
+ #endif
+ 	bool		batched;
+ 	MemoryContext oldcontext;
+ 
  	/* Make sure we can handle the pin inside SyncOneBuffer */
  	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
  
***************
*** 1234,1239 **** BufferSync(int flags)
--- 1384,1406 ----
  
  	TRACE_POSTGRESQL_BUFFER_SYNC_START(NBuffers, num_to_write);
  
+ #ifdef DBLWRITE_DEBUG
+ 	if (log_checkpoints)
+ 		elog(LOG, "checkpoint: %d buffers to write", num_to_write);
+ 	/* Initialize some debug code for watching progress of the checkpoint. */
+ 	start = GetCurrentTimestamp();
+ 	/* Print out message every 30 seconds */
+ 	targetTime = start + 30000000;
+ 	cur_num_written = 0;
+ #endif
+ 
+ 	Assert(writeIndex == 0);
+ 	batched = (batched_buffer_writes > 0);
+ 	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+ 	AllocCksumBuf();
+ 	MemoryContextSwitchTo(oldcontext);
+ 	CheckDoubleWriteFile();
+ 
  	/*
  	 * Loop over all buffers again, and write the ones (still) marked with
  	 * BM_CHECKPOINT_NEEDED.  In this loop, we start at the clock sweep point
***************
*** 1263,1269 **** BufferSync(int flags)
  		 */
  		if (bufHdr->flags & BM_CHECKPOINT_NEEDED)
  		{
! 			if (SyncOneBuffer(buf_id, false) & BUF_WRITTEN)
  			{
  				TRACE_POSTGRESQL_BUFFER_SYNC_WRITTEN(buf_id);
  				BgWriterStats.m_buf_written_checkpoints++;
--- 1430,1440 ----
  		 */
  		if (bufHdr->flags & BM_CHECKPOINT_NEEDED)
  		{
! 			int			r;
! 
! 			r = SyncOneBufferBatched(buf_id, false);
! 
! 			if (r & BUF_WRITTEN)
  			{
  				TRACE_POSTGRESQL_BUFFER_SYNC_WRITTEN(buf_id);
  				BgWriterStats.m_buf_written_checkpoints++;
***************
*** 1283,1298 **** BufferSync(int flags)
  				if (num_written >= num_to_write)
  					break;
  
! 				/*
! 				 * Sleep to throttle our I/O rate.
! 				 */
! 				CheckpointWriteDelay(flags, (double) num_written / num_to_write);
  			}
  		}
  
  		if (++buf_id >= NBuffers)
  			buf_id = 0;
  	}
  
  	/*
  	 * Update checkpoint statistics. As noted above, this doesn't include
--- 1454,1494 ----
  				if (num_written >= num_to_write)
  					break;
  
! 				if (!batched || writeIndex == 0)
! 				{
! 					/*
! 					 * Sleep to throttle our I/O rate.
! 					 */
! 					CheckpointWriteDelay(flags,
! 										 (double) num_written / num_to_write);
! 				}
  			}
  		}
  
+ #ifdef DBLWRITE_DEBUG
+ 		if (log_checkpoints &&
+ 			((now = GetCurrentTimestamp()) >= targetTime ||
+ 			 num_to_scan == 0))
+ 		{
+ 			/* Print out a checkpoint progress message */
+ 			int			progress = num_written * 100 / num_to_write;
+ 			int			target = (now - start) * 100 / (int) (1000000 * CheckPointTimeout * CheckPointCompletionTarget);
+ 
+ 			elog(LOG, "%d written/s, %d%%, target %d%%",
+ 				 (num_written - cur_num_written) / 30,
+ 				 progress, target);
+ 			cur_num_written = num_written;
+ 			targetTime = now + 30000000;
+ 		}
+ #endif
+ 
  		if (++buf_id >= NBuffers)
  			buf_id = 0;
  	}
+ 	if (writeIndex > 0)
+ 	{
+ 		FlushBatch(true);
+ 	}
  
  	/*
  	 * Update checkpoint statistics. As noted above, this doesn't include
***************
*** 1347,1352 **** BgBufferSync(void)
--- 1543,1550 ----
  	int			num_to_scan;
  	int			num_written;
  	int			reusable_buffers;
+ 	bool		batched;
+ 	MemoryContext oldcontext;
  
  	/*
  	 * Find out where the freelist clock sweep currently is, and how many
***************
*** 1527,1536 **** BgBufferSync(void)
  	num_written = 0;
  	reusable_buffers = reusable_buffers_est;
  
  	/* Execute the LRU scan */
  	while (num_to_scan > 0 && reusable_buffers < upcoming_alloc_est)
  	{
! 		int			buffer_state = SyncOneBuffer(next_to_clean, true);
  
  		if (++next_to_clean >= NBuffers)
  		{
--- 1725,1742 ----
  	num_written = 0;
  	reusable_buffers = reusable_buffers_est;
  
+ 	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+ 	AllocCksumBuf();
+ 	MemoryContextSwitchTo(oldcontext);
+ 	CheckDoubleWriteFile();
+ 
+ 	batched = (batched_buffer_writes > 0);
  	/* Execute the LRU scan */
  	while (num_to_scan > 0 && reusable_buffers < upcoming_alloc_est)
  	{
! 		int			buffer_state;
! 
! 		buffer_state = SyncOneBufferBatched(next_to_clean, batched);
  
  		if (++next_to_clean >= NBuffers)
  		{
***************
*** 1551,1556 **** BgBufferSync(void)
--- 1757,1765 ----
  		else if (buffer_state & BUF_REUSABLE)
  			reusable_buffers++;
  	}
+ 	/* If we have an incomplete batch, write it out. */
+ 	if (batched && writeIndex > 0)
+ 		FlushBatch(true);
  
  	BgWriterStats.m_buf_written_clean += num_written;
  
***************
*** 1587,1609 **** BgBufferSync(void)
  }
  
  /*
!  * SyncOneBuffer -- process a single buffer during syncing.
!  *
!  * If skip_recently_used is true, we don't write currently-pinned buffers, nor
!  * buffers marked recently used, as these are not replacement candidates.
!  *
!  * Returns a bitmask containing the following flag bits:
!  *	BUF_WRITTEN: we wrote the buffer.
!  *	BUF_REUSABLE: buffer is available for replacement, ie, it has
!  *		pin count 0 and usage count 0.
!  *
!  * (BUF_WRITTEN could be set in error if FlushBuffers finds the buffer clean
!  * after locking it, but we don't care all that much.)
!  *
!  * Note: caller must have done ResourceOwnerEnlargeBuffers.
   */
  static int
! SyncOneBuffer(int buf_id, bool skip_recently_used)
  {
  	volatile BufferDesc *bufHdr = &BufferDescriptors[buf_id];
  	int			result = 0;
--- 1796,1805 ----
  }
  
  /*
!  * Prepare for syncing out one buffer.
   */
  static int
! SyncOneBufferPrepare(int buf_id, bool skip_recently_used)
  {
  	volatile BufferDesc *bufHdr = &BufferDescriptors[buf_id];
  	int			result = 0;
***************
*** 1640,1649 **** SyncOneBuffer(int buf_id, bool skip_recently_used)
  	 * buffer is clean by the time we've locked it.)
  	 */
  	PinBuffer_Locked(bufHdr);
  	LWLockAcquire(bufHdr->content_lock, LW_SHARED);
- 
  	FlushBuffer(bufHdr, NULL);
- 
  	LWLockRelease(bufHdr->content_lock);
  	UnpinBuffer(bufHdr, true);
  
--- 1836,1876 ----
  	 * buffer is clean by the time we've locked it.)
  	 */
  	PinBuffer_Locked(bufHdr);
+ 
+ 	return result | BUF_WRITTEN;
+ }
+ 
+ /*
+  * SyncOneBuffer -- process a single buffer during syncing.
+  *
+  * If skip_recently_used is true, we don't write currently-pinned buffers, nor
+  * buffers marked recently used, as these are not replacement candidates.
+  *
+  * Returns a bitmask containing the following flag bits:
+  *	BUF_WRITTEN: we wrote the buffer.
+  *	BUF_REUSABLE: buffer is available for replacement, ie, it has
+  *			 pin count 0 and usage count 0.
+  *	BUF_CHECKPOINT: buffer has BM_CHECKPOINT_NEEDED set.
+  *
+  * (BUF_WRITTEN could be set in error if FlushBuffers finds the buffer clean
+  * after locking it, but we don't care all that much.)
+  *
+  * Note: caller must have done ResourceOwnerEnlargeBuffers.
+  */
+ static int
+ SyncOneBuffer(int buf_id, bool skip_recently_used)
+ {
+ 	volatile BufferDesc *bufHdr = &BufferDescriptors[buf_id];
+ 	int			result;
+ 
+ 	result = SyncOneBufferPrepare(buf_id, skip_recently_used);
+ 	if ((result & BUF_WRITTEN) == 0)
+ 	{
+ 		return result;
+ 	}
+ 
  	LWLockAcquire(bufHdr->content_lock, LW_SHARED);
  	FlushBuffer(bufHdr, NULL);
  	LWLockRelease(bufHdr->content_lock);
  	UnpinBuffer(bufHdr, true);
  
***************
*** 1652,1657 **** SyncOneBuffer(int buf_id, bool skip_recently_used)
--- 1879,1943 ----
  
  
  /*
+  * Sync a buffer, or prepare to sync a buffer in a batch if doBatch is
+  * true.  Actually flush the batch of writes if we have reached now have
+  * batched_buffer_writes buffers in the batch.
+  */
+ static int
+ SyncOneBufferBatched(int buf_id, bool doBatch)
+ {
+ 	int			r;
+ 	volatile BufferDesc *bufHdr;
+ 
+ 	if (!doBatch)
+ 	{
+ 		return SyncOneBuffer(buf_id, false);
+ 	}
+ 	r = SyncOneBufferPrepare(buf_id, false);
+ 	if (!(r & BUF_WRITTEN))
+ 	{
+ 		return r;
+ 	}
+ 	bufHdr = &BufferDescriptors[buf_id];
+ retry:
+ 	if (writeIndex == 0)
+ 	{
+ 		LWLockAcquire(bufHdr->content_lock, LW_SHARED);
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * If we are already holding some buffer content locks (because of
+ 		 * batching multiple writes), then acquire the next content lock
+ 		 * conditionally, so as to avoid deadlock.	If we can't acquire it,
+ 		 * then flush the current batch (which will reset writeIndex to zero
+ 		 * as a consequence) and then retry (and now we won't have to acquire
+ 		 * it conditionally).
+ 		 */
+ 		if (!LWLockConditionalAcquire(bufHdr->content_lock, LW_SHARED))
+ 		{
+ 			FlushBatch(true);
+ 			goto retry;
+ 		}
+ 	}
+ 	if (!FlushBufferPrepare(bufHdr->buf_id, bufHdr,
+ 							cksumBuf + writeIndex * BLCKSZ, NULL))
+ 	{
+ 		LWLockRelease(bufHdr->content_lock);
+ 		UnpinBuffer(bufHdr, true);
+ 	}
+ 
+ 	/*
+ 	 * If we now have a full batch, then flush it out the batch.
+ 	 */
+ 	if (writeIndex == batched_buffer_writes)
+ 	{
+ 		FlushBatch(true);
+ 	}
+ 	return r | BUF_WRITTEN;
+ }
+ 
+ /*
   *		AtEOXact_Buffers - clean up at end of transaction.
   *
   *		As of PostgreSQL 8.0, buffer pins should get released by the
***************
*** 1932,1937 **** FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln)
--- 2218,2339 ----
  }
  
  /*
+  * Prepare to flush out a buffer and record it as part of a batch (add it
+  * to writeList[]).
+  */
+ static bool
+ FlushBufferPrepare(int buf_id, volatile BufferDesc *buf, char *callerBuf,
+ 				   SMgrRelation reln)
+ {
+ 	XLogRecPtr	recptr;
+ 
+ 	Assert(LWLockHeldByMe(buf->content_lock));
+ 
+ 	/*
+ 	 * Acquire the buffer's io_in_progress lock.  If StartBufferIO returns
+ 	 * false, then someone else flushed the buffer before we could, so we need
+ 	 * not do anything.
+ 	 */
+ 	if (!StartBufferIO(buf, false))
+ 		return FALSE;
+ 
+ 	/* Find smgr relation for buffer */
+ 	if (reln == NULL)
+ 	{
+ 		reln = smgropen(buf->tag.rnode, InvalidBackendId);
+ 		smgrsettransient(reln);
+ 	}
+ 
+ 	/*
+ 	 * Force XLOG flush up to buffer's LSN.  This implements the basic WAL
+ 	 * rule that log updates must hit disk before any of the data-file changes
+ 	 * they describe do.
+ 	 */
+ 	recptr = BufferGetLSN(buf);
+ 	XLogFlush(recptr);
+ 
+ 	/*
+ 	 * Now it's safe to write buffer to disk. Note that no one else should
+ 	 * have been able to write it while we were busy with log flushing because
+ 	 * we have the io_in_progress lock.
+ 	 */
+ 
+ 	/* To check if block content changes while flushing. - vadim 01/17/97 */
+ 	LockBufHdr(buf);
+ 	buf->flags &= ~BM_JUST_DIRTIED;
+ 	UnlockBufHdr(buf);
+ 
+ 	writeList[writeIndex].buf_id = buf_id;
+ 	writeList[writeIndex].reln = reln;
+ 	writeList[writeIndex].forkNum = buf->tag.forkNum;
+ 	writeList[writeIndex].blockNum = buf->tag.blockNum;
+ 	writeList[writeIndex].buffer = (char *) BufHdrGetBlock(buf);
+ 	writeList[writeIndex].callerBuf = callerBuf;
+ 	writeIndex++;
+ 	return TRUE;
+ }
+ 
+ /*
+  * Flush a buffer, or prepare to flush a buffer in a batch if doBatch is
+  * true.  Requires that bufHdr is already locked.  Actually flush the
+  * batch of writes if we have reached now have batched_buffer_writes
+  * buffers in the batch.
+  */
+ static void
+ FlushBufferBatched(volatile BufferDesc *bufHdr, SMgrRelation reln, bool doBatch)
+ {
+ 	PinBuffer_Locked(bufHdr);
+ 	if (doBatch)
+ 	{
+ retry:
+ 		Assert(batched_buffer_writes > 0);
+ 		if (writeIndex == 0)
+ 		{
+ 			LWLockAcquire(bufHdr->content_lock, LW_SHARED);
+ 		}
+ 		else
+ 		{
+ 			/*
+ 			 * If we are already holding some buffer content locks (because of
+ 			 * batching multiple writes), then acquire the next content lock
+ 			 * conditionally, so as to avoid deadlock.	If we can't acquire
+ 			 * it, then flush the current batch (which will reset writeIndex
+ 			 * to zero as a consequence) and then retry (and now we won't have
+ 			 * to acquire it conditionally).
+ 			 */
+ 			if (!LWLockConditionalAcquire(bufHdr->content_lock,
+ 										  LW_SHARED))
+ 			{
+ 				FlushBatch(true);
+ 				goto retry;
+ 			}
+ 		}
+ 		if (!FlushBufferPrepare(bufHdr->buf_id, bufHdr,
+ 								cksumBuf + writeIndex * BLCKSZ, reln))
+ 		{
+ 			LWLockRelease(bufHdr->content_lock);
+ 			UnpinBuffer(bufHdr, true);
+ 		}
+ 
+ 		/*
+ 		 * If we now have a full batch, then flush it out the batch.
+ 		 */
+ 		if (writeIndex == batched_buffer_writes)
+ 		{
+ 			FlushBatch(true);
+ 		}
+ 	}
+ 	else
+ 	{
+ 		LWLockAcquire(bufHdr->content_lock, LW_SHARED);
+ 		FlushBuffer(bufHdr, reln);
+ 		LWLockRelease(bufHdr->content_lock);
+ 		UnpinBuffer(bufHdr, true);
+ 	}
+ }
+ 
+ 
+ /*
   * RelationGetNumberOfBlocks
   *		Determines the current number of pages in the relation.
   */
***************
*** 2128,2137 **** PrintPinnedBufs(void)
   *		used in any performance-critical code paths, so it's not worth
   *		adding additional overhead to normal paths to make it go faster;
   *		but see also DropRelFileNodeBuffers.
   * --------------------------------------------------------------------
   */
  void
! FlushRelationBuffers(Relation rel)
  {
  	int			i;
  	volatile BufferDesc *bufHdr;
--- 2530,2543 ----
   *		used in any performance-critical code paths, so it's not worth
   *		adding additional overhead to normal paths to make it go faster;
   *		but see also DropRelFileNodeBuffers.
+  *
+  *		If needsDoubleWrite is true, then the changed buffers should use
+  *		double-writing, if that option is enabled.	Currently, relation
+  *		changes that were not WAL-logged do not need double writes.
   * --------------------------------------------------------------------
   */
  void
! FlushRelationBuffers(Relation rel, bool needsDoubleWrite)
  {
  	int			i;
  	volatile BufferDesc *bufHdr;
***************
*** 2171,2176 **** FlushRelationBuffers(Relation rel)
--- 2577,2584 ----
  		return;
  	}
  
+ 	AllocCksumBuf();
+ 	CheckDoubleWriteFile();
  	/* Make sure we can handle the pin inside the loop */
  	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
  
***************
*** 2181,2195 **** FlushRelationBuffers(Relation rel)
  		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
  			(bufHdr->flags & BM_VALID) && (bufHdr->flags & BM_DIRTY))
  		{
! 			PinBuffer_Locked(bufHdr);
! 			LWLockAcquire(bufHdr->content_lock, LW_SHARED);
! 			FlushBuffer(bufHdr, rel->rd_smgr);
! 			LWLockRelease(bufHdr->content_lock);
! 			UnpinBuffer(bufHdr, true);
  		}
  		else
  			UnlockBufHdr(bufHdr);
  	}
  }
  
  /* ---------------------------------------------------------------------
--- 2589,2611 ----
  		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
  			(bufHdr->flags & BM_VALID) && (bufHdr->flags & BM_DIRTY))
  		{
! 			/*
! 			 * Don't batch if the pages were not logged, since in that case we
! 			 * don't want to do double writes, even if the double write option
! 			 * is on.
! 			 */
! 			FlushBufferBatched(bufHdr, rel->rd_smgr,
! 							   needsDoubleWrite && doubleWrites);
  		}
  		else
  			UnlockBufHdr(bufHdr);
  	}
+ 	if (needsDoubleWrite && doubleWrites && writeIndex > 0)
+ 	{
+ 		/* Write out any incomplete batch */
+ 		FlushBatch(true);
+ 	}
+ 	FreeCksumBuf();
  }
  
  /* ---------------------------------------------------------------------
***************
*** 2213,2218 **** FlushDatabaseBuffers(Oid dbid)
--- 2629,2636 ----
  	int			i;
  	volatile BufferDesc *bufHdr;
  
+ 	AllocCksumBuf();
+ 	CheckDoubleWriteFile();
  	/* Make sure we can handle the pin inside the loop */
  	ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
  
***************
*** 2223,2237 **** FlushDatabaseBuffers(Oid dbid)
  		if (bufHdr->tag.rnode.dbNode == dbid &&
  			(bufHdr->flags & BM_VALID) && (bufHdr->flags & BM_DIRTY))
  		{
! 			PinBuffer_Locked(bufHdr);
! 			LWLockAcquire(bufHdr->content_lock, LW_SHARED);
! 			FlushBuffer(bufHdr, NULL);
! 			LWLockRelease(bufHdr->content_lock);
! 			UnpinBuffer(bufHdr, true);
  		}
  		else
  			UnlockBufHdr(bufHdr);
  	}
  }
  
  /*
--- 2641,2657 ----
  		if (bufHdr->tag.rnode.dbNode == dbid &&
  			(bufHdr->flags & BM_VALID) && (bufHdr->flags & BM_DIRTY))
  		{
! 			FlushBufferBatched(bufHdr, NULL, doubleWrites);
  		}
  		else
  			UnlockBufHdr(bufHdr);
  	}
+ 	if (doubleWrites && writeIndex > 0)
+ 	{
+ 		/* Write out any incomplete batch */
+ 		FlushBatch(true);
+ 	}
+ 	FreeCksumBuf();
  }
  
  /*
***************
*** 2648,2655 **** WaitIO(volatile BufferDesc *buf)
  static bool
  StartBufferIO(volatile BufferDesc *buf, bool forInput)
  {
- 	Assert(!InProgressBuf);
- 
  	for (;;)
  	{
  		/*
--- 3068,3073 ----
***************
*** 2688,2694 **** StartBufferIO(volatile BufferDesc *buf, bool forInput)
  
  	UnlockBufHdr(buf);
  
! 	InProgressBuf = buf;
  	IsForInput = forInput;
  
  	return true;
--- 3106,3112 ----
  
  	UnlockBufHdr(buf);
  
! 	InProgressBuf[InProgressIndex++] = buf;
  	IsForInput = forInput;
  
  	return true;
***************
*** 2715,2721 **** static void
  TerminateBufferIO(volatile BufferDesc *buf, bool clear_dirty,
  				  int set_flag_bits)
  {
! 	Assert(buf == InProgressBuf);
  
  	LockBufHdr(buf);
  
--- 3133,3140 ----
  TerminateBufferIO(volatile BufferDesc *buf, bool clear_dirty,
  				  int set_flag_bits)
  {
! 	/* BufferSync() will release batched buffers in reverse order */
! 	Assert(InProgressBuf[InProgressIndex - 1] == buf);
  
  	LockBufHdr(buf);
  
***************
*** 2727,2734 **** TerminateBufferIO(volatile BufferDesc *buf, bool clear_dirty,
  
  	UnlockBufHdr(buf);
  
! 	InProgressBuf = NULL;
! 
  	LWLockRelease(buf->io_in_progress_lock);
  }
  
--- 3146,3153 ----
  
  	UnlockBufHdr(buf);
  
! 	InProgressIndex--;
! 	InProgressBuf[InProgressIndex] = NULL;
  	LWLockRelease(buf->io_in_progress_lock);
  }
  
***************
*** 2744,2753 **** TerminateBufferIO(volatile BufferDesc *buf, bool clear_dirty,
  void
  AbortBufferIO(void)
  {
! 	volatile BufferDesc *buf = InProgressBuf;
  
! 	if (buf)
  	{
  		/*
  		 * Since LWLockReleaseAll has already been called, we're not holding
  		 * the buffer's io_in_progress_lock. We have to re-acquire it so that
--- 3163,3175 ----
  void
  AbortBufferIO(void)
  {
! 	volatile BufferDesc *buf;
! 	int			i;
  
! 	for (i = InProgressIndex - 1; i >= 0; i--)
  	{
+ 		buf = InProgressBuf[i];
+ 
  		/*
  		 * Since LWLockReleaseAll has already been called, we're not holding
  		 * the buffer's io_in_progress_lock. We have to re-acquire it so that
***************
*** 2789,2794 **** AbortBufferIO(void)
--- 3211,3217 ----
  		}
  		TerminateBufferIO(buf, false, BM_IO_ERROR);
  	}
+ 	InProgressIndex = 0;
  }
  
  /*
*** a/src/backend/storage/file/fd.c
--- b/src/backend/storage/file/fd.c
***************
*** 55,60 ****
--- 55,61 ----
  #include "catalog/pg_tablespace.h"
  #include "storage/fd.h"
  #include "storage/ipc.h"
+ #include "storage/smgr.h"
  #include "utils/guc.h"
  #include "utils/resowner.h"
  
***************
*** 1335,1340 **** retry:
--- 1336,1565 ----
  	return returnCode;
  }
  
+ struct io_iocb_common
+ {
+ 	void	   *buf;
+ 	unsigned	nbytes;
+ 	long long	offset;
+ };
+ 
+ struct iocb
+ {
+ 	void	   *data;
+ 	int			aio_fildes;
+ 	union
+ 	{
+ 		struct io_iocb_common c;
+ 	}			u;
+ };
+ 
+ /* Array of iocbs used for io_submit */
+ struct iocb ioc[MAX_BATCHED_WRITES];
+ int			iocIndex = 0;
+ 
+ /* Array of iovecs use for iocbs and for writev.  One extra entry for
+  * double-write header. */
+ struct iovec iov[MAX_BATCHED_WRITES + 1];
+ int			iovIndex = 0;
+ 
+ /*
+  * Return number of bytes described by the list of memory regions (struct
+  * iovec elements) referenced by ioc.
+  */
+ static int
+ iovlen(struct iocb *ioc)
+ {
+ 	int			i,
+ 				len;
+ 	struct iovec *iov = (struct iovec *) ioc->u.c.buf;
+ 
+ 	len = 0;
+ 	for (i = 0; i < ioc->u.c.nbytes; i++)
+ 	{
+ 		len += iov->iov_len;
+ 		iov++;
+ 	}
+ 	return len;
+ }
+ 
+ /*
+  * Flush existing batched IOs, doing a write to a double-write file first
+  * if double_writes option is turned on.  When called, iov[] lists all
+  * the memory regions being written, in order, and ioc[] groups together
+  * consecutive iov elements which go to consecutive locations on disk.
+  * Each ioc represents a set of memory regions that can be written to
+  * disk in a single write using writev().
+  */
+ static int
+ FlushIOs(File doubleWriteFile)
+ {
+ 	int			returnCode;
+ 	int			i,
+ 				j;
+ 
+ 	if (doubleWriteFile > 0)
+ 	{
+ 		returnCode = FileAccess(doubleWriteFile);
+ 		if (returnCode < 0)
+ 			return returnCode;
+ 		returnCode = lseek(VfdCache[doubleWriteFile].fd, 0, SEEK_SET);
+ 		if (returnCode < 0)
+ 		{
+ 			elog(LOG, "lseek error errno %d, rc %d",
+ 				 errno, returnCode);
+ 			return returnCode;
+ 		}
+ 
+ 		/*
+ 		 * Write out all the blocks sequentially in double-write file,
+ 		 * starting with the double-write file header describing all the
+ 		 * buffers.
+ 		 */
+ 		returnCode = writev(VfdCache[doubleWriteFile].fd, &iov[0], iovIndex);
+ 		if (returnCode < 0)
+ 		{
+ 			elog(LOG, "writev to tmp, errno %d, rc %d, iovIndex %d",
+ 				 errno, returnCode, iovIndex);
+ 			return returnCode;
+ 		}
+ 		returnCode = fdatasync(VfdCache[doubleWriteFile].fd);
+ 		if (returnCode < 0)
+ 		{
+ 			elog(LOG, "fdatasync error errno %d, rc %d", errno,
+ 				 returnCode);
+ 			return returnCode;
+ 		}
+ 	}
+ 
+ 	for (i = 0; i < iocIndex; i++)
+ 	{
+ 		returnCode = lseek(ioc[i].aio_fildes, ioc[i].u.c.offset, SEEK_SET);
+ 		if (returnCode < 0)
+ 		{
+ 			elog(LOG, "lseek error errno %d, rc %d, offset %Ld",
+ 				 errno, returnCode, ioc[i].u.c.offset);
+ 			return returnCode;
+ 		}
+ 		/* Use writev to do one IO per consecutive blocks to the disk. */
+ 		returnCode = writev(ioc[i].aio_fildes, ioc[i].u.c.buf,
+ 							ioc[i].u.c.nbytes);
+ 		if (returnCode < 0)
+ 		{
+ 			elog(LOG, "writev error errno %d, rc %d", errno, returnCode);
+ 			return returnCode;
+ 		}
+ 	}
+ 	if (doubleWriteFile > 0)
+ 	{
+ 		/*
+ 		 * If we are doing double writes, must make sure that the blocks was
+ 		 * just wrote are forced to disk, so we can re-use double-write buffer
+ 		 * next time.
+ 		 */
+ 		for (i = 0; i < iocIndex; i++)
+ 		{
+ 			for (j = 0; j < i; j++)
+ 			{
+ 				if (ioc[j].aio_fildes == ioc[i].aio_fildes)
+ 				{
+ 					break;
+ 				}
+ 			}
+ 			if (j == i)
+ 			{
+ 				returnCode = fdatasync(ioc[i].aio_fildes);
+ 				if (returnCode < 0)
+ 				{
+ 					elog(LOG, "fdatasync error errno %d, rc %d", errno,
+ 						 returnCode);
+ 					return returnCode;
+ 				}
+ 			}
+ 		}
+ 	}
+ 	return 0;
+ }
+ 
+ /*
+  * Write out a batch of memory regions (Postgres buffers) to locations in
+  * files, as specified in writeList.  If doubleWriteFile is >= 0, then
+  * also do double writes to the specified file (so full_page_writes can
+  * be avoided).
+  */
+ int
+ FileBwrite(int writeLen, struct SMgrWriteList *writeList,
+ 		   File doubleWriteFile, char *doubleBuf)
+ {
+ 	int			returnCode;
+ 	int			i;
+ 
+ 	for (i = 0; i < writeLen; i++)
+ 	{
+ 		struct SMgrWriteList *w = &(writeList[i]);
+ 
+ 		Assert(FileIsValid(w->fd));
+ 		DO_DB(elog(LOG, "FileBwrite: %d (%s) " INT64_FORMAT " %d %p",
+ 				   file, VfdCache[w->fd].fileName,
+ 				   (int64) VfdCache[w->fd].seekPos,
+ 				   w->len, w->callerBuf));
+ 
+ 		returnCode = FileAccess(w->fd);
+ 		if (returnCode < 0)
+ 			return returnCode;
+ 	}
+ 
+ 	iovIndex = 0;
+ 	iocIndex = 0;
+ 
+ 	if (doubleWriteFile > 0)
+ 	{
+ 		/*
+ 		 * Write out the double-write header (which lists all the blocks) in a
+ 		 * 4K chunk at the beginning of the double-write file.
+ 		 */
+ 		iov[iovIndex].iov_base = doubleBuf;
+ 		iov[iovIndex].iov_len = DOUBLE_WRITE_HEADER_SIZE;
+ 		iovIndex++;
+ 	}
+ 
+ 	/*
+ 	 * Convert the list of writes (writeList) into an iovec array iov that
+ 	 * describes all the memory regions being written, and an ioc array for
+ 	 * each consecutive set of iov elements which are going to the consecutive
+ 	 * locations in the same file.	Each ioc element describes a set of memory
+ 	 * regions that can be written to the disk in a single write usinf
+ 	 * writev() (also known as a scatter-gather array).
+ 	 */
+ 	for (i = 0; i < writeLen; i++)
+ 	{
+ 		struct SMgrWriteList *w = &(writeList[i]);
+ 		struct iocb *last;
+ 
+ 		iov[iovIndex].iov_base = w->callerBuf;
+ 		iov[iovIndex].iov_len = w->len;
+ 		last = (iocIndex > 0) ? &(ioc[iocIndex - 1]) : NULL;
+ 		if (iocIndex > 0 && w->seekPos == last->u.c.offset + iovlen(last) &&
+ 			last->aio_fildes == VfdCache[w->fd].fd)
+ 		{
+ 			last->u.c.nbytes++;
+ 		}
+ 		else
+ 		{
+ 			ioc[iocIndex].aio_fildes = VfdCache[w->fd].fd;
+ 			ioc[iocIndex].u.c.buf = (void *) &iov[iovIndex];
+ 			ioc[iocIndex].u.c.nbytes = 1;
+ 			ioc[iocIndex].u.c.offset = w->seekPos;
+ 			iocIndex++;
+ 		}
+ 		iovIndex++;
+ 		VfdCache[w->fd].seekPos = FileUnknownPos;
+ 	}
+ 
+ 	returnCode = FlushIOs(doubleWriteFile);
+ 
+ 	return returnCode;
+ }
+ 
  int
  FileSync(File file)
  {
*** a/src/backend/storage/lmgr/lwlock.c
--- b/src/backend/storage/lmgr/lwlock.c
***************
*** 81,88 **** NON_EXEC_STATIC LWLockPadded *LWLockArray = NULL;
   * during error recovery.  The maximum size could be determined at runtime
   * if necessary, but it seems unlikely that more than a few locks could
   * ever be held simultaneously.
   */
! #define MAX_SIMUL_LWLOCKS	100
  
  static int	num_held_lwlocks = 0;
  static LWLockId held_lwlocks[MAX_SIMUL_LWLOCKS];
--- 81,91 ----
   * during error recovery.  The maximum size could be determined at runtime
   * if necessary, but it seems unlikely that more than a few locks could
   * ever be held simultaneously.
+  *
+  * Must be at least 2 * MAX_BATCHED_WRITES, since checkpointer/bgwriter
+  * holds 2 lwlocks for each buffer that it is batching.
   */
! #define MAX_SIMUL_LWLOCKS	(Max(100, (2 * MAX_BATCHED_WRITES)))
  
  static int	num_held_lwlocks = 0;
  static LWLockId held_lwlocks[MAX_SIMUL_LWLOCKS];
*** a/src/backend/storage/smgr/md.c
--- b/src/backend/storage/smgr/md.c
***************
*** 745,750 **** mdwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
--- 745,799 ----
  }
  
  /*
+  * Write out a list of buffers, as specified in writeList.	If
+  * doubleWriteFile is >= 0, then also do double writes to the specified
+  * file (so full_page_writes can be avoided).
+  */
+ void
+ mdbwrite(int writeLen, struct SMgrWriteList *writeList,
+ 		 File doubleWriteFile, char *doubleBuf)
+ {
+ 	off_t		seekpos;
+ 	int			nbytes;
+ 	MdfdVec    *v;
+ 	File		fd;
+ 	int			i;
+ 
+ 	for (i = 0; i < writeLen; i++)
+ 	{
+ 		struct SMgrWriteList *w = &(writeList[i]);
+ 
+ 		/* This assert is too expensive to have on normally ... */
+ #ifdef CHECK_WRITE_VS_EXTEND
+ 		Assert(w->blockNum < mdnblocks(w->reln, w->forknum));
+ #endif
+ 		v = _mdfd_getseg(w->reln, w->forkNum, w->blockNum, false, EXTENSION_FAIL);
+ 		fd = v->mdfd_vfd;
+ 
+ 		seekpos = (off_t) BLCKSZ *(w->blockNum % ((BlockNumber) RELSEG_SIZE));
+ 
+ 		Assert(seekpos < (off_t) BLCKSZ * RELSEG_SIZE);
+ 		w->fd = fd;
+ 		w->seekPos = seekpos;
+ 		w->len = BLCKSZ;
+ 	}
+ 
+ 	nbytes = FileBwrite(writeLen, writeList, doubleWriteFile, doubleBuf);
+ 	if (nbytes < 0)
+ 	{
+ 		ereport(ERROR,
+ 				(errcode_for_file_access(),
+ 				 errmsg("FileBwrite error %d", errno)));
+ 	}
+ 
+ 	/*
+ 	 * XXX register_dirty_segment() call is not needed if we only do batched
+ 	 * writes for the double_write option.
+ 	 */
+ }
+ 
+ 
+ /*
   *	mdnblocks() -- Get the number of blocks stored in a relation.
   *
   *		Important side effect: all active segments of the relation are opened
*** a/src/backend/storage/smgr/smgr.c
--- b/src/backend/storage/smgr/smgr.c
***************
*** 15,22 ****
--- 15,25 ----
   *
   *-------------------------------------------------------------------------
   */
+ #include <unistd.h>
+ 
  #include "postgres.h"
  
+ #include "catalog/catalog.h"
  #include "commands/tablespace.h"
  #include "storage/bufmgr.h"
  #include "storage/ipc.h"
***************
*** 53,58 **** typedef struct f_smgr
--- 56,63 ----
  										  BlockNumber blocknum, char *buffer);
  	void		(*smgr_write) (SMgrRelation reln, ForkNumber forknum,
  						 BlockNumber blocknum, char *buffer, bool skipFsync);
+ 	void		(*smgr_bwrite) (int writeLen, struct SMgrWriteList *writeList,
+ 									  File doubleWriteFile, char *doubleBuf);
  	BlockNumber (*smgr_nblocks) (SMgrRelation reln, ForkNumber forknum);
  	void		(*smgr_truncate) (SMgrRelation reln, ForkNumber forknum,
  											  BlockNumber nblocks);
***************
*** 66,72 **** typedef struct f_smgr
  static const f_smgr smgrsw[] = {
  	/* magnetic disk */
  	{mdinit, NULL, mdclose, mdcreate, mdexists, mdunlink, mdextend,
! 		mdprefetch, mdread, mdwrite, mdnblocks, mdtruncate, mdimmedsync,
  		mdpreckpt, mdsync, mdpostckpt
  	}
  };
--- 71,77 ----
  static const f_smgr smgrsw[] = {
  	/* magnetic disk */
  	{mdinit, NULL, mdclose, mdcreate, mdexists, mdunlink, mdextend,
! 		mdprefetch, mdread, mdwrite, mdbwrite, mdnblocks, mdtruncate, mdimmedsync,
  		mdpreckpt, mdsync, mdpostckpt
  	}
  };
***************
*** 79,87 **** static const int NSmgr = lengthof(smgrsw);
--- 84,100 ----
   */
  static HTAB *SMgrRelationHash = NULL;
  
+ /* Page checksumming. */
+ static uint64 tempbuf[BLCKSZ / sizeof(uint64)];
+ extern bool page_checksum;
+ 
+ #define INVALID_CKSUM 0x1b0af034
+ 
  /* local function prototypes */
  static void smgrshutdown(int code, Datum arg);
  
+ /* Buffer used to write the double-write header */
+ static char doubleBuf[DOUBLE_WRITE_HEADER_SIZE];
  
  /*
   *	smgrinit(), smgrshutdown() -- Initialize or shut down storage
***************
*** 381,386 **** smgrdounlink(SMgrRelation reln, ForkNumber forknum, bool isRedo)
--- 394,454 ----
  }
  
  /*
+  * The initial value when computing the checksum for a data page.
+  */
+ static inline uint64
+ ChecksumInit(SMgrRelation reln, ForkNumber f, BlockNumber b)
+ {
+ 	return b + f;
+ }
+ 
+ /*
+  * Compute a checksum of buffer (with length len), using initial value
+  * cksum.  We use a relatively simple checksum calculation to avoid
+  * overhead, but could replace with some kind of CRC calculation.
+  */
+ static inline uint32
+ ComputeChecksum(uint64 *buffer, uint32 len, uint64 cksum)
+ {
+ 	int			i;
+ 
+ 	for (i = 0; i < len / sizeof(uint64); i += 4)
+ 	{
+ 		cksum += (cksum << 5) + *buffer;
+ 		cksum += (cksum << 5) + *(buffer + 1);
+ 		cksum += (cksum << 5) + *(buffer + 2);
+ 		cksum += (cksum << 5) + *(buffer + 3);
+ 		buffer += 4;
+ 	}
+ 	cksum = (cksum & 0xFFFFFFFF) + (cksum >> 32);
+ 	return cksum;
+ }
+ 
+ /*
+  * Copy buffer to dst and compute the checksum during the copy (so that
+  * the checksum is correct for the final contents of dst).
+  */
+ static inline uint32
+ CopyAndComputeChecksum(uint64 *dst, volatile uint64 *buffer,
+ 					   uint32 len, uint64 cksum)
+ {
+ 	int			i;
+ 
+ 	for (i = 0; i < len / sizeof(uint64); i += 4)
+ 	{
+ 		cksum += (cksum << 5) + (*dst = *buffer);
+ 		cksum += (cksum << 5) + (*(dst + 1) = *(buffer + 1));
+ 		cksum += (cksum << 5) + (*(dst + 2) = *(buffer + 2));
+ 		cksum += (cksum << 5) + (*(dst + 3) = *(buffer + 3));
+ 		dst += 4;
+ 		buffer += 4;
+ 	}
+ 	cksum = (cksum & 0xFFFFFFFF) + (cksum >> 32);
+ 	return cksum;
+ }
+ 
+ 
+ /*
   *	smgrextend() -- Add a new block to a file.
   *
   *		The semantics are nearly the same as smgrwrite(): write at the
***************
*** 393,400 **** void
  smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
  		   char *buffer, bool skipFsync)
  {
  	(*(smgrsw[reln->smgr_which].smgr_extend)) (reln, forknum, blocknum,
! 											   buffer, skipFsync);
  }
  
  /*
--- 461,490 ----
  smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
  		   char *buffer, bool skipFsync)
  {
+ 	PageHeader	p;
+ 
+ 	Assert(PageGetPageLayoutVersion(((PageHeader) buffer)) == PG_PAGE_LAYOUT_VERSION ||
+ 		   PageIsNew(buffer));
+ 	if (page_checksum)
+ 	{
+ 		p = (PageHeader) tempbuf;
+ 		((PageHeader) buffer)->cksum = 0;
+ 
+ 		/*
+ 		 * We copy and compute the checksum, and then write out the data from
+ 		 * the copy, so that we avoid any problem with hint bits changing
+ 		 * after we compute the checksum.
+ 		 */
+ 		p->cksum = CopyAndComputeChecksum(tempbuf, (uint64 *) buffer, BLCKSZ,
+ 									  ChecksumInit(reln, forknum, blocknum));
+ 	}
+ 	else
+ 	{
+ 		p = (PageHeader) buffer;
+ 		p->cksum = INVALID_CKSUM;
+ 	}
  	(*(smgrsw[reln->smgr_which].smgr_extend)) (reln, forknum, blocknum,
! 											   (char *) p, skipFsync);
  }
  
  /*
***************
*** 416,424 **** smgrprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum)
   */
  void
  smgrread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
! 		 char *buffer)
  {
  	(*(smgrsw[reln->smgr_which].smgr_read)) (reln, forknum, blocknum, buffer);
  }
  
  /*
--- 506,543 ----
   */
  void
  smgrread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
! 		 char *buffer, bool *cksumMismatch)
  {
+ 	PageHeader	p = (PageHeader) buffer;
+ 
  	(*(smgrsw[reln->smgr_which].smgr_read)) (reln, forknum, blocknum, buffer);
+ 	Assert(PageIsNew(p) || PageGetPageLayoutVersion(p) == PG_PAGE_LAYOUT_VERSION);
+ 	if (page_checksum && p->cksum != INVALID_CKSUM)
+ 	{
+ 		const uint32 diskCksum = p->cksum;
+ 		uint32		cksum;
+ 
+ 		p->cksum = 0;
+ 		cksum = ComputeChecksum((uint64 *) buffer, BLCKSZ,
+ 								ChecksumInit(reln, forknum, blocknum));
+ 		if (cksum != diskCksum)
+ 		{
+ 			if (cksumMismatch != NULL)
+ 			{
+ 				*cksumMismatch = TRUE;
+ 				return;
+ 			}
+ 			ereport(PANIC, (0, errmsg("checksum mismatch: disk has %#x, should be %#x\n"
+ 				  "filename %s, BlockNum %u, block specifier %d/%d/%d/%d/%u",
+ 									  diskCksum, (uint32) cksum,
+ 									  relpath(reln->smgr_rnode, forknum),
+ 									  blocknum,
+ 									  reln->smgr_rnode.node.spcNode,
+ 									  reln->smgr_rnode.node.dbNode,
+ 									  reln->smgr_rnode.node.relNode,
+ 									  forknum, blocknum)));
+ 		}
+ 	}
  }
  
  /*
***************
*** 440,447 **** void
  smgrwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
  		  char *buffer, bool skipFsync)
  {
  	(*(smgrsw[reln->smgr_which].smgr_write)) (reln, forknum, blocknum,
! 											  buffer, skipFsync);
  }
  
  /*
--- 559,673 ----
  smgrwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
  		  char *buffer, bool skipFsync)
  {
+ 	PageHeader	p;
+ 
+ 	if (page_checksum)
+ 	{
+ 		p = (PageHeader) tempbuf;
+ 		((PageHeader) buffer)->cksum = 0;
+ 
+ 		/*
+ 		 * We copy and compute the checksum, and then write out the data from
+ 		 * the copy, so that we avoid any problem with hint bits changing
+ 		 * after we compute the checksum.
+ 		 */
+ 		p->cksum = CopyAndComputeChecksum(tempbuf, (uint64 *) buffer, BLCKSZ,
+ 									  ChecksumInit(reln, forknum, blocknum));
+ 	}
+ 	else
+ 	{
+ 		p = (PageHeader) buffer;
+ 		p->cksum = INVALID_CKSUM;
+ 	}
+ 	Assert(PageGetPageLayoutVersion(p) == PG_PAGE_LAYOUT_VERSION);
  	(*(smgrsw[reln->smgr_which].smgr_write)) (reln, forknum, blocknum,
! 											  (char *) p, skipFsync);
! }
! 
! /*
!  * Write out a list of buffers, as specified in writeList.	If
!  * doubleWriteFile is >= 0, then also do double writes to the specified
!  * file (so full_page_writes can be avoided).
!  */
! void
! smgrbwrite(int writeLen, struct SMgrWriteList *writeList,
! 		   File doubleWriteFile)
! {
! 	PageHeader	p = NULL;
! 	int			i;
! 
! 	if (page_checksum)
! 	{
! 		for (i = 0; i < writeLen; i++)
! 		{
! 			struct SMgrWriteList *w = &(writeList[i]);
! 
! 			p = (PageHeader) w->callerBuf;
! 			if (p == NULL)
! 			{
! 				/* Use tempbuf in the 1-page case (BufferAlloc) */
! 				Assert(writeLen == 1);
! 				p = (PageHeader) tempbuf;
! 				w->callerBuf = (char *) tempbuf;
! 			}
! 			((PageHeader) w->buffer)->cksum = 0;
! 			p->cksum = CopyAndComputeChecksum((uint64 *) p,
! 											  (uint64 *) w->buffer,
! 											  BLCKSZ,
! 											  ChecksumInit(w->reln,
! 														   w->forkNum,
! 														   w->blockNum));
! 			Assert(PageGetPageLayoutVersion(p) == PG_PAGE_LAYOUT_VERSION);
! 		}
! 	}
! 	else
! 	{
! 		for (i = 0; i < writeLen; i++)
! 		{
! 			p = (PageHeader) writeList[i].buffer;
! 			writeList[i].callerBuf = (char *) p;
! 			p->cksum = INVALID_CKSUM;
! 			Assert(PageGetPageLayoutVersion(p) == PG_PAGE_LAYOUT_VERSION);
! 		}
! 	}
! 
! 	if (doubleWriteFile > 0)
! 	{
! 		/*
! 		 * Set up the initial double-write page that lists all the buffers
! 		 * that will be written to the double write file This list includes
! 		 * the checksums of all the buffers and is checksummed itself.
! 		 */
! 		struct DoubleBufHeader *hdr = (struct DoubleBufHeader *) doubleBuf;
! 		struct DoubleBufItem *item = hdr->items;
! 
! 		/*
! 		 * The double-write header size should be big enough to contain info
! 		 * for up to MAX_BATCHED_WRITES buffers.
! 		 */
! 		Assert(sizeof(struct DoubleBufHeader) + MAX_BATCHED_WRITES * sizeof(struct DoubleBufItem) <= DOUBLE_WRITE_HEADER_SIZE);
! 		for (i = 0; i < writeLen; i++)
! 		{
! 			item->rnode = writeList[i].reln->smgr_rnode;
! 			item->forkNum = writeList[i].forkNum;
! 			item->blockNum = writeList[i].blockNum;
! 			p = (PageHeader) writeList[i].callerBuf;
! 			item->cksum = p->cksum;
! 			item->pd_lsn = p->pd_lsn;
! 			item++;
! 		}
! 		hdr->writeLen = writeLen;
! 		hdr->cksum = ComputeChecksum((uint64 *) hdr, DOUBLE_WRITE_HEADER_SIZE, 0);
! 	}
! 
! 	(*(smgrsw[writeList[0].reln->smgr_which].smgr_bwrite)) (writeLen, writeList,
! 												 doubleWriteFile, doubleBuf);
! 	if (doubleWriteFile > 0)
! 	{
! 		/* Zero out part of header that we filled in. */
! 		memset(doubleBuf, 0,
! 			   (char *) &(((struct DoubleBufHeader *) doubleBuf)->items[writeLen]) - doubleBuf);
! 	}
  }
  
  /*
***************
*** 561,563 **** smgrpostckpt(void)
--- 787,949 ----
  			(*(smgrsw[i].smgr_post_ckpt)) ();
  	}
  }
+ 
+ extern char *double_write_directory;
+ 
+ /*
+  * Return the name of the double write file.  Caller must use pfree().
+  */
+ char *
+ DoubleWriteFileName()
+ {
+ 	char	   *name;
+ 
+ 	if (double_write_directory != NULL)
+ 	{
+ 		name = palloc(strlen(double_write_directory) + strlen("/double") + 1);
+ 		sprintf(name, "%s/double", double_write_directory);
+ 	}
+ 	else
+ 	{
+ 		name = pstrdup("base/double");
+ 	}
+ 	return name;
+ }
+ 
+ /*
+  * Called by postmaster at startup during recovery to read double-write
+  * file and see if there are any data blocks to be recovered.
+  */
+ void
+ RecoverDoubleWriteFile()
+ {
+ 	char	   *name;
+ 	struct stat stat_buf;
+ 	File		fd;
+ 	int			r;
+ 	struct DoubleBufHeader *hdr;
+ 	int			i;
+ 	uint32		savedCksum;
+ 
+ 	name = DoubleWriteFileName();
+ 	if (stat(name, &stat_buf) == -1)
+ 	{
+ 		elog(LOG, "No double-write file");
+ 		pfree(name);
+ 		return;
+ 	}
+ 	fd = PathNameOpenFile(name, O_RDONLY, S_IRUSR | S_IWUSR);
+ 	if (fd < 0)
+ 	{
+ 		elog(PANIC, "Double-write file %s exists but can't be opened", name);
+ 	}
+ 
+ 	r = FileRead(fd, doubleBuf, DOUBLE_WRITE_HEADER_SIZE);
+ 	if (r < 0)
+ 		goto badformat;
+ 
+ 	hdr = (struct DoubleBufHeader *) doubleBuf;
+ 	if (hdr->writeLen == 0 && hdr->cksum == 0)
+ 	{
+ 		elog(LOG, "Double-write file %s is zeroed out", name);
+ 		FileClose(fd);
+ 		pfree(name);
+ 		return;
+ 	}
+ 	if (hdr->writeLen < 1 || hdr->writeLen > MAX_BATCHED_WRITES)
+ 		goto badformat;
+ 
+ 	savedCksum = hdr->cksum;
+ 	hdr->cksum = 0;
+ 	if (savedCksum !=
+ 		ComputeChecksum((uint64 *) hdr, DOUBLE_WRITE_HEADER_SIZE, 0))
+ 		goto badformat;
+ 
+ 	for (i = 0; i < hdr->writeLen; i++)
+ 	{
+ 		struct DoubleBufItem *it = &(hdr->items[i]);
+ 		SMgrRelation smgr;
+ 		bool		mismatch;
+ 		PageHeader	p;
+ 
+ 		/*
+ 		 * For each block described in double-write file header, see if the
+ 		 * block in the database file has a checksum mismatch.	If so, restore
+ 		 * the block from the double-write file if that entry has correct
+ 		 * checksum.
+ 		 */
+ 		smgr = smgropen(it->rnode.node, InvalidBackendId);
+ 		mismatch = false;
+ 
+ 		/*
+ 		 * The block may no longer exist if relation was deleted/truncated
+ 		 * after the last double-write.
+ 		 */
+ 		if (!smgrexists(smgr, it->forkNum) ||
+ 			it->blockNum >= smgrnblocks(smgr, it->forkNum))
+ 		{
+ 			elog(LOG, "Block %s/%d in slot %d of double-write file no longer exists, skipping",
+ 				 relpath(it->rnode, it->forkNum), it->blockNum, i);
+ 			continue;
+ 		}
+ 
+ 		smgrread(smgr, it->forkNum, it->blockNum, (char *) tempbuf, &mismatch);
+ 		if (mismatch)
+ 		{
+ 			/*
+ 			 * The corresponding data block has a checksum error, and is
+ 			 * likely a torn page.	See if the block in the double-write file
+ 			 * has the correct checksum.  If so, we can correct the data page
+ 			 * from the block in the double-write file.
+ 			 */
+ 			FileSeek(fd, DOUBLE_WRITE_HEADER_SIZE + i * BLCKSZ, SEEK_SET);
+ 			FileRead(fd, (char *) tempbuf, BLCKSZ);
+ 			p = (PageHeader) tempbuf;
+ 			savedCksum = p->cksum;
+ 			p->cksum = 0;
+ 			if (savedCksum != it->cksum ||
+ 				savedCksum != ComputeChecksum((uint64 *) tempbuf, BLCKSZ,
+ 											  ChecksumInit(smgr, it->forkNum,
+ 														   it->blockNum)))
+ 			{
+ 				elog(LOG, "Block %s/%d has checksum error, but can't be fixed, because slot %d of double-write file %s looks invalid",
+ 					 relpath(it->rnode, it->forkNum), it->blockNum, i, name);
+ 			}
+ 			else
+ 			{
+ 				/*
+ 				 * Correct the block in the data file from the block in the
+ 				 * double-write file.
+ 				 */
+ 				Assert(XLByteEQ(p->pd_lsn, it->pd_lsn));
+ 				smgrwrite(smgr, it->forkNum, it->blockNum, (char *) tempbuf, false);
+ 				elog(LOG, "Fixed block %s/%d (which had checksum error) from double-write file %s slot %d",
+ 					 relpath(it->rnode, it->forkNum), it->blockNum, name, i);
+ 			}
+ 		}
+ 		else
+ 		{
+ 			elog(DEBUG1, "Skipping slot %d of double-write file because block %s/%d is correct",
+ 				 i, relpath(it->rnode, it->forkNum), it->blockNum);
+ 		}
+ 		smgrclose(smgr);
+ 	}
+ 	FileClose(fd);
+ 
+ 	/*
+ 	 * Remove double-write file, so it can't be re-applied again if crash
+ 	 * happens during recovery.
+ 	 */
+ 	if (unlink(name) == -1)
+ 	{
+ 		elog(PANIC, "Failed to remove double-write file");
+ 	}
+ 	pfree(name);
+ 	return;
+ 
+ badformat:
+ 	elog(LOG, "Double-write file %s has bad format", name);
+ 	FileClose(fd);
+ 	pfree(name);
+ 	return;
+ }
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
***************
*** 130,135 **** extern int	CommitSiblings;
--- 130,137 ----
  extern char *default_tablespace;
  extern char *temp_tablespaces;
  extern bool synchronize_seqscans;
+ bool		doubleWrites;
+ char	   *double_write_directory;
  extern bool fullPageWrites;
  extern int	ssl_renegotiation_limit;
  extern char *SSLCipherSuites;
***************
*** 419,424 **** bool		default_with_oids = false;
--- 421,428 ----
  bool		SQL_inheritance = true;
  
  bool		Password_encryption = true;
+ bool		page_checksum = true;
+ 
  
  int			log_min_error_statement = ERROR;
  int			log_min_messages = WARNING;
***************
*** 445,450 **** int			tcp_keepalives_idle;
--- 449,456 ----
  int			tcp_keepalives_interval;
  int			tcp_keepalives_count;
  
+ int			batched_buffer_writes = 0;
+ 
  /*
   * These variables are all dummies that don't do anything, except in some
   * cases provide the value for SHOW to display.  The real state is elsewhere
***************
*** 816,821 **** static struct config_bool ConfigureNamesBool[] =
--- 822,835 ----
  		NULL, NULL, NULL
  	},
  	{
+ 		{"double_writes", PGC_POSTMASTER, DEVELOPER_OPTIONS,
+ 			gettext_noop("Page writes are written first to a temporary file before being written to the database file."),
+ 			NULL
+ 		},
+ 		&doubleWrites,
+ 		false, NULL, NULL
+ 	},
+ 	{
  		{"zero_damaged_pages", PGC_SUSET, DEVELOPER_OPTIONS,
  			gettext_noop("Continues processing past damaged page headers."),
  			gettext_noop("Detection of a damaged page header normally causes PostgreSQL to "
***************
*** 1438,1443 **** static struct config_bool ConfigureNamesBool[] =
--- 1452,1465 ----
  		NULL, NULL, NULL
  	},
  
+ 	{
+ 		{"page_checksum", PGC_POSTMASTER, CUSTOM_OPTIONS,
+ 			gettext_noop("enable disk page checksumming"),
+ 			NULL
+ 		},
+ 		&page_checksum, true, NULL, NULL
+ 	},
+ 
  	/* End-of-list marker */
  	{
  		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
***************
*** 2370,2375 **** static struct config_int ConfigureNamesInt[] =
--- 2392,2407 ----
  		NULL, NULL, NULL
  	},
  
+ 	{
+ 		{"batched_buffer_writes", PGC_POSTMASTER, DEVELOPER_OPTIONS,
+ 			gettext_noop("Checkpointer/bgwriter writes buffers in batches."),
+ 			NULL,
+ 			GUC_NOT_IN_SAMPLE
+ 		},
+ 		&batched_buffer_writes,
+ 		0, 0, MAX_BATCHED_WRITES, NULL, NULL
+ 	},
+ 
  	/* End-of-list marker */
  	{
  		{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL
***************
*** 3018,3023 **** static struct config_string ConfigureNamesString[] =
--- 3050,3065 ----
  		check_application_name, assign_application_name, NULL
  	},
  
+ 	{
+ 		{"double_write_directory", PGC_POSTMASTER, DEVELOPER_OPTIONS,
+ 			gettext_noop("Location of the double write file."),
+ 			NULL,
+ 			GUC_REPORT | GUC_NOT_IN_SAMPLE
+ 		},
+ 		&double_write_directory,
+ 		NULL, NULL, NULL
+ 	},
+ 
  	/* End-of-list marker */
  	{
  		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
*** a/src/backend/utils/misc/postgresql.conf.sample
--- b/src/backend/utils/misc/postgresql.conf.sample
***************
*** 521,526 ****
--- 521,527 ----
  # LOCK MANAGEMENT
  #------------------------------------------------------------------------------
  
+ #page_checksum = on
  #deadlock_timeout = 1s
  #max_locks_per_transaction = 64		# min 10
  					# (change requires restart)
***************
*** 564,566 ****
--- 565,570 ----
  #------------------------------------------------------------------------------
  
  # Add settings for extensions here
+ #double_writes = off
+ #double_write_directory = ''
+ #batched_buffer_writes = 0
*** a/src/backend/utils/resowner/resowner.c
--- b/src/backend/utils/resowner/resowner.c
***************
*** 517,523 **** ResourceOwnerEnlargeBuffers(ResourceOwner owner)
  
  	if (owner->buffers == NULL)
  	{
! 		newmax = 16;
  		owner->buffers = (Buffer *)
  			MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Buffer));
  		owner->maxbuffers = newmax;
--- 517,527 ----
  
  	if (owner->buffers == NULL)
  	{
! 		/*
! 		 * This should be at least MAX_BATCHED_WRITES, since bgwriter can lock
! 		 * that many buffers at one time.
! 		 */
! 		newmax = MAX_BATCHED_WRITES;
  		owner->buffers = (Buffer *)
  			MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Buffer));
  		owner->maxbuffers = newmax;
*** a/src/include/access/heapam.h
--- b/src/include/access/heapam.h
***************
*** 124,130 **** extern void simple_heap_update(Relation relation, ItemPointer otid,
  extern void heap_markpos(HeapScanDesc scan);
  extern void heap_restrpos(HeapScanDesc scan);
  
! extern void heap_sync(Relation relation);
  
  extern void heap_redo(XLogRecPtr lsn, XLogRecord *rptr);
  extern void heap_desc(StringInfo buf, uint8 xl_info, char *rec);
--- 124,130 ----
  extern void heap_markpos(HeapScanDesc scan);
  extern void heap_restrpos(HeapScanDesc scan);
  
! extern void heap_sync(Relation relation, bool needsDoubleWrite);
  
  extern void heap_redo(XLogRecPtr lsn, XLogRecord *rptr);
  extern void heap_desc(StringInfo buf, uint8 xl_info, char *rec);
*** a/src/include/c.h
--- b/src/include/c.h
***************
*** 735,740 **** typedef NameData *Name;
--- 735,742 ----
  #define PG_TEXTDOMAIN(domain) (domain "-" PG_MAJORVERSION)
  #endif
  
+ /* Maximum number of buffers that can be written in a batch. */
+ #define MAX_BATCHED_WRITES		64
  
  /* ----------------------------------------------------------------
   *				Section 8: system-specific hacks
*** a/src/include/storage/bufmgr.h
--- b/src/include/storage/bufmgr.h
***************
*** 183,189 **** extern void CheckPointBuffers(int flags);
  extern BlockNumber BufferGetBlockNumber(Buffer buffer);
  extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation,
  								ForkNumber forkNum);
! extern void FlushRelationBuffers(Relation rel);
  extern void FlushDatabaseBuffers(Oid dbid);
  extern void DropRelFileNodeBuffers(RelFileNodeBackend rnode,
  					   ForkNumber forkNum, BlockNumber firstDelBlock);
--- 183,189 ----
  extern BlockNumber BufferGetBlockNumber(Buffer buffer);
  extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation,
  								ForkNumber forkNum);
! extern void FlushRelationBuffers(Relation rel, bool needsDoubleWrite);
  extern void FlushDatabaseBuffers(Oid dbid);
  extern void DropRelFileNodeBuffers(RelFileNodeBackend rnode,
  					   ForkNumber forkNum, BlockNumber firstDelBlock);
*** a/src/include/storage/bufpage.h
--- b/src/include/storage/bufpage.h
***************
*** 132,137 **** typedef struct PageHeaderData
--- 132,138 ----
  	LocationIndex pd_special;	/* offset to start of special space */
  	uint16		pd_pagesize_version;
  	TransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */
+ 	uint32		cksum;			/* page checksum */
  	ItemIdData	pd_linp[1];		/* beginning of line pointer array */
  } PageHeaderData;
  
***************
*** 154,160 **** typedef PageHeaderData *PageHeader;
  										 * tuple? */
  #define PD_ALL_VISIBLE		0x0004		/* all tuples on page are visible to
  										 * everyone */
- 
  #define PD_VALID_FLAG_BITS	0x0007		/* OR of all valid pd_flags bits */
  
  /*
--- 155,160 ----
***************
*** 165,172 **** typedef PageHeaderData *PageHeader;
   * Release 8.3 uses 4; it changed the HeapTupleHeader layout again, and
   *		added the pd_flags field (by stealing some bits from pd_tli),
   *		as well as adding the pd_prune_xid field (which enlarges the header).
   */
! #define PG_PAGE_LAYOUT_VERSION		4
  
  
  /* ----------------------------------------------------------------
--- 165,173 ----
   * Release 8.3 uses 4; it changed the HeapTupleHeader layout again, and
   *		added the pd_flags field (by stealing some bits from pd_tli),
   *		as well as adding the pd_prune_xid field (which enlarges the header).
+  * Release x.y uses 5; we added checksums to heap/index/fsm files.
   */
! #define PG_PAGE_LAYOUT_VERSION		5
  
  
  /* ----------------------------------------------------------------
*** a/src/include/storage/fd.h
--- b/src/include/storage/fd.h
***************
*** 53,63 **** typedef int File;
--- 53,70 ----
  /* GUC parameter */
  extern int	max_files_per_process;
  
+ /*
+  * Size of the header on the double-write file that describes the buffers
+  * currently written to the double-write file.
+  */
+ #define DOUBLE_WRITE_HEADER_SIZE 4096
  
  /*
   * prototypes for functions in fd.c
   */
  
+ struct SMgrWriteList;
+ 
  /* Operations on virtual Files --- equivalent to Unix kernel file ops */
  extern File PathNameOpenFile(FileName fileName, int fileFlags, int fileMode);
  extern File OpenTemporaryFile(bool interXact);
***************
*** 67,72 **** extern int	FilePrefetch(File file, off_t offset, int amount);
--- 74,81 ----
  extern int	FileRead(File file, char *buffer, int amount);
  extern int	FileWrite(File file, char *buffer, int amount);
  extern int	FileSync(File file);
+ extern int FileBwrite(int writeLen, struct SMgrWriteList *writeList,
+ 		   File doubleWriteFile, char *doubleBuf);
  extern off_t FileSeek(File file, off_t offset, int whence);
  extern int	FileTruncate(File file, off_t offset);
  extern char *FilePathName(File file);
*** a/src/include/storage/lwlock.h
--- b/src/include/storage/lwlock.h
***************
*** 79,84 **** typedef enum LWLockId
--- 79,85 ----
  	SerializablePredicateLockListLock,
  	OldSerXidLock,
  	SyncRepLock,
+ 	DoubleWriteLock,
  	/* Individual lock IDs end here */
  	FirstBufMappingLock,
  	FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS,
*** a/src/include/storage/smgr.h
--- b/src/include/storage/smgr.h
***************
*** 15,22 ****
--- 15,24 ----
  #define SMGR_H
  
  #include "fmgr.h"
+ #include "fd.h"
  #include "storage/block.h"
  #include "storage/relfilenode.h"
+ #include "access/xlogdefs.h"
  
  
  /*
***************
*** 71,76 **** typedef SMgrRelationData *SMgrRelation;
--- 73,123 ----
  #define SmgrIsTemp(smgr) \
  	((smgr)->smgr_rnode.backend != InvalidBackendId)
  
+ /*
+  * Element of an array specifying a buffer write in a list of buffer
+  * writes being batched.
+  */
+ struct SMgrWriteList
+ {
+ 	int			buf_id;
+ 	SMgrRelation reln;
+ 	ForkNumber	forkNum;
+ 	BlockNumber blockNum;
+ 	char	   *buffer;
+ 	char	   *callerBuf;
+ 
+ 	/* Filled in by mdawrite */
+ 	File		fd;
+ 	off_t		seekPos;
+ 	int			len;
+ };
+ 
+ /*
+  * Description of one buffer in the double-write file, as listed in the
+  * header.
+  */
+ struct DoubleBufItem
+ {
+ 	/* Specification of where this buffer is in the database */
+ 	RelFileNodeBackend rnode;	/* physical relation identifier */
+ 	ForkNumber	forkNum;
+ 	BlockNumber blockNum;		/* blknum relative to begin of reln */
+ 	/* Checksum of the buffer. */
+ 	int32		cksum;
+ 	/* LSN of the buffer */
+ 	XLogRecPtr	pd_lsn;
+ };
+ 
+ /* Format of the header of the double-write file */
+ struct DoubleBufHeader
+ {
+ 	uint32		cksum;
+ 	int32		writeLen;
+ 	uint32		pad1;
+ 	uint32		pad2;
+ 	struct DoubleBufItem items[0];
+ };
+ 
  extern void smgrinit(void);
  extern SMgrRelation smgropen(RelFileNode rnode, BackendId backend);
  extern void smgrsettransient(SMgrRelation reln);
***************
*** 87,95 **** extern void smgrextend(SMgrRelation reln, ForkNumber forknum,
  extern void smgrprefetch(SMgrRelation reln, ForkNumber forknum,
  			 BlockNumber blocknum);
  extern void smgrread(SMgrRelation reln, ForkNumber forknum,
! 		 BlockNumber blocknum, char *buffer);
  extern void smgrwrite(SMgrRelation reln, ForkNumber forknum,
  		  BlockNumber blocknum, char *buffer, bool skipFsync);
  extern BlockNumber smgrnblocks(SMgrRelation reln, ForkNumber forknum);
  extern void smgrtruncate(SMgrRelation reln, ForkNumber forknum,
  			 BlockNumber nblocks);
--- 134,144 ----
  extern void smgrprefetch(SMgrRelation reln, ForkNumber forknum,
  			 BlockNumber blocknum);
  extern void smgrread(SMgrRelation reln, ForkNumber forknum,
! 		 BlockNumber blocknum, char *buffer, bool *cksumMismatch);
  extern void smgrwrite(SMgrRelation reln, ForkNumber forknum,
  		  BlockNumber blocknum, char *buffer, bool skipFsync);
+ extern void smgrbwrite(int writeLen, struct SMgrWriteList *writeList,
+ 		   File doubleWriteFile);
  extern BlockNumber smgrnblocks(SMgrRelation reln, ForkNumber forknum);
  extern void smgrtruncate(SMgrRelation reln, ForkNumber forknum,
  			 BlockNumber nblocks);
***************
*** 98,103 **** extern void smgrpreckpt(void);
--- 147,155 ----
  extern void smgrsync(void);
  extern void smgrpostckpt(void);
  
+ extern char *DoubleWriteFileName(void);
+ extern void RecoverDoubleWriteFile(void);
+ 
  
  /* internals: move me elsewhere -- ay 7/94 */
  
***************
*** 115,120 **** extern void mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
--- 167,174 ----
  	   char *buffer);
  extern void mdwrite(SMgrRelation reln, ForkNumber forknum,
  		BlockNumber blocknum, char *buffer, bool skipFsync);
+ extern void mdbwrite(int writeLen, struct SMgrWriteList *writeList,
+ 		 File doubleWriteFile, char *doublebuf);
  extern BlockNumber mdnblocks(SMgrRelation reln, ForkNumber forknum);
  extern void mdtruncate(SMgrRelation reln, ForkNumber forknum,
  		   BlockNumber nblocks);