v5-0002-rework.patch
text/x-patch
Filename: v5-0002-rework.patch
Type: text/x-patch
Part: 1
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: format-patch
Series: patch v5-0002
Subject: rework
| File | + | − |
|---|---|---|
| src/backend/access/transam/xact.c | 3 | 0 |
| src/backend/access/transam/xlog.c | 311 | 27 |
| src/backend/utils/misc/guc_tables.c | 26 | 2 |
| src/include/access/xlog.h | 5 | 1 |
From 4a14b9edbcf79075c050cd60fc28a80c53452bc1 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@2ndquadrant.com>
Date: Fri, 10 Nov 2023 17:37:41 +0100
Subject: [PATCH v5 2/2] rework
---
src/backend/access/transam/xact.c | 3 +
src/backend/access/transam/xlog.c | 338 +++++++++++++++++++++++++---
src/backend/utils/misc/guc_tables.c | 28 ++-
src/include/access/xlog.h | 6 +-
4 files changed, 345 insertions(+), 30 deletions(-)
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 536edb3792f..9a333e8533e 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2139,6 +2139,9 @@ StartTransaction(void)
*/
s->state = TRANS_INPROGRESS;
+ /* reset limits for WAL throttling */
+ ResetXLogThrottling();
+
ShowTransactionState("StartTransaction");
}
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 5dab46e17e1..b44e3c84d3d 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -140,7 +140,16 @@ int wal_retrieve_retry_interval = 5000;
int max_slot_wal_keep_size_mb = -1;
int wal_decode_buffer_size = 512 * 1024;
bool track_wal_io_timing = false;
-int wal_throttle_threshold = 0;
+
+/* user-settable parameters for WAL throttling */
+int wal_write_after = 0;
+int wal_flush_after_local = 0;
+int wal_flush_after_remote = 0;
+
+/* auto-adjusted version of the throttling parameters */
+int wal_write_after_current = 0;
+int wal_flush_after_local_current = 0;
+int wal_flush_after_remote_current = 0;
#ifdef WAL_DEBUG
bool XLOG_DEBUG = false;
@@ -255,16 +264,10 @@ static int LocalXLogInsertAllowed = -1;
* parallel backends may have written WAL records at later LSNs than the value
* stored here. The parallel leader advances its own copy, when necessary,
* in WaitForParallelWorkersToFinish.
- *
- * XactLastThrottledRecEnd points to the last XLOG record that should be throttled
- * as the additional WAL records could be generated before processing interrupts.
- *
- * XXX I'm not sure I understand what "record to be throttled" means?
*/
XLogRecPtr ProcLastRecPtr = InvalidXLogRecPtr;
XLogRecPtr XactLastRecEnd = InvalidXLogRecPtr;
XLogRecPtr XactLastCommitEnd = InvalidXLogRecPtr;
-static XLogRecPtr XactLastThrottledRecEnd = InvalidXLogRecPtr;
/*
* RedoRecPtr is this backend's local copy of the REDO record pointer
@@ -663,7 +666,8 @@ static MemoryContext walDebugCxt = NULL;
*
* XXX Not sure it should refer to "backend", it's really about xact, no?
*/
-uint32 backendWalInserted = 0;
+uint32 backendWalInserted = 0;
+XLogRecPtr backendLastFlushPtr = InvalidXLogRecPtr;
static void CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI,
XLogRecPtr EndOfLog,
@@ -1094,17 +1098,48 @@ XLogInsertRecord(XLogRecData *rdata,
/*
* Decide if we need to throttle this backend, so that it does not write
* WAL too fast, causing lag against the sync standby (which in turn
- * increases latency for standby confirmations). We may be holding locks
- * and blocking interrupts here, so we only make the decision, but the
- * wait (for sync standby confirmation) happens elsewhere.
+ * increases latency both for local commits and for standby confirmations).
+ * We may be holding locks and blocking interrupts here, so we only make
+ * the decisions here, but the wait (for sync standby confirmation)
+ * happens elsewhere in processing interrupts.
+ *
+ * This is somewhat similar to what walwriter does, except that walwriter
+ * is driven by time (not by amount of WAL written), and does not throttle
+ * the backends (in a way, the intention is to do exactly the opposite by
+ * taking away the responsibility for writing WAL from backends).
+ *
+ * We may decide to do one or more of these things:
+ *
+ * - write the WAL locally (in the background, no throttle)
+ * - flush the WAL locally (throttle, if synchronous_commit == local)
+ * - wait for sync replica (throttle, if synchronous_commit != local)
+ *
+ * The first point (driven by wal_write_after) is meant to start writing
+ * WAL to disk early, so that the subsequent flush is faster. This does
+ * not do any flushes, it merely writes WAL data to page cache.
*
- * The throttling is applied only to large transactions (producing more
- * than wal_throttle_threshold kilobytes of WAL). Throttled backends
- * can be identified by a new wait event SYNC_REP_THROTTLED.
+ * The second point (driven by wal_flush_after_local) forces the backend
+ * to actually flush WAL to disk, to reduce the amount of unflushed WAL.
+ * This is a synchronous operation, and is applied only to transactions
+ * that generate enough WAL (e.g. 256kB). The goal is to limit impact of
+ * large transactions (producing a lot of WAL) on small OLTP ones.
+ *
+ * The third point (driven by wal_flush_after_remote) is similar, except
+ * that it throttles based on confirmations from a sync replica.
+ *
+ * The throttling is applied only to large transactions, and the backends
+ * affected by the throttling can be identified by a new wait event
+ * SYNC_REP_THROTTLED.
*
* Small transactions (by amount of produced WAL) are still subject to
* the sync replication, so the same wait happens at commit time.
*
+ * All of those thresholds are auto-tuned, i.e. the value is adjusted
+ * based on concurrent activity. All the large transactions need to
+ * share the WAL budgets, somehow. We do that by adjusting the value
+ * by observing the distance since the last write/flush, and comparing
+ * it to the current limit.
+ *
* XXX Not sure this is the right place for a comment explaining how the
* throttling works. This place is way too low level, and rather far from
* the place where the wait actually happens.
@@ -1112,14 +1147,47 @@ XLogInsertRecord(XLogRecData *rdata,
* XXX Should this be done even if XLogDelayPending is already set? Maybe
* that should only update XactLastThrottledRecEnd, withoug incrementing
* the pgWalUsage.wal_throttled counter?
+ *
+ * XXX Maybe the different cases should have separate wait events?
+ *
+ * XXX Note that for the remote WAL, we need to also enforce the local
+ * flush throttling, because we only send data to replica after it was
+ * flushed locally.
+ *
+ * XXX I guess to make this work well, we should ensure
+ * (wal_write_after < wal_flush_after_local < wal_flush_after_remove)
*/
backendWalInserted += rechdr->xl_tot_len;
- if ((synchronous_commit >= SYNCHRONOUS_COMMIT_REMOTE_WRITE) &&
- (wal_throttle_threshold > 0) &&
- (backendWalInserted >= wal_throttle_threshold * 1024L))
+ /*
+ * Should we issue the WAL write to local disk (without a flush)?
+ */
+ if ((wal_write_after_current > 0) &&
+ (backendWalInserted >= wal_write_after_current * 1024L))
+ {
+ InterruptPending = true;
+ XLogDelayPending = true;
+ }
+
+ /*
+ * Should we flush the WAL to local disk?
+ */
+ else if ((synchronous_commit >= SYNCHRONOUS_COMMIT_LOCAL_FLUSH) &&
+ (wal_flush_after_local_current > 0) &&
+ (backendWalInserted >= wal_flush_after_local_current * 1024L))
+ {
+ InterruptPending = true;
+ XLogDelayPending = true;
+ pgWalUsage.wal_throttled++;
+ }
+
+ /*
+ * Should we flush the WAL to sync replica?
+ */
+ else if ((synchronous_commit >= SYNCHRONOUS_COMMIT_REMOTE_WRITE) &&
+ (wal_flush_after_remote_current > 0) &&
+ (backendWalInserted >= wal_flush_after_remote_current * 1024L))
{
- XactLastThrottledRecEnd = XactLastRecEnd;
InterruptPending = true;
XLogDelayPending = true;
pgWalUsage.wal_throttled++;
@@ -2703,6 +2771,7 @@ XLogFlush(XLogRecPtr record)
* XXX For the approximation we should probably use LogwrtResult.Flush.
*/
backendWalInserted = 0;
+ backendLastFlushPtr = record;
/* Quick exit if already known flushed */
if (record <= LogwrtResult.Flush)
@@ -9234,19 +9303,234 @@ SetWalWriterSleeping(bool sleeping)
* XXX Not sure why we use XactLastThrottledRecEnd and not simply XLogRecEnd?
*/
void
-HandleXLogDelayPending()
+HandleXLogDelayPending(void)
{
- XLogRecPtr lsn;
+ /* need to remember the values, because XLogFlush may reset them */
+ uint32 wal_inserted = backendWalInserted;
+ XLogRecPtr last_flush_lsn = backendLastFlushPtr;
+ XLogRecPtr flush_lsn = InvalidXLogRecPtr;
+
+ /* make sure we're not in recovery - timeline could change, etc. */
+ Assert(!InRecovery);
+
+ /*
+ * XactLastRecEnd gets reset once in a while (e.g. after commit or abort),
+ * and if such record just gets us over the throttling threshold, we'll see
+ * 0 here. We just ignore the trottling request in that case - one might
+ * argue the commit already does the throttling anyway (flush and wait
+ * for sync replica), and it should be a fairly rare case (How likely is
+ * it that a large transaction crosses the limit just with when writing
+ * the commit record?).
+ *
+ * XXX An alternative would be to have a separate variable, storing the
+ * XactLastRecEnd value we've seen while deciding to throttle (in fact an
+ * earlier patch did that). But it does not seem worth it.
+ */
+ if (XactLastRecEnd == InvalidXLogRecPtr)
+ {
+ XLogDelayPending = false;
+ return;
+ }
+
+ /*
+ * read LogwrtResult and update local state
+ *
+ * XXX Maybe we should use the cached local LogwrtResult copy to determine if
+ * we need to a write/flush? That wouldn't require a spinlock, and in the worst
+ * case we'd see a bit stale values and perhaps issue a write/flush early.
+ * However, we shouldn't to this throttling very often, so seems fine.
+ */
+ SpinLockAcquire(&XLogCtl->info_lck);
+ LogwrtResult = XLogCtl->LogwrtResult;
+ SpinLockRelease(&XLogCtl->info_lck);
+
+ /*
+ * First decide if we need to issue a write to local WAL. We assume this LSN
+ * will be ahead of LSN for either flush (local or remote), so let's do this
+ * first.
+ */
+ if ((wal_write_after_current > 0) &&
+ (wal_inserted >= wal_write_after_current * 1024L))
+ {
+ /*
+ * We want to keep write lag below wal_write_after_current, but we don't
+ * want to write partial pages (because of write amplification).
+ */
+ XLogRecPtr lsn = XactLastRecEnd - wal_write_after_current * 1024L;
+
+ /* truncate to the last full page */
+ lsn = lsn - (lsn % XLOG_BLCKSZ);
+
+ /* Do the WAL write unless someone already issued the write earlier. */
+ if (lsn > LogwrtResult.Write)
+ {
+ START_CRIT_SECTION();
+
+ /*
+ * Before actually performing the write, wait for all in-flight
+ * insertions to the pages we're about to write to finish.
+ */
+ lsn = WaitXLogInsertionsToFinish(lsn);
+
+ LWLockAcquire(WALWriteLock, LW_EXCLUSIVE);
+
+ LogwrtResult = XLogCtl->LogwrtResult;
+ if (LogwrtResult.Write >= lsn)
+ {
+ /* OK, someone wrote it already */
+ LWLockRelease(WALWriteLock);
+ }
+ else
+ {
+ XLogwrtRqst WriteRqst;
+
+ /* Have to write it ourselves */
+ TRACE_POSTGRESQL_WAL_BUFFER_WRITE_DIRTY_START();
+ WriteRqst.Write = lsn;
+ WriteRqst.Flush = 0;
+ XLogWrite(WriteRqst, XLogCtl->InsertTimeLineID, false);
+ LWLockRelease(WALWriteLock);
+ PendingWalStats.wal_buffers_full++; // FIXME
+ TRACE_POSTGRESQL_WAL_BUFFER_WRITE_DIRTY_DONE();
+ }
+
+ END_CRIT_SECTION();
+ }
+ }
+
+ /* Now consider if we should do a local flush. */
+ if ((synchronous_commit >= SYNCHRONOUS_COMMIT_LOCAL_FLUSH) &&
+ (wal_flush_after_local_current > 0) &&
+ (wal_inserted >= wal_flush_after_local_current * 1024L))
+ {
+ /* flush lag should be less than wal_flush_after_local_current (kB) */
+ XLogRecPtr lsn = XactLastRecEnd - wal_flush_after_local_current * 1024L;
+
+ /* truncate to the last full */
+ lsn = lsn - (lsn % XLOG_BLCKSZ);
+
+ /* do the flush (will reset backendWalInserted etc.) */
+ XLogFlush(lsn);
+
+ /* remember the LSN of this flush */
+ flush_lsn = lsn;
+ }
+
+ /*
+ * Finally, decide if we should wait for confirmation from a sync replica.
+ */
+ if ((synchronous_commit >= SYNCHRONOUS_COMMIT_REMOTE_WRITE) &&
+ (wal_flush_after_remote_current > 0) &&
+ (wal_inserted >= wal_flush_after_remote_current * 1024L))
+ {
+ /* sync flush lag should be less than wal_flush_after_remote_current (kB) */
+ XLogRecPtr lsn = XactLastRecEnd - wal_flush_after_remote_current * 1024L;
+
+ /* truncate to the last full page */
+ lsn = lsn - (lsn % XLOG_BLCKSZ);
+
+ SyncRepWaitForLSN(lsn, false);
+ }
+
+ /*
+ * Now adjust the thresholds based on the distance since the last flush in this
+ * backend, and the fraction of WAL this backend was responsible for.
+ */
+ if (flush_lsn != InvalidXLogRecPtr)
+ {
+ /* Fraction of WAL between the flushes was produced by this backend. */
+ double wal_fraction = (double) wal_inserted / (flush_lsn - last_flush_lsn);
+
+ double sum = 0;
+ int cnt = 0;
+
+ /*
+ * If the fraction is sufficiently far from 1.0 or close to it, we adjust
+ * the limits to get close to the "ideal" value based on the fraction. We
+ * don't immediately switch to the new value, but instead change the values
+ * gradually by halving/doubling the previous value.
+ *
+ * The fraction is guaranteed to be less than 1.0, so we halve the limit
+ * for low values below 0.5, double the limit for values above 0.9, and
+ * do nothing for values in between.
+ *
+ * We calculate the desired values for each enabled limit (skip those set
+ * to 0), and then use the average fraction to maintain the proportion.
+ */
- /* calculate last fully filled page */
- lsn = XactLastThrottledRecEnd - (XactLastThrottledRecEnd % XLOG_BLCKSZ);
+ if (wal_fraction < 0.5)
+ {
+ wal_write_after_current /= 2;
+ wal_flush_after_local_current /= 2;
+ wal_flush_after_remote_current /= 2;
+ }
+ else if (wal_fraction > 0.9)
+ {
+ wal_write_after_current *= 2;
+ wal_flush_after_local_current *= 2;
+ wal_flush_after_remote_current *= 2;
+ }
+
+
+ /* clamp to correct range */
+
+ wal_write_after_current
+ = Min(wal_write_after, Max(1, wal_write_after_current));
+
+ wal_flush_after_local_current
+ = Min(wal_flush_after_local, Max(1, wal_flush_after_local_current));
+
+ wal_flush_after_remote_current
+ = Min(wal_flush_after_remote, Max(1, wal_flush_after_remote_current));
+
+
+ /* calculate the average fraction */
+
+ if (wal_write_after > 0)
+ {
+ sum += (double) wal_write_after_current / wal_write_after;
+ cnt += 1;
+ }
+
+ if (wal_flush_after_local > 0)
+ {
+ sum += (double) wal_flush_after_local_current / wal_flush_after_local;
+ cnt += 1;
+ }
+
+ if (wal_flush_after_remote > 0)
+ {
+ sum += (double) wal_flush_after_remote_current / wal_flush_after_remote;
+ cnt += 1;
+ }
+
+ Assert(cnt > 0);
+
+ /* apply the average fraction */
+
+ wal_write_after_current = wal_write_after * (sum / cnt);
+ wal_flush_after_local_current = wal_flush_after_local * (sum / cnt);
+ wal_flush_after_remote_current = wal_flush_after_remote * (sum / cnt);
- Assert(wal_throttle_threshold > 0);
- Assert(backendWalInserted >= wal_throttle_threshold * 1024L);
- Assert(XactLastThrottledRecEnd != InvalidXLogRecPtr);
+ /* clamp to accepted ranges */
- XLogFlush(lsn);
- SyncRepWaitForLSN(lsn, false);
+ wal_write_after_current
+ = Min(wal_write_after, Max(1, wal_write_after_current));
+
+ wal_flush_after_local_current
+ = Min(wal_flush_after_local, Max(1, wal_flush_after_local_current));
+
+ wal_flush_after_remote_current
+ = Min(wal_flush_after_remote, Max(1, wal_flush_after_remote_current));
+ }
XLogDelayPending = false;
}
+
+void
+ResetXLogThrottling(void)
+{
+ wal_write_after_current = wal_write_after;
+ wal_flush_after_local_current = wal_flush_after_local;
+ wal_flush_after_remote_current = wal_flush_after_remote;
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index b14610e0b6e..5f98de1f356 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2893,13 +2893,37 @@ struct config_int ConfigureNamesInt[] =
},
{
- {"wal_throttle_after", PGC_USERSET, REPLICATION_SENDING,
+ {"wal_write_after", PGC_USERSET, REPLICATION_SENDING,
gettext_noop("Sets the maximum amount of WAL in kilobytes a backend generates "
" before waiting for sync standby, to limit the replication lag."),
NULL,
GUC_UNIT_KB
},
- &wal_throttle_threshold,
+ &wal_write_after,
+ 0, 0, MAX_KILOBYTES,
+ NULL, NULL, NULL
+ },
+
+ {
+ {"wal_flush_after_local", PGC_USERSET, REPLICATION_SENDING,
+ gettext_noop("Sets the maximum amount of WAL in kilobytes a backend generates "
+ " before waiting for sync standby, to limit the replication lag."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &wal_flush_after_local,
+ 0, 0, MAX_KILOBYTES,
+ NULL, NULL, NULL
+ },
+
+ {
+ {"wal_flush_after_remote", PGC_USERSET, REPLICATION_SENDING,
+ gettext_noop("Sets the maximum amount of WAL in kilobytes a backend generates "
+ " before waiting for sync standby, to limit the replication lag."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &wal_flush_after_remote,
0, 0, MAX_KILOBYTES,
NULL, NULL, NULL
},
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 9d1175edab2..cb7a15c9520 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -54,7 +54,9 @@ extern PGDLLIMPORT char *wal_consistency_checking_string;
extern PGDLLIMPORT bool log_checkpoints;
extern PGDLLIMPORT bool track_wal_io_timing;
extern PGDLLIMPORT int wal_decode_buffer_size;
-extern PGDLLIMPORT int wal_throttle_threshold;
+extern PGDLLIMPORT int wal_write_after;
+extern PGDLLIMPORT int wal_flush_after_local;
+extern PGDLLIMPORT int wal_flush_after_remote;
extern PGDLLIMPORT uint32 backendWalInserted;
extern PGDLLIMPORT int CheckPointSegments;
@@ -253,6 +255,8 @@ extern XLogRecPtr GetLastImportantRecPtr(void);
extern void SetWalWriterSleeping(bool sleeping);
extern void HandleXLogDelayPending(void);
+extern void HandleXLogDelayPending(void);
+extern void ResetXLogThrottling(void);
/*
* Routines used by xlogrecovery.c to call back into xlog.c during recovery.
--
2.41.0