double_writes_90.diff
text/plain
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 | 3 | 0 |
| src/backend/access/heap/rewriteheap.c | 1 | 0 |
| src/backend/access/transam/xlog.c | 6 | 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 | 4 | 0 |
| src/backend/storage/buffer/buf_init.c | 1 | 0 |
| src/backend/storage/buffer/bufmgr.c | 503 | 0 |
| src/backend/storage/file/fd.c | 189 | 0 |
| src/backend/storage/lmgr/lwlock.c | 4 | 0 |
| src/backend/storage/smgr/md.c | 42 | 0 |
| src/backend/storage/smgr/smgr.c | 193 | 0 |
| src/backend/utils/misc/guc.c | 32 | 0 |
| src/backend/utils/resowner/resowner.c | 3 | 0 |
| src/include/access/heapam.h | 1 | 0 |
| src/include/storage/bufmgr.h | 3 | 0 |
| src/include/storage/fd.h | 6 | 0 |
| src/include/storage/smgr.h | 49 | 0 |
*** a/src/backend/access/heap/heapam.c
--- b/src/backend/access/heap/heapam.c
***************
*** 5008,5021 **** heap2_desc(StringInfo buf, uint8 xl_info, char *rec)
* to be done here.)
*/
void
! heap_sync(Relation rel)
{
/* temp tables never need fsync */
if (rel->rd_istemp)
return;
/* main heap */
! FlushRelationBuffers(rel);
/* FlushRelationBuffers will have opened rd_smgr */
smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
--- 5008,5021 ----
* to be done here.)
*/
void
! heap_sync(Relation rel, bool wasLogged)
{
/* temp tables never need fsync */
if (rel->rd_istemp)
return;
/* main heap */
! FlushRelationBuffers(rel, wasLogged);
/* FlushRelationBuffers will have opened rd_smgr */
smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
***************
*** 5027,5033 **** 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);
}
--- 5027,5033 ----
Relation toastrel;
toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
! FlushRelationBuffers(toastrel, wasLogged);
smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
heap_close(toastrel, AccessShareLock);
}
*** a/src/backend/access/heap/rewriteheap.c
--- b/src/backend/access/heap/rewriteheap.c
***************
*** 290,296 **** end_heap_rewrite(RewriteState state)
* wrote before the checkpoint.
*/
if (!state->rs_new_rel->rd_istemp)
! heap_sync(state->rs_new_rel);
/* Deleting the context frees everything */
MemoryContextDelete(state->rs_cxt);
--- 290,296 ----
* wrote before the checkpoint.
*/
if (!state->rs_new_rel->rd_istemp)
! 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
***************
*** 5980,5985 **** StartupXLOG(void)
--- 5980,5991 ----
InRecovery = true;
}
+ /*
+ * If double write file exists, see if there are any pages to
+ * be recovered because of torn writes.
+ */
+ smgr_recoverdoublewritefile();
+
/* REDO */
if (InRecovery)
{
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2223,2229 **** CopyFrom(CopyState cstate)
* indexes since those use WAL anyway)
*/
if (hi_options & HEAP_INSERT_SKIP_WAL)
! heap_sync(cstate->rel);
}
--- 2223,2229 ----
* indexes since those use WAL anyway)
*/
if (hi_options & HEAP_INSERT_SKIP_WAL)
! heap_sync(cstate->rel, false);
}
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 3294,3300 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
/* 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);
}
--- 3294,3300 ----
/* 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);
}
***************
*** 7114,7120 **** ATExecSetTableSpace(Oid tableOid, Oid newTableSpace)
* 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
--- 7114,7120 ----
* 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
***************
*** 7213,7219 **** 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)
--- 7213,7219 ----
/* 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
***************
*** 2351,2357 **** 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);
--- 2351,2357 ----
/* 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 int optind,
--- 314,320 ----
extern int page_checksum;
extern bool fullPageWrites;
+ extern int doubleWrites;
#ifdef HAVE_INT_OPTRESET
extern int optreset; /* might not be declared by system headers */
***************
*** 737,763 **** PostmasterMain(int argc, char *argv[])
if (max_wal_senders > 0 && wal_level == WAL_LEVEL_MINIMAL)
ereport(ERROR,
(errmsg("WAL streaming (max_wal_senders > 0) requires wal_level \"archive\" or \"hot_standby\"")));
!
! /*
! * The idea here is that there will be checksum mismatches if there
! * are partial writes to pages during hardware crashes. So, the user
! * should have full_page_writes enabled if page_checksum is enabled,
! * so that these pages are automatically fixed, else Postgres may
! * often get checksum errors after crashes (on pages that are in fact
! * partially written and hence corrupted). With full_page_writes
! * enabled, Postgres will replace each page without ever looking at
! * the partially-written page and seeing an incorrect checksum.
! * Hence, checksums will detect only real disk corruptions (where the
! * disk reported a successful write but the data was still corrupted
! * at some point).
! *
! * Alternatively, we may want to leave this check out, for those
! * sophisticated users who know that their hardware/software setup
! * can never produce partial writes during crashes.
! */
! if (page_checksum && !fullPageWrites)
ereport(ERROR,
! (errmsg("full_page_writes must be enabled if page_checksum is enabled")));
/*
* Other one-time internal sanity checks can go here, if they are fast.
--- 738,747 ----
if (max_wal_senders > 0 && wal_level == WAL_LEVEL_MINIMAL)
ereport(ERROR,
(errmsg("WAL streaming (max_wal_senders > 0) requires wal_level \"archive\" or \"hot_standby\"")));
! if (doubleWrites && !page_checksum) {
ereport(ERROR,
! (errmsg("page_checksum must be enabled if double_writes is enabled")));
! }
/*
* Other one-time internal sanity checks can go here, if they are fast.
*** a/src/backend/storage/buffer/buf_init.c
--- b/src/backend/storage/buffer/buf_init.c
***************
*** 127,132 **** InitBufferPool(void)
--- 127,133 ----
/* Init other shared buffer-management stuff */
StrategyInitialize(!foundDescs);
+ Bufmgr_DoubleWriteInit();
}
/*
*** a/src/backend/storage/buffer/bufmgr.c
--- b/src/backend/storage/buffer/bufmgr.c
***************
*** 41,46 ****
--- 41,47 ----
#include "postmaster/bgwriter.h"
#include "storage/buf_internals.h"
#include "storage/bufmgr.h"
+ #include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/proc.h"
#include "storage/smgr.h"
***************
*** 60,65 ****
--- 61,67 ----
/* Bits in SyncOneBuffer's return value */
#define BUF_WRITTEN 0x01
#define BUF_REUSABLE 0x02
+ #define BUF_RETRY_AFTER_FLUSH 0x20
/* GUC variables */
***************
*** 74,81 **** 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,86 ----
*/
int target_prefetch_pages = 0;
+ extern bool doubleWrites;
+
/* local state for StartBufferIO and related functions */
! static volatile BufferDesc *InProgressBuf[MAX_BATCHED_WRITES];
! static int InProgressIndex = 0;
static bool IsForInput;
/* local state for LockBufferForCleanup */
***************
*** 90,96 **** static bool PinBuffer(volatile BufferDesc *buf, BufferAccessStrategy strategy);
--- 95,106 ----
static void PinBuffer_Locked(volatile BufferDesc *buf);
static void UnpinBuffer(volatile BufferDesc *buf, bool fixOwner);
static void BufferSync(int flags);
+ static int SyncOneBufferPrepare(int buf_id, bool skip_recently_used);
static int SyncOneBuffer(int buf_id, bool skip_recently_used);
+ static int WriteOneBufferPrepare(int buf_id, char *callerBuf,
+ bool skip_recently_used);
+ static void WriteOneBufferDone(int buf_id, bool doUnpin);
+
static void WaitIO(volatile BufferDesc *buf);
static bool StartBufferIO(volatile BufferDesc *buf, bool forInput);
static void TerminateBufferIO(volatile BufferDesc *buf, bool clear_dirty,
***************
*** 102,107 **** static volatile BufferDesc *BufferAlloc(SMgrRelation smgr, ForkNumber forkNum,
--- 112,122 ----
bool *foundPtr);
static void FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln);
static void AtProcExit_Buffers(int code, Datum arg);
+ static bool FlushBufferPrepare(int buf_id, volatile BufferDesc *buf,
+ char *callerBuf,
+ SMgrRelation reln);
+ static void CheckDoubleWriteFile(void);
+ static void FlushBatch(bool doUnpin);
/*
***************
*** 426,432 **** ReadBuffer_common(SMgrRelation smgr, bool isLocalBuf, ForkNumber forkNum,
MemSet((char *) bufBlock, 0, BLCKSZ);
else
{
! smgrread(smgr, forkNum, blockNum, (char *) bufBlock);
/* check for garbage data */
if (!PageHeaderIsValid((PageHeader) bufBlock))
--- 441,447 ----
MemSet((char *) bufBlock, 0, BLCKSZ);
else
{
! smgrread(smgr, forkNum, blockNum, (char *) bufBlock, NULL);
/* check for garbage data */
if (!PageHeaderIsValid((PageHeader) bufBlock))
***************
*** 639,646 **** BufferAlloc(SMgrRelation smgr, ForkNumber forkNum,
smgr->smgr_rnode.dbNode,
smgr->smgr_rnode.relNode);
! FlushBuffer(buf, NULL);
! LWLockRelease(buf->content_lock);
TRACE_POSTGRESQL_BUFFER_WRITE_DIRTY_DONE(forkNum, blockNum,
smgr->smgr_rnode.spcNode,
--- 654,676 ----
smgr->smgr_rnode.dbNode,
smgr->smgr_rnode.relNode);
! if (doubleWrites) {
! CheckDoubleWriteFile();
! /*
! * Make a one-element batch, so we can do the double
! * writes. We are serializing on the double write
! * file here. 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.spcNode,
***************
*** 1146,1151 **** UnpinBuffer(volatile BufferDesc *buf, bool fixOwner)
--- 1176,1343 ----
}
}
+ extern bool page_checksum;
+ extern int batched_buffer_writes;
+
+ /* File to which double writes are going */
+ File doubleWriteFile = 0;
+ LWLockId doubleWriteLock;
+ /* Buffers in to which to do checksumming of batched buffers */
+ char *origCksumBuf, *cksumBuf;
+
+ /* Array of buffer writes that are being batched. */
+ struct SMgrWriteList writeList[MAX_BATCHED_WRITES];
+ int writeIndex;
+
+
+
+ /*
+ * Unlock the buffers in the writeList[] (called after all IO is
+ * complete). Release them in reverse order, so InProgressBuf[] is
+ * managed efficiently.
+ */
+ static void
+ UnlockBufs(bool doUnpin)
+ {
+ int j;
+
+ for (j = writeIndex-1; j >= 0; j--) {
+ WriteOneBufferDone(writeList[j].buf_id, doUnpin);
+ }
+ writeIndex = 0;
+ }
+
+ /*
+ * Initialize the lock on the double write file (run once in postmaster).
+ */
+ void
+ Bufmgr_DoubleWriteInit()
+ {
+ doubleWriteLock = LWLockAssign();
+ }
+
+ /*
+ * Pre-allocate file with zeros in BLOCKSZ pages to at least len.
+ */
+ static void
+ ZeroFile(FileName name, int len)
+ {
+ char *zbuffer;
+ int nbytes;
+
+ int fd = BasicOpenFile(name, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (fd < 0) {
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create file \"%s\": %m", name)));
+ }
+ zbuffer = (char *) palloc0(BLCKSZ);
+ for (nbytes = 0; nbytes < len; nbytes += BLCKSZ) {
+ errno = 0;
+ if ((int) write(fd, zbuffer, BLCKSZ) != (int) BLCKSZ)
+ {
+ int save_errno = errno;
+
+ /*
+ * If we fail to make the file, delete it to release disk space
+ */
+ unlink(name);
+ /* if write didn't set errno, assume problem is no disk space */
+ errno = save_errno ? save_errno : ENOSPC;
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write to file \"%s\": %m", name)));
+ }
+ }
+ pfree(zbuffer);
+
+ if (pg_fsync(fd) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not fsync file \"%s\": %m", name)));
+
+ if (close(fd))
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close file \"%s\": %m", name)));
+ }
+
+ /*
+ * Allocate the buffers used for checksumming a batch of writes.
+ */
+ static bool
+ 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);
+ return true;
+ }
+ return false;
+ }
+
+ static void
+ FreeCksumBuf()
+ {
+ if (cksumBuf != NULL) {
+ pfree(origCksumBuf);
+ cksumBuf = origCksumBuf = NULL;
+ }
+ }
+
+ /* Round up a to next multiple of b */
+ #define ROUNDUP(a, b) ((((a)+(b)-1)/(b))*(b))
+
+ /*
+ * Open up the doubleWrite file and zero it out, if necessary.
+ */
+ static void
+ CheckDoubleWriteFile()
+ {
+ if (doubleWrites) {
+ if (doubleWriteFile == 0) {
+ struct stat stat_buf;
+ char *name = smgr_doublewritefilename();
+ int len = ROUNDUP(batched_buffer_writes * BLCKSZ + DOUBLE_WRITE_HEADER_SIZE,
+ BLCKSZ);
+
+ /* Make sure that the file is completely allocated */
+ LWLockAcquire(doubleWriteLock, LW_EXCLUSIVE);
+ if (stat(name, &stat_buf) == -1 || stat_buf.st_size < len) {
+ /* Fill out file if it is not big enough */
+ ZeroFile(name, len);
+ }
+ LWLockRelease(doubleWriteLock);
+
+ 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 releasing the buffer locks.
+ */
+ static void
+ FlushBatch(bool doUnpin)
+ {
+ if (doubleWrites) {
+ LWLockAcquire(doubleWriteLock, LW_EXCLUSIVE);
+ }
+ smgrbwrite(writeIndex, writeList, doubleWriteFile, false);
+ if (doubleWrites) {
+ LWLockRelease(doubleWriteLock);
+ }
+ UnlockBufs(doUnpin);
+ }
+
/*
* BufferSync -- Write out all dirty buffers in the pool.
*
***************
*** 1160,1165 **** BufferSync(int flags)
--- 1352,1363 ----
int num_to_scan;
int num_to_write;
int num_written;
+ int64 targetTime, now, start;
+ int cur_num_written;
+ bool batched;
+
+ AllocCksumBuf();
+ CheckDoubleWriteFile();
/* Make sure we can handle the pin inside SyncOneBuffer */
ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
***************
*** 1205,1210 **** BufferSync(int flags)
--- 1403,1411 ----
TRACE_POSTGRESQL_BUFFER_SYNC_START(NBuffers, num_to_write);
+ if (log_checkpoints) {
+ elog(LOG, "checkpoint: %d buffers to write", num_to_write);
+ }
/*
* 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
***************
*** 1213,1218 **** BufferSync(int flags)
--- 1414,1426 ----
* Note that we don't read the buffer alloc count here --- that should be
* left untouched till the next BgBufferSync() call.
*/
+ if (writeIndex != 0) {
+ elog(LOG, "Incomplete batch");
+ }
+ start = GetCurrentTimestamp();
+ targetTime = start + 30000000;
+ cur_num_written = 0;
+ batched = (batched_buffer_writes > 0);
buf_id = StrategySyncStart(NULL, NULL);
num_to_scan = NBuffers;
num_written = 0;
***************
*** 1234,1245 **** 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++;
num_written++;
/*
* We know there are at most num_to_write buffers with
* BM_CHECKPOINT_NEEDED set; so we can stop scanning if
--- 1442,1489 ----
*/
if (bufHdr->flags & BM_CHECKPOINT_NEEDED)
{
! int r;
! if (!batched) {
! r = SyncOneBuffer(buf_id, false);
! } else {
! retry:
! r = WriteOneBufferPrepare(buf_id,
! cksumBuf + writeIndex * BLCKSZ,
! false);
! if (r & BUF_RETRY_AFTER_FLUSH) {
! if (writeIndex > 0) {
! elog(LOG, "Couldn't get context_lock, flushing");
! FlushBatch(true);
! } else {
! elog(LOG, "Couldn't get context_lock, sleeping");
! pg_usleep(10000);
! }
! goto retry;
! }
! if (0) elog(LOG, "async io %d", buf_id);
!
! }
!
! if (r & BUF_WRITTEN)
{
TRACE_POSTGRESQL_BUFFER_SYNC_WRITTEN(buf_id);
BgWriterStats.m_buf_written_checkpoints++;
num_written++;
+ if (!batched) {
+ /*
+ * Perform normal bgwriter duties and sleep to throttle our
+ * I/O rate.
+ */
+ CheckpointWriteDelay(flags,
+ (double)num_written / num_to_write);
+ }
+ }
+ }
+ if (batched) {
+ if (writeIndex == batched_buffer_writes ||
+ (writeIndex > 0 && num_to_scan == 0)) {
+ FlushBatch(true);
/*
* We know there are at most num_to_write buffers with
* BM_CHECKPOINT_NEEDED set; so we can stop scanning if
***************
*** 1262,1267 **** BufferSync(int flags)
--- 1506,1523 ----
(double) num_written / num_to_write);
}
}
+ 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;
+ }
if (++buf_id >= NBuffers)
buf_id = 0;
***************
*** 1274,1279 **** BufferSync(int flags)
--- 1530,1536 ----
CheckpointStats.ckpt_bufs_written += num_written;
TRACE_POSTGRESQL_BUFFER_SYNC_DONE(NBuffers, num_written, num_to_write);
+ FreeCksumBuf();
}
/*
***************
*** 1320,1325 **** BgBufferSync(void)
--- 1577,1584 ----
int num_to_scan;
int num_written;
int reusable_buffers;
+ bool batched;
+ bool alloc;
/*
* Find out where the freelist clock sweep currently is, and how many
***************
*** 1500,1509 **** 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)
{
--- 1759,1792 ----
num_written = 0;
reusable_buffers = reusable_buffers_est;
+ alloc = AllocCksumBuf();
+ CheckDoubleWriteFile();
+
+ batched = (batched_buffer_writes > 0);
/* Execute the LRU scan */
while (num_to_scan > 0 && reusable_buffers < upcoming_alloc_est)
{
! int buffer_state;
!
! if (!batched) {
! buffer_state = SyncOneBuffer(next_to_clean, true);
! } else {
! retry:
! buffer_state = WriteOneBufferPrepare(next_to_clean,
! cksumBuf + writeIndex * BLCKSZ,
! false);
! if (buffer_state & BUF_RETRY_AFTER_FLUSH) {
! if (writeIndex > 0) {
! elog(LOG, "Couldn't get context_lock, flushing");
! FlushBatch(true);
! } else {
! elog(LOG, "Couldn't get context_lock, sleeping");
! pg_usleep(10000);
! }
! goto retry;
! }
! }
!
if (++next_to_clean >= NBuffers)
{
***************
*** 1523,1528 **** BgBufferSync(void)
--- 1806,1823 ----
}
else if (buffer_state & BUF_REUSABLE)
reusable_buffers++;
+
+ /* If we've filled a batch, write it out */
+ if (batched && writeIndex == batched_buffer_writes) {
+ FlushBatch(true);
+ }
+ }
+ /* If we have an incomplete batch, write it out. */
+ if (batched && writeIndex > 0) {
+ FlushBatch(true);
+ }
+ if (alloc) {
+ FreeCksumBuf();
}
BgWriterStats.m_buf_written_clean += num_written;
***************
*** 1560,1582 **** 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;
--- 1855,1864 ----
}
/*
! * 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;
***************
*** 1613,1619 **** 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);
--- 1895,1942 ----
* buffer is clean by the time we've locked it.)
*/
PinBuffer_Locked(bufHdr);
!
! /*
! * Since we are acquiring multiple buffers, we must do a
! * conditional-acquire. If we can't get the lock (very infrequent),
! * then we force a flush of the current batch of buffers, so we can
! * release our current set of locks before trying to get this one
! * again.
! */
! if (!LWLockConditionalAcquire(bufHdr->content_lock, LW_SHARED)) {
! UnpinBuffer(bufHdr, true);
! return result | BUF_RETRY_AFTER_FLUSH;
! }
! 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;
! }
FlushBuffer(bufHdr, NULL);
***************
*** 1625,1630 **** SyncOneBuffer(int buf_id, bool skip_recently_used)
--- 1948,1991 ----
/*
+ * Prepare for writing out one buffer.
+ */
+ static int
+ WriteOneBufferPrepare(int buf_id, char *callerBuf, 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;
+ }
+ if (!FlushBufferPrepare(buf_id, bufHdr, callerBuf, NULL)) {
+ LWLockRelease(bufHdr->content_lock);
+ UnpinBuffer(bufHdr, true);
+ elog(LOG, "Somebody else flushed...");
+ result &= ~BUF_WRITTEN;
+ }
+ return result;
+ }
+
+ /*
+ * Release locks after writing out a buffer.
+ */
+ static void
+ WriteOneBufferDone(int buf_id, bool doUnpin)
+ {
+ volatile BufferDesc *bufHdr = &BufferDescriptors[buf_id];
+
+ pgBufferUsage.shared_blks_written++;
+ TerminateBufferIO(bufHdr, true, 0);
+ LWLockRelease(bufHdr->content_lock);
+ if (doUnpin) {
+ UnpinBuffer(bufHdr, true);
+ }
+ }
+
+ /*
* AtEOXact_Buffers - clean up at end of transaction.
*
* As of PostgreSQL 8.0, buffer pins should get released by the
***************
*** 1901,1906 **** FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln)
--- 2262,2319 ----
}
/*
+ * Prepare to flush out a buffer and record it on the 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);
+
+ /*
+ * 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;
+ }
+
+
+ /*
* RelationGetNumberOfBlocks
* Determines the current number of pages in the relation.
*/
***************
*** 2050,2055 **** PrintPinnedBufs(void)
--- 2463,2504 ----
}
#endif
+ /*
+ * Flush a buffer, doing it in a batch if doBatch is true. Requires that
+ * bufHdr is already locked.
+ */
+ 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 (!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 (writeIndex == batched_buffer_writes) {
+ FlushBatch(true);
+ }
+ } else {
+ LWLockAcquire(bufHdr->content_lock, LW_SHARED);
+ FlushBuffer(bufHdr, reln);
+ LWLockRelease(bufHdr->content_lock);
+ UnpinBuffer(bufHdr, true);
+ }
+ }
+
/* ---------------------------------------------------------------------
* FlushRelationBuffers
*
***************
*** 2070,2076 **** PrintPinnedBufs(void)
* --------------------------------------------------------------------
*/
void
! FlushRelationBuffers(Relation rel)
{
int i;
volatile BufferDesc *bufHdr;
--- 2519,2525 ----
* --------------------------------------------------------------------
*/
void
! FlushRelationBuffers(Relation rel, bool wasLogged)
{
int i;
volatile BufferDesc *bufHdr;
***************
*** 2110,2115 **** FlushRelationBuffers(Relation rel)
--- 2559,2566 ----
return;
}
+ AllocCksumBuf();
+ CheckDoubleWriteFile();
/* Make sure we can handle the pin inside the loop */
ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
***************
*** 2120,2134 **** 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);
}
}
/* ---------------------------------------------------------------------
--- 2571,2591 ----
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, wasLogged && doubleWrites);
}
else
UnlockBufHdr(bufHdr);
}
+ if (wasLogged && doubleWrites && writeIndex > 0) {
+ /* Write out any incomplete batch */
+ FlushBatch(true);
+ }
+ FreeCksumBuf();
}
/* ---------------------------------------------------------------------
***************
*** 2152,2157 **** FlushDatabaseBuffers(Oid dbid)
--- 2609,2616 ----
int i;
volatile BufferDesc *bufHdr;
+ AllocCksumBuf();
+ CheckDoubleWriteFile();
/* Make sure we can handle the pin inside the loop */
ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
***************
*** 2162,2176 **** 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);
}
}
/*
--- 2621,2636 ----
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();
}
/*
***************
*** 2583,2590 **** WaitIO(volatile BufferDesc *buf)
static bool
StartBufferIO(volatile BufferDesc *buf, bool forInput)
{
- Assert(!InProgressBuf);
-
for (;;)
{
/*
--- 3043,3048 ----
***************
*** 2623,2629 **** StartBufferIO(volatile BufferDesc *buf, bool forInput)
UnlockBufHdr(buf);
! InProgressBuf = buf;
IsForInput = forInput;
return true;
--- 3081,3087 ----
UnlockBufHdr(buf);
! InProgressBuf[InProgressIndex++] = buf;
IsForInput = forInput;
return true;
***************
*** 2650,2656 **** static void
TerminateBufferIO(volatile BufferDesc *buf, bool clear_dirty,
int set_flag_bits)
{
! Assert(buf == InProgressBuf);
LockBufHdr(buf);
--- 3108,3115 ----
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);
***************
*** 2662,2669 **** TerminateBufferIO(volatile BufferDesc *buf, bool clear_dirty,
UnlockBufHdr(buf);
! InProgressBuf = NULL;
!
LWLockRelease(buf->io_in_progress_lock);
}
--- 3121,3128 ----
UnlockBufHdr(buf);
! InProgressIndex--;
! InProgressBuf[InProgressIndex] = NULL;
LWLockRelease(buf->io_in_progress_lock);
}
***************
*** 2679,2688 **** 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
--- 3138,3148 ----
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
***************
*** 2723,2728 **** AbortBufferIO(void)
--- 3183,3189 ----
}
TerminateBufferIO(buf, false, BM_IO_ERROR);
}
+ InProgressIndex = 0;
}
/*
*** a/src/backend/storage/file/fd.c
--- b/src/backend/storage/file/fd.c
***************
*** 1260,1265 **** retry:
--- 1260,1454 ----
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;
+ };
+
+ #if 0
+ struct iovec {
+ void *iov_base;
+ size_t iov_len;
+ };
+ #endif
+
+ /* 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;
+
+ 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 temp area first if
+ * double_writes option is turned on.
+ */
+ 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);
+ abort();
+ return returnCode;
+ }
+ /* Write out all the blocks sequentially in double-write file,
+ * starting with the double-write file header describing all the
+ * buffers. Double-write file should be open with O_DIRECT. */
+ 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);
+ abort();
+ return returnCode;
+ }
+ returnCode = fdatasync(VfdCache[doubleWriteFile].fd);
+ if (returnCode < 0) {
+ elog(LOG, "fdatasync error errno %d, rc %d", errno,
+ returnCode);
+ abort();
+ 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);
+ abort();
+ 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);
+ abort();
+ 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);
+ abort();
+ return returnCode;
+ }
+ }
+ }
+ }
+ return 0;
+ }
+
+ #include "storage/smgr.h"
+
+ /*
+ * Write out a list of memory regions 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 block directory in 4K chunk at the beginning */
+ iov[iovIndex].iov_base = doubleBuf;
+ iov[iovIndex].iov_len = DOUBLE_WRITE_HEADER_SIZE;
+ iovIndex++;
+ }
+
+ 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
***************
*** 80,87 **** 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];
--- 80,90 ----
* 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 bgwriter holds 2
+ * lwlocks for each buffer that it is batching.
*/
! #define MAX_SIMUL_LWLOCKS 150
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
***************
*** 727,732 **** mdwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
--- 727,774 ----
}
/*
+ * 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, bool isTemp)
+ {
+ 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, isTemp, 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)));
+ }
+ }
+
+
+ /*
* 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
***************
*** 55,60 **** typedef struct f_smgr
--- 55,63 ----
BlockNumber blocknum, char *buffer);
void (*smgr_write) (SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool isTemp);
+ void (*smgr_bwrite) (int writeLen, struct SMgrWriteList *writeList,
+ File doubleWriteFile, char *doubleBuf,
+ bool isTemp);
BlockNumber (*smgr_nblocks) (SMgrRelation reln, ForkNumber forknum);
void (*smgr_truncate) (SMgrRelation reln, ForkNumber forknum,
BlockNumber nblocks, bool isTemp);
***************
*** 68,74 **** 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
}
};
***************
*** 92,97 **** static void smgrshutdown(int code, Datum arg);
--- 95,102 ----
static void smgr_internal_unlink(RelFileNode rnode, ForkNumber forknum,
int which, bool isTemp, bool isRedo);
+ /* Buffer used to write the double-write header */
+ static char doubleBuf[DOUBLE_WRITE_HEADER_SIZE];
/*
* smgrinit(), smgrshutdown() -- Initialize or shut down storage
***************
*** 486,492 **** smgrprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum)
*/
void
smgrread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
! char *buffer)
{
PageHeader p = (PageHeader) buffer;
--- 491,497 ----
*/
void
smgrread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
! char *buffer, bool *cksumMismatch)
{
PageHeader p = (PageHeader) buffer;
***************
*** 554,559 **** smgrwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
--- 559,636 ----
}
/*
+ * 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, bool isTemp)
+ {
+ 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;
+ 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,
+ isTemp);
+ if (doubleWriteFile > 0) {
+ /* Zero out part of header that we filled in. */
+ memset(doubleBuf, 0,
+ (char *)&(((struct DoubleBufHeader *) doubleBuf)->items[writeLen]) - doubleBuf);
+ }
+ }
+
+ /*
* smgrnblocks() -- Calculate the number of blocks in the
* supplied relation.
*/
***************
*** 672,674 **** smgrpostckpt(void)
--- 749,865 ----
(*(smgrsw[i].smgr_post_ckpt)) ();
}
}
+
+ extern char *double_write_directory;
+
+ /*
+ * Return the name of the double write file. Call must use pfree().
+ */
+ char *
+ smgr_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
+ smgr_recoverdoublewritefile()
+ {
+ char *name;
+ struct stat stat_buf;
+ File fd;
+ int r;
+ struct DoubleBufHeader *hdr;
+ int i;
+ uint32 savedCksum;
+
+ name = smgr_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);
+ mismatch = false;
+ smgrread(smgr, it->forkNum, it->blockNum, (char *)tempbuf, &mismatch);
+ if (mismatch) {
+ 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 {
+ 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);
+ 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
***************
*** 116,121 **** extern int CommitSiblings;
--- 116,123 ----
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;
***************
*** 392,397 **** int tcp_keepalives_idle;
--- 394,401 ----
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
***************
*** 767,772 **** static struct config_bool ConfigureNamesBool[] =
--- 771,784 ----
true, 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
+ },
+ {
{"silent_mode", PGC_POSTMASTER, LOGGING_WHERE,
gettext_noop("Runs the server silently."),
gettext_noop("If this parameter is set, the server will automatically run in the "
***************
*** 2088,2093 **** static struct config_int ConfigureNamesInt[] =
--- 2100,2115 ----
1024, 100, 102400, NULL, NULL
},
+ {
+ {"batched_buffer_writes", PGC_POSTMASTER, DEVELOPER_OPTIONS,
+ gettext_noop("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
***************
*** 2668,2673 **** static struct config_string ConfigureNamesString[] =
--- 2690,2705 ----
"", 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
*** a/src/backend/utils/resowner/resowner.c
--- b/src/backend/utils/resowner/resowner.c
***************
*** 515,521 **** ResourceOwnerEnlargeBuffers(ResourceOwner owner)
if (owner->buffers == NULL)
{
! newmax = 16;
owner->buffers = (Buffer *)
MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Buffer));
owner->maxbuffers = newmax;
--- 515,523 ----
if (owner->buffers == NULL)
{
! /* This should be at least MAX_BATCHED_WRITES, since bgwriter can
! * lock that many buffers at one time. */
! newmax = 64;
owner->buffers = (Buffer *)
MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Buffer));
owner->maxbuffers = newmax;
*** a/src/include/access/heapam.h
--- b/src/include/access/heapam.h
***************
*** 119,125 **** 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);
--- 119,125 ----
extern void heap_markpos(HeapScanDesc scan);
extern void heap_restrpos(HeapScanDesc scan);
! extern void heap_sync(Relation relation, bool wasLogged);
extern void heap_redo(XLogRecPtr lsn, XLogRecord *rptr);
extern void heap_desc(StringInfo buf, uint8 xl_info, char *rec);
*** a/src/include/storage/bufmgr.h
--- b/src/include/storage/bufmgr.h
***************
*** 178,184 **** extern void PrintBufferLeakWarning(Buffer buffer);
extern void CheckPointBuffers(int flags);
extern BlockNumber BufferGetBlockNumber(Buffer buffer);
extern BlockNumber RelationGetNumberOfBlocks(Relation relation);
! extern void FlushRelationBuffers(Relation rel);
extern void FlushDatabaseBuffers(Oid dbid);
extern void DropRelFileNodeBuffers(RelFileNode rnode, ForkNumber forkNum,
bool istemp, BlockNumber firstDelBlock);
--- 178,184 ----
extern void CheckPointBuffers(int flags);
extern BlockNumber BufferGetBlockNumber(Buffer buffer);
extern BlockNumber RelationGetNumberOfBlocks(Relation relation);
! extern void FlushRelationBuffers(Relation rel, bool wasLogged);
extern void FlushDatabaseBuffers(Oid dbid);
extern void DropRelFileNodeBuffers(RelFileNode rnode, ForkNumber forkNum,
bool istemp, BlockNumber firstDelBlock);
***************
*** 211,214 **** extern void AtProcExit_LocalBuffers(void);
--- 211,216 ----
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
+ extern void Bufmgr_DoubleWriteInit(void);
+
#endif
*** a/src/include/storage/fd.h
--- b/src/include/storage/fd.h
***************
*** 53,63 **** typedef int File;
--- 53,67 ----
/* GUC parameter */
extern int max_files_per_process;
+ #define MAX_BATCHED_WRITES 64
+ #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);
***************
*** 66,71 **** extern int FilePrefetch(File file, off_t offset, int amount);
--- 70,77 ----
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/smgr.h
--- b/src/include/storage/smgr.h
***************
*** 16,21 ****
--- 16,22 ----
#include "access/xlog.h"
#include "fmgr.h"
+ #include "fd.h"
#include "storage/block.h"
#include "storage/relfilenode.h"
***************
*** 68,73 **** typedef struct SMgrRelationData
--- 69,115 ----
typedef SMgrRelationData *SMgrRelation;
+ /*
+ * 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 */
+ RelFileNode 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);
***************
*** 84,92 **** 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 isTemp);
extern BlockNumber smgrnblocks(SMgrRelation reln, ForkNumber forknum);
extern void smgrtruncate(SMgrRelation reln, ForkNumber forknum,
BlockNumber nblocks, bool isTemp);
--- 126,136 ----
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 isTemp);
+ extern void smgrbwrite(int writeLen, struct SMgrWriteList *writeList,
+ File doubleWriteFile, bool isTemp);
extern BlockNumber smgrnblocks(SMgrRelation reln, ForkNumber forknum);
extern void smgrtruncate(SMgrRelation reln, ForkNumber forknum,
BlockNumber nblocks, bool isTemp);
***************
*** 94,99 **** extern void smgrimmedsync(SMgrRelation reln, ForkNumber forknum);
--- 138,145 ----
extern void smgrpreckpt(void);
extern void smgrsync(void);
extern void smgrpostckpt(void);
+ extern char *smgr_doublewritefilename(void);
+ extern void smgr_recoverdoublewritefile(void);
/* internals: move me elsewhere -- ay 7/94 */
***************
*** 112,117 **** extern void mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
--- 158,165 ----
char *buffer);
extern void mdwrite(SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool isTemp);
+ extern void mdbwrite(int writeLen, struct SMgrWriteList *writeList,
+ File doubleWriteFile, char *doublebuf, bool isTemp);
extern BlockNumber mdnblocks(SMgrRelation reln, ForkNumber forknum);
extern void mdtruncate(SMgrRelation reln, ForkNumber forknum,
BlockNumber nblocks, bool isTemp);