diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 0cc3296..60bebee 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1560,7 +1560,8 @@ SET ENABLE_SEQSCAN TO OFF; Specifies whether transaction commit will wait for WAL records to be written to disk before the command returns a success indication to the client. Valid values are on, - local, and off. The default, and safe, value + local, fallback and off. + The default, and safe, value is on. When off, there can be a delay between when success is reported to the client and when the transaction is really guaranteed to be safe against a server crash. (The maximum @@ -1574,6 +1575,10 @@ SET ENABLE_SEQSCAN TO OFF; can be a useful alternative when performance is more important than exact certainty about the durability of a transaction. For more discussion see . + If set to fallback, the master will act as if it was set to + on except in the special case where all suitable synchronous + standbys are currentlydisconnected, in which case it will temporarily fall + back to local mode until suitable standbys are connected. If is set, this diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c index e9ae1e8..7c39bad 100644 --- a/src/backend/postmaster/checkpointer.c +++ b/src/backend/postmaster/checkpointer.c @@ -352,7 +352,7 @@ CheckpointerMain(void) ThisTimeLineID = GetRecoveryTargetTLI(); /* Do this once before starting the loop, then just at SIGHUP time. */ - SyncRepUpdateSyncStandbysDefined(); + SyncRepUpdateConfig(); /* * Loop forever @@ -381,7 +381,7 @@ CheckpointerMain(void) got_SIGHUP = false; ProcessConfigFile(PGC_SIGHUP); /* update global shmem state for sync rep */ - SyncRepUpdateSyncStandbysDefined(); + SyncRepUpdateConfig(); } if (checkpoint_requested) { @@ -657,7 +657,7 @@ CheckpointWriteDelay(int flags, double progress) got_SIGHUP = false; ProcessConfigFile(PGC_SIGHUP); /* update global shmem state for sync rep */ - SyncRepUpdateSyncStandbysDefined(); + SyncRepUpdateConfig(); } AbsorbFsyncRequests(); diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c index 95de6c7..559c89c 100644 --- a/src/backend/replication/syncrep.c +++ b/src/backend/replication/syncrep.c @@ -126,6 +126,17 @@ SyncRepWaitForLSN(XLogRecPtr XactCommitLSN) return; } + + /* + * Fast exit also if running in standalone mode + * because there are no synchronous standbys connected + */ + if ( WalSndCtl->sync_standalone_master ) + { + LWLockRelease(SyncRepLock); + return; + } + /* * Set our waitLSN so WALSender will know when to wake us, and add * ourselves to the queue. @@ -326,6 +337,58 @@ SyncRepCleanupAtProcExit(void) } /* + * Check if the master should switch to standalone mode and stop trying + * to wait for standby synchronization because there are no standby servers currently + * connected. If there are servers connected, then switch back and start waiting for them. + * This function is called on connect/disconnect of standby WAL senders. Must hold SyncRepLock. + */ +void SyncRepCheckIfStandaloneMaster() +{ + bool sync_standby_connected = false; + int i = 0; + + if (!SyncRepRequested() || !SyncStandbysDefined() || ! WalSndCtl->sync_standalone_allowed) + { + WalSndCtl->sync_standalone_master = false; + return; + } + + for (i = 0; i < max_wal_senders && ! sync_standby_connected; i++) + { + volatile WalSnd *walsnd = &WalSndCtl->walsnds[i]; + if ( walsnd->pid != 0 && walsnd->sync_standby_priority ) + { + sync_standby_connected = true; + if ( WalSndCtl->sync_standalone_master ) + { + ereport(LOG, + (errmsg("waiting for standby synchronization"), + errhidestmt(true))); + + WalSndCtl->sync_standalone_master = false; + } + } + } + + if ( ! sync_standby_connected ) + { + if ( ! WalSndCtl->sync_standalone_master ) + { + ereport(LOG, + (errmsg("not waiting for standby synchronization"), + errhidestmt(true))); + + WalSndCtl->sync_standalone_master = true; + + /* + * We just switched to standalone mode so wake up anyone that is waiting + */ + SyncRepWakeQueue(true); + } + } +} + +/* * =========================================================== * Synchronous Replication functions for wal sender processes * =========================================================== @@ -345,10 +408,11 @@ SyncRepInitConfig(void) * for handling replies from standby. */ priority = SyncRepGetStandbyPriority(); - if (MyWalSnd->sync_standby_priority != priority) + if (priority) { LWLockAcquire(SyncRepLock, LW_EXCLUSIVE); MyWalSnd->sync_standby_priority = priority; + SyncRepCheckIfStandaloneMaster(); LWLockRelease(SyncRepLock); ereport(DEBUG1, (errmsg("standby \"%s\" now has synchronous standby priority %u", @@ -567,6 +631,17 @@ SyncRepWakeQueue(bool all) } /* + * The background writer calls this as needed to update the shared WalSndCtl + * with new sync-related config. + */ +void +SyncRepUpdateConfig(void) +{ + SyncRepUpdateSyncStandbysDefined(); + SyncRepUpdateSyncStandaloneAllowed(); +} + +/* * The background writer calls this as needed to update the shared * sync_standbys_defined flag, so that backends don't remain permanently wedged * if synchronous_standby_names is unset. It's safe to check the current value @@ -598,6 +673,28 @@ SyncRepUpdateSyncStandbysDefined(void) * the queue (and never wake up). This prevents that. */ WalSndCtl->sync_standbys_defined = sync_standbys_defined; + LWLockRelease(SyncRepLock); + } + + +} + +/* + * The background writer calls this as needed to update the shared + * sync_standalone_allowed flag. If the flag is enabled, then also check if + * any synchronous standby servers are connected in order to switch mode from sync + * replication to standalone mode. + */ +void +SyncRepUpdateSyncStandaloneAllowed(void) +{ + bool SyncRepStandaloneMasterAllowed = SyncRepRequested() && synchronous_commit == SYNCHRONOUS_COMMIT_FALLBACK; + if ( SyncRepStandaloneMasterAllowed != WalSndCtl->sync_standalone_allowed ) + { + LWLockAcquire(SyncRepLock, LW_EXCLUSIVE); + + WalSndCtl->sync_standalone_allowed = SyncRepStandaloneMasterAllowed; + SyncRepCheckIfStandaloneMaster(); LWLockRelease(SyncRepLock); } diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index ea86520..1da44e0 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -955,6 +955,13 @@ WalSndKill(int code, Datum arg) MyWalSnd->pid = 0; DisownLatch(&MyWalSnd->latch); + /* + * Check if this was the last standby + */ + LWLockAcquire(SyncRepLock, LW_EXCLUSIVE); + SyncRepCheckIfStandaloneMaster(); + LWLockRelease(SyncRepLock); + /* WalSnd struct isn't mine anymore */ MyWalSnd = NULL; } diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index da7b6d4..f2d8f96 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -370,11 +370,12 @@ static const struct config_enum_entry constraint_exclusion_options[] = { }; /* - * Although only "on", "off", and "local" are documented, we + * Although only "on", "off", "fallback" and "local" are documented, we * accept all the likely variants of "on" and "off". */ static const struct config_enum_entry synchronous_commit_options[] = { {"local", SYNCHRONOUS_COMMIT_LOCAL_FLUSH, false}, + {"fallback", SYNCHRONOUS_COMMIT_FALLBACK, false}, {"on", SYNCHRONOUS_COMMIT_ON, false}, {"off", SYNCHRONOUS_COMMIT_OFF, false}, {"true", SYNCHRONOUS_COMMIT_ON, true}, diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 315db46..83bc120 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -158,7 +158,7 @@ #wal_level = minimal # minimal, archive, or hot_standby # (change requires restart) #fsync = on # turns forced synchronization on or off -#synchronous_commit = on # synchronization level; on, off, or local +#synchronous_commit = on # synchronization level; on, off, fallback or local #wal_sync_method = fsync # the default is the first option # supported by the operating system: # open_datasync @@ -215,6 +215,7 @@ # from standby(s); '*' = all #vacuum_defer_cleanup_age = 0 # number of xacts by which cleanup is delayed + # - Standby Servers - # These settings are ignored on a master server diff --git a/src/include/access/xact.h b/src/include/access/xact.h index aaa6204..6e8f7dc 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -55,6 +55,7 @@ typedef enum { SYNCHRONOUS_COMMIT_OFF, /* asynchronous commit */ SYNCHRONOUS_COMMIT_LOCAL_FLUSH, /* wait for local flush only */ + SYNCHRONOUS_COMMIT_FALLBACK, /* wait for local and remote flush, if connected standbys */ SYNCHRONOUS_COMMIT_REMOTE_FLUSH /* wait for local and remote flush */ } SyncCommitLevel; diff --git a/src/include/replication/syncrep.h b/src/include/replication/syncrep.h index 65b725f..66a2c71 100644 --- a/src/include/replication/syncrep.h +++ b/src/include/replication/syncrep.h @@ -34,7 +34,9 @@ extern void SyncRepInitConfig(void); extern void SyncRepReleaseWaiters(void); /* called by wal writer */ -extern void SyncRepUpdateSyncStandbysDefined(void); +extern void SyncRepUpdateSyncConfig(void); + +extern void SyncRepCheckIfStandaloneMaster(void); /* called by various procs */ extern int SyncRepWakeQueue(bool all); diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h index be7a341..954c79f 100644 --- a/src/include/replication/walsender_private.h +++ b/src/include/replication/walsender_private.h @@ -85,6 +85,17 @@ typedef struct */ bool sync_standbys_defined; + /* + * Is the synchronous master allowed to switch to standalone mode when no + * synchronous standby servers are connected ? Protected by SyncRepLock. + */ + bool sync_standalone_allowed; + + /* + * Is the synchronous master currently running in standalone mode ? Protected by SyncRepLock. + */ + bool sync_standalone_master; + WalSnd walsnds[1]; /* VARIABLE LENGTH ARRAY */ } WalSndCtlData;