Skip to content

Commit

Permalink
Skip WAL for new relfilenodes, under wal_level=minimal.
Browse files Browse the repository at this point in the history
Until now, only selected bulk operations (e.g. COPY) did this.  If a
given relfilenode received both a WAL-skipping COPY and a WAL-logged
operation (e.g. INSERT), recovery could lose tuples from the COPY.  See
src/backend/access/transam/README section "Skipping WAL for New
RelFileNode" for the new coding rules.  Maintainers of table access
methods should examine that section.

To maintain data durability, just before commit, we choose between an
fsync of the relfilenode and copying its contents to WAL.  A new GUC,
wal_skip_threshold, guides that choice.  If this change slows a workload
that creates small, permanent relfilenodes under wal_level=minimal, try
adjusting wal_skip_threshold.  Users setting a timeout on COMMIT may
need to adjust that timeout, and log_min_duration_statement analysis
will reflect time consumption moving to COMMIT from commands like COPY.

Internally, this requires a reliable determination of whether
RollbackAndReleaseCurrentSubTransaction() would unlink a relation's
current relfilenode.  Introduce rd_firstRelfilenodeSubid.  Amend the
specification of rd_createSubid such that the field is zero when a new
rel has an old rd_node.  Make relcache.c retain entries for certain
dropped relations until end of transaction.

Bump XLOG_PAGE_MAGIC, since this introduces XLOG_GIST_ASSIGN_LSN.
Future servers accept older WAL, so this bump is discretionary.

Kyotaro Horiguchi, reviewed (in earlier, similar versions) by Robert
Haas.  Heikki Linnakangas and Michael Paquier implemented earlier
designs that materially clarified the problem.  Reviewed, in earlier
designs, by Andrew Dunstan, Andres Freund, Alvaro Herrera, Tom Lane,
Fujii Masao, and Simon Riggs.  Reported by Martijn van Oosterhout.

Discussion: https://postgr.es/m/20150702220524.GA9392@svana.org
  • Loading branch information
nmisch committed Apr 4, 2020
1 parent 552fceb commit c6b9204
Show file tree
Hide file tree
Showing 53 changed files with 1,565 additions and 371 deletions.
35 changes: 35 additions & 0 deletions contrib/pg_visibility/expected/pg_visibility.out
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
CREATE EXTENSION pg_visibility;
--
-- recently-dropped table
--
\set VERBOSITY sqlstate
BEGIN;
CREATE TABLE droppedtest (c int);
SELECT 'droppedtest'::regclass::oid AS oid \gset
SAVEPOINT q; DROP TABLE droppedtest; RELEASE q;
SAVEPOINT q; SELECT * FROM pg_visibility_map(:oid); ROLLBACK TO q;
ERROR: XX000
-- ERROR: could not open relation with OID 16xxx
SAVEPOINT q; SELECT 1; ROLLBACK TO q;
?column?
----------
1
(1 row)

SAVEPOINT q; SELECT 1; ROLLBACK TO q;
?column?
----------
1
(1 row)

SELECT pg_relation_size(:oid), pg_relation_filepath(:oid),
has_table_privilege(:oid, 'SELECT');
pg_relation_size | pg_relation_filepath | has_table_privilege
------------------+----------------------+---------------------
| |
(1 row)

SELECT * FROM pg_visibility_map(:oid);
ERROR: XX000
-- ERROR: could not open relation with OID 16xxx
ROLLBACK;
\set VERBOSITY default
--
-- check that using the module's functions with unsupported relations will fail
--
-- partitioned tables (the parent ones) don't have visibility maps
Expand Down
19 changes: 19 additions & 0 deletions contrib/pg_visibility/sql/pg_visibility.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
CREATE EXTENSION pg_visibility;

--
-- recently-dropped table
--
\set VERBOSITY sqlstate
BEGIN;
CREATE TABLE droppedtest (c int);
SELECT 'droppedtest'::regclass::oid AS oid \gset
SAVEPOINT q; DROP TABLE droppedtest; RELEASE q;
SAVEPOINT q; SELECT * FROM pg_visibility_map(:oid); ROLLBACK TO q;
-- ERROR: could not open relation with OID 16xxx
SAVEPOINT q; SELECT 1; ROLLBACK TO q;
SAVEPOINT q; SELECT 1; ROLLBACK TO q;
SELECT pg_relation_size(:oid), pg_relation_filepath(:oid),
has_table_privilege(:oid, 'SELECT');
SELECT * FROM pg_visibility_map(:oid);
-- ERROR: could not open relation with OID 16xxx
ROLLBACK;
\set VERBOSITY default

--
-- check that using the module's functions with unsupported relations will fail
--
Expand Down
40 changes: 32 additions & 8 deletions doc/src/sgml/config.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -2496,16 +2496,19 @@ include_dir 'conf.d'
levels. This parameter can only be set at server start.
</para>
<para>
In <literal>minimal</literal> level, WAL-logging of some bulk
operations can be safely skipped, which can make those
operations much faster (see <xref linkend="populate-pitr"/>).
Operations in which this optimization can be applied include:
In <literal>minimal</literal> level, no information is logged for
permanent relations for the remainder of a transaction that creates or
rewrites them. This can make operations much faster (see
<xref linkend="populate-pitr"/>). Operations that initiate this
optimization include:
<simplelist>
<member><command>CREATE TABLE AS</command></member>
<member><command>CREATE INDEX</command></member>
<member><command>ALTER ... SET TABLESPACE</command></member>
<member><command>CLUSTER</command></member>
<member><command>COPY</command> into tables that were created or truncated in the same
transaction</member>
<member><command>CREATE TABLE</command></member>
<member><command>REFRESH MATERIALIZED VIEW</command>
(without <option>CONCURRENTLY</option>)</member>
<member><command>REINDEX</command></member>
<member><command>TRUNCATE</command></member>
</simplelist>
But minimal WAL does not contain enough information to reconstruct the
data from a base backup and the WAL logs, so <literal>replica</literal> or
Expand Down Expand Up @@ -2902,6 +2905,27 @@ include_dir 'conf.d'
</listitem>
</varlistentry>

<varlistentry id="guc-wal-skip-threshold" xreflabel="wal_skip_threshold">
<term><varname>wal_skip_threshold</varname> (<type>integer</type>)
<indexterm>
<primary><varname>wal_skip_threshold</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
When <varname>wal_level</varname> is <literal>minimal</literal> and a
transaction commits after creating or rewriting a permanent relation,
this setting determines how to persist the new data. If the data is
smaller than this setting, write it to the WAL log; otherwise, use an
fsync of affected files. Depending on the properties of your storage,
raising or lowering this value might help if such commits are slowing
concurrent transactions. If this value is specified without units, it
is taken as kilobytes. The default is two megabytes
(<literal>2MB</literal>).
</para>
</listitem>
</varlistentry>

<varlistentry id="guc-commit-delay" xreflabel="commit_delay">
<term><varname>commit_delay</varname> (<type>integer</type>)
<indexterm>
Expand Down
47 changes: 9 additions & 38 deletions doc/src/sgml/perform.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -1607,8 +1607,8 @@ SELECT * FROM x, y, a, b, c WHERE something AND somethingelse;
needs to be written, because in case of an error, the files
containing the newly loaded data will be removed anyway.
However, this consideration only applies when
<xref linkend="guc-wal-level"/> is <literal>minimal</literal> for
non-partitioned tables as all commands must write WAL otherwise.
<xref linkend="guc-wal-level"/> is <literal>minimal</literal>
as all commands must write WAL otherwise.
</para>

</sect2>
Expand Down Expand Up @@ -1708,42 +1708,13 @@ SELECT * FROM x, y, a, b, c WHERE something AND somethingelse;
</para>

<para>
Aside from avoiding the time for the archiver or WAL sender to
process the WAL data,
doing this will actually make certain commands faster, because they
are designed not to write WAL at all if <varname>wal_level</varname>
is <literal>minimal</literal>. (They can guarantee crash safety more cheaply
by doing an <function>fsync</function> at the end than by writing WAL.)
This applies to the following commands:
<itemizedlist>
<listitem>
<para>
<command>CREATE TABLE AS SELECT</command>
</para>
</listitem>
<listitem>
<para>
<command>CREATE INDEX</command> (and variants such as
<command>ALTER TABLE ADD PRIMARY KEY</command>)
</para>
</listitem>
<listitem>
<para>
<command>ALTER TABLE SET TABLESPACE</command>
</para>
</listitem>
<listitem>
<para>
<command>CLUSTER</command>
</para>
</listitem>
<listitem>
<para>
<command>COPY FROM</command>, when the target table has been
created or truncated earlier in the same transaction
</para>
</listitem>
</itemizedlist>
Aside from avoiding the time for the archiver or WAL sender to process the
WAL data, doing this will actually make certain commands faster, because
they do not to write WAL at all if <varname>wal_level</varname>
is <literal>minimal</literal> and the current subtransaction (or top-level
transaction) created or truncated the table or index they change. (They
can guarantee crash safety more cheaply by doing
an <function>fsync</function> at the end than by writing WAL.)
</para>
</sect2>

Expand Down
31 changes: 26 additions & 5 deletions src/backend/access/gist/gistutil.c
Original file line number Diff line number Diff line change
Expand Up @@ -1004,23 +1004,44 @@ gistproperty(Oid index_oid, int attno,
}

/*
* Temporary and unlogged GiST indexes are not WAL-logged, but we need LSNs
* to detect concurrent page splits anyway. This function provides a fake
* sequence of LSNs for that purpose.
* Some indexes are not WAL-logged, but we need LSNs to detect concurrent page
* splits anyway. This function provides a fake sequence of LSNs for that
* purpose.
*/
XLogRecPtr
gistGetFakeLSN(Relation rel)
{
static XLogRecPtr counter = FirstNormalUnloggedLSN;

if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
{
/*
* Temporary relations are only accessible in our session, so a simple
* backend-local counter will do.
*/
static XLogRecPtr counter = FirstNormalUnloggedLSN;

return counter++;
}
else if (rel->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
{
/*
* WAL-logging on this relation will start after commit, so its LSNs
* must be distinct numbers smaller than the LSN at the next commit.
* Emit a dummy WAL record if insert-LSN hasn't advanced after the
* last call.
*/
static XLogRecPtr lastlsn = InvalidXLogRecPtr;
XLogRecPtr currlsn = GetXLogInsertRecPtr();

/* Shouldn't be called for WAL-logging relations */
Assert(!RelationNeedsWAL(rel));

/* No need for an actual record if we already have a distinct LSN */
if (!XLogRecPtrIsInvalid(lastlsn) && lastlsn == currlsn)
currlsn = gistXLogAssignLSN();

lastlsn = currlsn;
return currlsn;
}
else
{
/*
Expand Down
21 changes: 21 additions & 0 deletions src/backend/access/gist/gistxlog.c
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,9 @@ gist_redo(XLogReaderState *record)
case XLOG_GIST_PAGE_DELETE:
gistRedoPageDelete(record);
break;
case XLOG_GIST_ASSIGN_LSN:
/* nop. See gistGetFakeLSN(). */
break;
default:
elog(PANIC, "gist_redo: unknown op code %u", info);
}
Expand Down Expand Up @@ -592,6 +595,24 @@ gistXLogPageDelete(Buffer buffer, FullTransactionId xid,
return recptr;
}

/*
* Write an empty XLOG record to assign a distinct LSN.
*/
XLogRecPtr
gistXLogAssignLSN(void)
{
int dummy = 0;

/*
* Records other than SWITCH_WAL must have content. We use an integer 0 to
* follow the restriction.
*/
XLogBeginInsert();
XLogSetRecordFlags(XLOG_MARK_UNIMPORTANT);
XLogRegisterData((char *) &dummy, sizeof(dummy));
return XLogInsert(RM_GIST_ID, XLOG_GIST_ASSIGN_LSN);
}

/*
* Write XLOG record about reuse of a deleted page.
*/
Expand Down
45 changes: 2 additions & 43 deletions src/backend/access/heap/heapam.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
* heap_multi_insert - insert multiple tuples into a relation
* heap_delete - delete a tuple from a relation
* heap_update - replace a tuple in a relation with another tuple
* heap_sync - sync heap, for when no WAL has been written
*
* NOTES
* This file contains the heap_ routines which implement
Expand Down Expand Up @@ -1939,7 +1938,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
MarkBufferDirty(buffer);

/* XLOG stuff */
if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation))
if (RelationNeedsWAL(relation))
{
xl_heap_insert xlrec;
xl_heap_header xlhdr;
Expand Down Expand Up @@ -2122,7 +2121,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
/* currently not needed (thus unsupported) for heap_multi_insert() */
AssertArg(!(options & HEAP_INSERT_NO_LOGICAL));

needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
needwal = RelationNeedsWAL(relation);
saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
HEAP_DEFAULT_FILLFACTOR);

Expand Down Expand Up @@ -8920,46 +8919,6 @@ heap2_redo(XLogReaderState *record)
}
}

/*
* heap_sync - sync a heap, for use when no WAL has been written
*
* This forces the heap contents (including TOAST heap if any) down to disk.
* If we skipped using WAL, and WAL is otherwise needed, we must force the
* relation down to disk before it's safe to commit the transaction. This
* requires writing out any dirty buffers and then doing a forced fsync.
*
* Indexes are not touched. (Currently, index operations associated with
* the commands that use this are WAL-logged and so do not need fsync.
* 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);

/* FSM is not critical, don't bother syncing it */

/* toast heap, if any */
if (OidIsValid(rel->rd_rel->reltoastrelid))
{
Relation toastrel;

toastrel = table_open(rel->rd_rel->reltoastrelid, AccessShareLock);
FlushRelationBuffers(toastrel);
smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
table_close(toastrel, AccessShareLock);
}
}

/*
* Mask a heap page before performing consistency checks on it.
*/
Expand Down
22 changes: 3 additions & 19 deletions src/backend/access/heap/heapam_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -555,17 +555,6 @@ heapam_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot,
return result;
}

static void
heapam_finish_bulk_insert(Relation relation, int options)
{
/*
* If we skipped writing WAL, then we need to sync the heap (but not
* indexes since those use WAL anyway / don't go through tableam)
*/
if (options & HEAP_INSERT_SKIP_WAL)
heap_sync(relation);
}


/* ------------------------------------------------------------------------
* DDL related callbacks for heap AM.
Expand Down Expand Up @@ -698,7 +687,6 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
bool use_wal;
bool is_system_catalog;
Tuplesortstate *tuplesort;
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
Expand All @@ -713,12 +701,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
is_system_catalog = IsSystemRelation(OldHeap);

/*
* We need to log the copied data in WAL iff WAL archiving/streaming is
* enabled AND it's a WAL-logged rel.
* Valid smgr_targblock implies something already wrote to the relation.
* This may be harmless, but this function hasn't planned for it.
*/
use_wal = XLogIsNeeded() && RelationNeedsWAL(NewHeap);

/* use_wal off requires smgr_targblock be initially invalid */
Assert(RelationGetTargetBlock(NewHeap) == InvalidBlockNumber);

/* Preallocate values/isnull arrays */
Expand All @@ -728,7 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,

/* Initialize the rewrite operation */
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff,
*multi_cutoff, use_wal);
*multi_cutoff);


/* Set up sorting if wanted */
Expand Down Expand Up @@ -2525,7 +2510,6 @@ static const TableAmRoutine heapam_methods = {
.tuple_delete = heapam_tuple_delete,
.tuple_update = heapam_tuple_update,
.tuple_lock = heapam_tuple_lock,
.finish_bulk_insert = heapam_finish_bulk_insert,

.tuple_fetch_row_version = heapam_fetch_row_version,
.tuple_get_latest_tid = heap_get_latest_tid,
Expand Down

0 comments on commit c6b9204

Please sign in to comment.