public inbox for [email protected]help / color / mirror / Atom feed
[PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots 22+ messages / 3 participants [nested] [flat]
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots @ 2020-06-30 12:09 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Kyotaro Horiguchi @ 2020-06-30 12:09 UTC (permalink / raw) pg_replication_slot.min_safe_lsn, which shows the oldest LSN kept in pg_wal, is doubtful in usability for monitoring. Change it to distance, which shows how many bytes the server can advance before the slot loses required segments. --- src/backend/access/transam/xlog.c | 39 +++++++++++++++++++++++ src/backend/catalog/system_views.sql | 2 +- src/backend/replication/slotfuncs.c | 19 +++++------ src/include/access/xlog.h | 1 + src/include/catalog/pg_proc.dat | 4 +-- src/test/recovery/t/019_replslot_limit.pl | 20 ++++++------ src/test/regress/expected/rules.out | 4 +-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fd93bcfaeb..1f27639912 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9570,6 +9570,45 @@ GetWALAvailability(XLogRecPtr targetLSN) return WALAVAIL_REMOVED; } +/* + * Calculate how many bytes we can advance from currptr until the targetLSN is + * removed. + * + * Returns 0 if the distance is invalid. + */ +uint64 +DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr) +{ + XLogSegNo targetSeg; + XLogSegNo keepSegs; + XLogSegNo failSeg; + XLogRecPtr horizon; + + XLByteToSeg(targetLSN, targetSeg, wal_segment_size); + keepSegs = 0; + + /* no limit if max_slot_wal_keep_size is invalid */ + if (max_slot_wal_keep_size_mb < 0) + return 0; + + /* How many segments slots can keep? */ + keepSegs = ConvertToXSegs(max_slot_wal_keep_size_mb, wal_segment_size); + + /* override by wal_keep_segments if needed */ + if (wal_keep_segments > keepSegs) + keepSegs = wal_keep_segments; + + /* calculate the LSN where targetLSN is lost when currpos reaches */ + failSeg = targetSeg + keepSegs + 1; + XLogSegNoOffsetToRecPtr(failSeg, 0, wal_segment_size, horizon); + + /* If currptr already beyond the horizon, return zero. */ + if (currptr > horizon) + return 0; + + /* return the distance from currptr to the horizon */ + return horizon - currptr; +} /* * Retreat *logSegNo to the last segment that we need to retain because of diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5314e9348f..b9847a9f92 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -879,7 +879,7 @@ CREATE VIEW pg_replication_slots AS L.restart_lsn, L.confirmed_flush_lsn, L.wal_status, - L.min_safe_lsn + L.distance FROM pg_get_replication_slots() AS L LEFT JOIN pg_database D ON (L.datoid = D.oid); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 88033a79b2..532b3c5826 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -242,6 +242,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; + XLogRecPtr currlsn; int slotno; /* check to see if caller supports us returning a tuplestore */ @@ -274,6 +275,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + currlsn = GetXLogWriteRecPtr(); + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); for (slotno = 0; slotno < max_replication_slots; slotno++) { @@ -282,7 +285,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; - XLogSegNo last_removed_seg; + uint32 distance; int i; if (!slot->in_use) @@ -398,16 +401,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) break; } - if (max_slot_wal_keep_size_mb >= 0 && - (walstate == WALAVAIL_RESERVED || walstate == WALAVAIL_EXTENDED) && - ((last_removed_seg = XLogGetLastRemovedSegno()) != 0)) - { - XLogRecPtr min_safe_lsn; - - XLogSegNoOffsetToRecPtr(last_removed_seg + 1, 0, - wal_segment_size, min_safe_lsn); - values[i++] = Int64GetDatum(min_safe_lsn); - } + distance = + DistanceToWALHorizon(slot_contents.data.restart_lsn, currlsn); + if (distance > 0) + values[i++] = Int64GetDatum(distance); else nulls[i++] = true; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 77ac4e785f..1ec448c5d5 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -329,6 +329,7 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(int flags); extern bool CreateRestartPoint(int flags); extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN); +extern uint64 DistanceToWALHorizon(XLogRecPtr targetLSN, XLogRecPtr currptr); extern XLogRecPtr CalculateMaxmumSafeLSN(void); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr XLogRestorePoint(const char *rpName); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 61f2c2f5b4..199fd994bd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10063,9 +10063,9 @@ proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', prorettype => 'record', proargtypes => '', - proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,pg_lsn}', + proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8}', proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,min_safe_lsn}', + proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,distance}', prosrc => 'pg_get_replication_slots' }, { oid => '3786', descr => 'set up a logical replication slot', proname => 'pg_create_logical_replication_slot', provolatile => 'v', diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl index 7d22ae5720..1c76d2d9e9 100644 --- a/src/test/recovery/t/019_replslot_limit.pl +++ b/src/test/recovery/t/019_replslot_limit.pl @@ -28,7 +28,7 @@ $node_master->safe_psql('postgres', # The slot state and remain should be null before the first connection my $result = $node_master->safe_psql('postgres', - "SELECT restart_lsn IS NULL, wal_status is NULL, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT restart_lsn IS NULL, wal_status is NULL, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"'); @@ -52,9 +52,9 @@ $node_master->wait_for_catchup($node_standby, 'replay', $start_lsn); # Stop standby $node_standby->stop; -# Preparation done, the slot is the state "normal" now +# Preparation done, the slot is the state "reserved" now $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check the catching-up state'); @@ -64,7 +64,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when fitting max_wal_size $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that it is safe if WAL fits in max_wal_size'); @@ -74,7 +74,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); # The slot is always "safe" when max_slot_wal_keep_size is not set $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "reserved|t", 'check that slot is working'); @@ -94,9 +94,7 @@ max_slot_wal_keep_size = ${max_slot_wal_keep_size_mb}MB )); $node_master->reload; -# The slot is in safe state. The distance from the min_safe_lsn should -# be as almost (max_slot_wal_keep_size - 1) times large as the segment -# size +# The slot is in safe state. $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); @@ -110,7 +108,7 @@ $node_master->safe_psql('postgres', "CHECKPOINT;"); $result = $node_master->safe_psql('postgres', "SELECT wal_status FROM pg_replication_slots WHERE slot_name = 'rep1'"); is($result, "reserved", - 'check that min_safe_lsn gets close to the current LSN'); + 'check that distance gets close to the current LSN'); # The standby can reconnect to master $node_standby->start; @@ -154,7 +152,7 @@ advance_wal($node_master, 1); # Slot gets into 'unreserved' state $result = $node_master->safe_psql('postgres', - "SELECT wal_status, min_safe_lsn is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT wal_status, distance is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "unreserved|t", 'check that the slot state changes to "unreserved"'); @@ -186,7 +184,7 @@ ok( find_in_log( # This slot should be broken $result = $node_master->safe_psql('postgres', - "SELECT slot_name, active, restart_lsn IS NULL, wal_status, min_safe_lsn FROM pg_replication_slots WHERE slot_name = 'rep1'" + "SELECT slot_name, active, restart_lsn IS NULL, wal_status, distance FROM pg_replication_slots WHERE slot_name = 'rep1'" ); is($result, "rep1|f|t|lost|", 'check that the slot became inactive and the state "lost" persists'); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b813e32215..392eab12dd 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1464,8 +1464,8 @@ pg_replication_slots| SELECT l.slot_name, l.restart_lsn, l.confirmed_flush_lsn, l.wal_status, - l.min_safe_lsn - FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, min_safe_lsn) + l.distance + FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, distance) LEFT JOIN pg_database d ON ((l.datoid = d.oid))); pg_roles| SELECT pg_authid.rolname, pg_authid.rolsuper, -- 2.18.4 ----Next_Part(Wed_Jul__1_10_32_59_2020_161)---- ^ permalink raw reply [nested|flat] 22+ messages in thread
* Add LIMIT option to COPY FROM @ 2026-02-03 13:49 Shinya Kato <[email protected]> 2026-02-03 14:41 ` Re: Add LIMIT option to COPY FROM Tom Lane <[email protected]> 0 siblings, 1 reply; 22+ messages in thread From: Shinya Kato @ 2026-02-03 13:49 UTC (permalink / raw) To: PostgreSQL Hackers <[email protected]> Hi hackers, I'd like to propose adding a LIMIT option to COPY FROM, which limits the number of rows to load. With COPY TO, we can use the LIMIT clause in the query to restrict output rows, but COPY FROM has no equivalent way to limit the number of rows to load (except using the PROGRAM option with external tools like head). This patch resolves that asymmetry. Syntax example: - COPY t FROM STDIN (LIMIT 100); This feature is useful for: - Loading only the first N rows from a huge CSV file to verify data or table definitions before a full import - Sampling production data for staging or testing environments - Preventing unexpectedly large data loads in ETL pipelines Design: - The LIMIT count applies after WHERE filtering and ON_ERROR skipping, so it represents the actual number of rows inserted. - When the source is STDIN, the server reads and discards the remaining stream to keep the frontend/backend protocol synchronized. The patch is attached. Thoughts? -- Best regards, Shinya Kato NTT OSS Center Attachments: [application/octet-stream] v1-0001-Refactor-defGetCopyRejectLimitOption.patch (2.7K, ../../CAOzEurR2N3iEG6fgQ34EjDghQEv-3u7t69dMw6KqOTq12J3zNg@mail.gmail.com/2-v1-0001-Refactor-defGetCopyRejectLimitOption.patch) download | inline diff: From c67b508a4422e10adb796ef32e12faaa29b58dff Mon Sep 17 00:00:00 2001 From: Shinya Kato <[email protected]> Date: Mon, 29 Dec 2025 14:42:36 +0900 Subject: [PATCH v1 1/2] Refactor defGetCopyRejectLimitOption() Introduce defGetCopyPositiveInt64Option() to share validation for limit-like COPY options, so future LIMIT can reuse the same checks while keeping REJECT_LIMIT behavior. Author: Shinya Kato <[email protected]> Reviewed-by: Discussion: https://postgr.es/m/ --- src/backend/commands/copy.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 155a79a70c5..5f4bf570fdf 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -485,16 +485,17 @@ defGetCopyOnErrorChoice(DefElem *def, ParseState *pstate, bool is_from) } /* - * Extract REJECT_LIMIT value from a DefElem. + * Extract a positive int64 value from a DefElem. * - * REJECT_LIMIT can be specified in two ways: as an int64 for the COPY command - * option or as a single-quoted string for the foreign table option using - * file_fdw. Therefore this function needs to handle both formats. + * COPY options that limit processing can be specified in two ways: as an int64 + * for the COPY command option or as a single-quoted string for the foreign + * table option using file_fdw. Therefore this function needs to handle both + * formats. */ static int64 -defGetCopyRejectLimitOption(DefElem *def) +defGetCopyPositiveInt64Option(DefElem *def, const char *optname) { - int64 reject_limit; + int64 option_value; if (def->arg == NULL) ereport(ERROR, @@ -502,17 +503,17 @@ defGetCopyRejectLimitOption(DefElem *def) errmsg("%s requires a numeric value", def->defname))); else if (IsA(def->arg, String)) - reject_limit = pg_strtoint64(strVal(def->arg)); + option_value = pg_strtoint64(strVal(def->arg)); else - reject_limit = defGetInt64(def); + option_value = defGetInt64(def); - if (reject_limit <= 0) + if (option_value <= 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("REJECT_LIMIT (%" PRId64 ") must be greater than zero", - reject_limit))); + errmsg("%s (%" PRId64 ") must be greater than zero", + optname, option_value))); - return reject_limit; + return option_value; } /* @@ -741,7 +742,8 @@ ProcessCopyOptions(ParseState *pstate, if (reject_limit_specified) errorConflictingDefElem(defel, pstate); reject_limit_specified = true; - opts_out->reject_limit = defGetCopyRejectLimitOption(defel); + opts_out->reject_limit = + defGetCopyPositiveInt64Option(defel, "REJECT_LIMIT"); } else ereport(ERROR, -- 2.47.3 [application/octet-stream] v1-0002-Add-LIMIT-option-to-COPY-FROM.patch (12.3K, ../../CAOzEurR2N3iEG6fgQ34EjDghQEv-3u7t69dMw6KqOTq12J3zNg@mail.gmail.com/3-v1-0002-Add-LIMIT-option-to-COPY-FROM.patch) download | inline diff: From a1056490595507cc51cf9f6c58308b970f8d1a93 Mon Sep 17 00:00:00 2001 From: Shinya Kato <[email protected]> Date: Sun, 4 Jan 2026 15:55:23 +0900 Subject: [PATCH v1 2/2] Add LIMIT option to COPY FROM This commit adds a LIMIT option to COPY FROM so callers can cap the final number of rows loaded, after any WHERE clauses or ON_ERROR filtering have been applied. COPY TO could already restrict rows by using LIMIT in the query, but COPY FROM had no equivalent. Author: Shinya Kato <[email protected]> Reviewed-by: Discussion: https://postgr.es/m/ --- doc/src/sgml/ref/copy.sgml | 16 +++++++++ src/backend/commands/copy.c | 18 ++++++++++ src/backend/commands/copyfrom.c | 26 ++++++++++++++ src/backend/commands/copyfromparse.c | 28 +++++++++++++++ src/bin/psql/tab-complete.in.c | 2 +- src/include/commands/copy.h | 1 + src/include/commands/copyfrom_internal.h | 1 + src/test/regress/expected/copy2.out | 43 +++++++++++++++++++++++ src/test/regress/sql/copy2.sql | 44 ++++++++++++++++++++++++ 9 files changed, 178 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml index 53b0ea8f573..d521899b56e 100644 --- a/doc/src/sgml/ref/copy.sgml +++ b/doc/src/sgml/ref/copy.sgml @@ -38,6 +38,7 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable NULL '<replaceable class="parameter">null_string</replaceable>' DEFAULT '<replaceable class="parameter">default_string</replaceable>' HEADER [ <replaceable class="parameter">boolean</replaceable> | <replaceable class="parameter">integer</replaceable> | MATCH ] + LIMIT <replaceable class="parameter">rowcount</replaceable> QUOTE '<replaceable class="parameter">quote_character</replaceable>' ESCAPE '<replaceable class="parameter">escape_character</replaceable>' FORCE_QUOTE { ( <replaceable class="parameter">column_name</replaceable> [, ...] ) | * } @@ -335,6 +336,21 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable </listitem> </varlistentry> + <varlistentry id="sql-copy-params-limit"> + <term><literal>LIMIT</literal></term> + <listitem> + <para> + Specifies the maximum number of rows that <command>COPY FROM</command> + inserts. Rows filtered out by the <literal>WHERE</literal> clause or + skipped because of <literal>ON_ERROR</literal>=<literal>ignore</literal> + do not count toward the limit. When the limit is reached, any remaining + input is ignored; if the source is <literal>STDIN</literal>, the server + will read and discard the rest of the stream to finish the copy + protocol. This option is allowed only in <command>COPY FROM</command>. + </para> + </listitem> + </varlistentry> + <varlistentry id="sql-copy-params-quote"> <term><literal>QUOTE</literal></term> <listitem> diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 5f4bf570fdf..599b7798424 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -568,6 +568,7 @@ ProcessCopyOptions(ParseState *pstate, bool format_specified = false; bool freeze_specified = false; bool header_specified = false; + bool limit_specified = false; bool on_error_specified = false; bool log_verbosity_specified = false; bool reject_limit_specified = false; @@ -635,6 +636,13 @@ ProcessCopyOptions(ParseState *pstate, header_specified = true; opts_out->header_line = defGetCopyHeaderOption(defel, is_from); } + else if (strcmp(defel->defname, "limit") == 0) + { + if (limit_specified) + errorConflictingDefElem(defel, pstate); + limit_specified = true; + opts_out->limit = defGetCopyPositiveInt64Option(defel, "LIMIT"); + } else if (strcmp(defel->defname, "quote") == 0) { if (opts_out->quote) @@ -843,6 +851,16 @@ ProcessCopyOptions(ParseState *pstate, /*- translator: %s is the name of a COPY option, e.g. ON_ERROR */ errmsg("cannot specify %s in BINARY mode", "HEADER"))); + /* Check limit */ + if (opts_out->limit && !is_from) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + /*- translator: first %s is the name of a COPY option, e.g. ON_ERROR, + second %s is a COPY with direction, e.g. COPY TO */ + errmsg("COPY %s cannot be used with %s", "LIMIT", "COPY TO"))); + } + /* Check quote */ if (!opts_out->csv_mode && opts_out->quote != NULL) ereport(ERROR, diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index 25ee20b23db..021b42c72c4 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -1122,6 +1122,32 @@ CopyFrom(CopyFromState cstate) */ ResetPerTupleExprContext(estate); + /* + * Stop early when LIMIT would be exceeded. In multi-insert mode, + * include already buffered tuples so we don't overshoot. + */ + if (cstate->opts.limit > 0) + { + int64 pending_tuples = 0; + + if (insertMethod != CIM_SINGLE) + pending_tuples = multiInsertInfo.bufferedTuples; + + if (processed + pending_tuples >= cstate->opts.limit) + { + /* + * COPY FROM STDIN uses the FE/BE COPY IN protocol. To finish + * cleanly after reaching LIMIT, we must consume incoming + * CopyData up to CopyDone/CopyFail so that protocol state + * stays synchronized. + */ + if (cstate->copy_src == COPY_FRONTEND) + CopyFromDrainInput(cstate); + + break; + } + } + /* select slot to (initially) load row into */ if (insertMethod == CIM_SINGLE || proute) { diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c index 5868a7fa11f..31df4131114 100644 --- a/src/backend/commands/copyfromparse.c +++ b/src/backend/commands/copyfromparse.c @@ -2066,3 +2066,31 @@ CopyReadBinaryAttribute(CopyFromState cstate, FmgrInfo *flinfo, *isnull = false; return result; } + +/* + * Discard remaining COPY FROM STDIN data after reaching a row limit. + */ +void +CopyFromDrainInput(CopyFromState cstate) +{ + Assert(cstate->copy_src == COPY_FRONTEND); + + /* Read to EOF on the raw input */ + while (!cstate->raw_reached_eof) + { + int inbytes; + + inbytes = CopyGetData(cstate, cstate->raw_buf, 1, RAW_BUF_SIZE); + if (inbytes <= 0) + break; + } + + /* Reset buffer pointers */ + cstate->raw_buf_index = 0; + cstate->raw_buf_len = 0; + if (cstate->input_buf != NULL) + { + cstate->input_buf_index = 0; + cstate->input_buf_len = 0; + } +} diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 8b91bc00062..f34b7e7ad7d 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -1228,7 +1228,7 @@ Alter_routine_options, "CALLED ON NULL INPUT", "RETURNS NULL ON NULL INPUT", \ /* COPY FROM options */ #define Copy_from_options \ Copy_common_options, "DEFAULT", "FORCE_NOT_NULL", "FORCE_NULL", "FREEZE", \ -"LOG_VERBOSITY", "ON_ERROR", "REJECT_LIMIT" +"LIMIT", "LOG_VERBOSITY", "ON_ERROR", "REJECT_LIMIT" /* COPY TO options */ #define Copy_to_options \ diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h index 43c2580539f..4c827b0665b 100644 --- a/src/include/commands/copy.h +++ b/src/include/commands/copy.h @@ -63,6 +63,7 @@ typedef struct CopyFormatOptions bool csv_mode; /* Comma Separated Value format? */ int header_line; /* number of lines to skip or COPY_HEADER_XXX * value (see the above) */ + int64 limit; /* maximum rows to load */ char *null_print; /* NULL marker string (server encoding!) */ int null_print_len; /* length of same */ char *null_print_client; /* same converted to file encoding */ diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h index 822ef33cf69..9669b6bbcdf 100644 --- a/src/include/commands/copyfrom_internal.h +++ b/src/include/commands/copyfrom_internal.h @@ -193,5 +193,6 @@ extern bool CopyFromCSVOneRow(CopyFromState cstate, ExprContext *econtext, Datum *values, bool *nulls); extern bool CopyFromBinaryOneRow(CopyFromState cstate, ExprContext *econtext, Datum *values, bool *nulls); +extern void CopyFromDrainInput(CopyFromState cstate); #endif /* COPYFROM_INTERNAL_H */ diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out index 3145b314e48..53ee086dff5 100644 --- a/src/test/regress/expected/copy2.out +++ b/src/test/regress/expected/copy2.out @@ -85,6 +85,10 @@ COPY x from stdin (log_verbosity default, log_verbosity verbose); ERROR: conflicting or redundant options LINE 1: COPY x from stdin (log_verbosity default, log_verbosity verb... ^ +COPY x from stdin (limit 10, limit 10); +ERROR: conflicting or redundant options +LINE 1: COPY x from stdin (limit 10, limit 10); + ^ -- incorrect options COPY x from stdin (format BINARY, delimiter ','); ERROR: cannot specify DELIMITER in BINARY mode @@ -144,6 +148,12 @@ COPY x from stdin with (header '2.5'); ERROR: header requires a Boolean value, an integer value greater than or equal to zero, or the string "match" COPY x to stdout with (header '2'); ERROR: cannot use multi-line header in COPY TO +COPY x from stdin (limit 0); +ERROR: LIMIT (0) must be greater than zero +COPY x from stdin (limit 1.5); +ERROR: invalid input syntax for type bigint: "1.5" +COPY x to stdout (limit 1); +ERROR: COPY LIMIT cannot be used with COPY TO -- too many columns in column list: should fail COPY x (a, b, c, d, e, d, c) from stdin; ERROR: column "d" specified more than once @@ -192,6 +202,39 @@ COPY x from stdin WHERE a = row_number() over(b); ERROR: window functions are not allowed in COPY FROM WHERE conditions LINE 1: COPY x from stdin WHERE a = row_number() over(b); ^ +-- tests for LIMIT option +CREATE TEMP TABLE copy_limit_basic (a int); +COPY copy_limit_basic FROM stdin (limit 2); +SELECT * FROM copy_limit_basic ORDER BY a; + a +--- + 1 + 2 +(2 rows) + +-- test LIMIT with WHERE option +CREATE TEMP TABLE copy_limit (a int, b text); +COPY copy_limit FROM stdin (limit 2); +COPY copy_limit FROM stdin (limit 1) WHERE a > 5; +SELECT * FROM copy_limit ORDER BY a; + a | b +---+----- + 1 | one + 2 | two + 6 | six +(3 rows) + +-- test LIMIT with ON_ERROR option +CREATE TEMP TABLE copy_limit_err (a int); +COPY copy_limit_err FROM stdin (on_error ignore, limit 2); +NOTICE: 1 row was skipped due to data type incompatibility +SELECT * FROM copy_limit_err ORDER BY a; + a +--- + 1 + 2 +(2 rows) + -- check results of copy in SELECT * FROM x; a | b | c | d | e diff --git a/src/test/regress/sql/copy2.sql b/src/test/regress/sql/copy2.sql index 66435167500..f56a111052c 100644 --- a/src/test/regress/sql/copy2.sql +++ b/src/test/regress/sql/copy2.sql @@ -68,6 +68,7 @@ COPY x from stdin (convert_selectively (a), convert_selectively (b)); COPY x from stdin (encoding 'sql_ascii', encoding 'sql_ascii'); COPY x from stdin (on_error ignore, on_error ignore); COPY x from stdin (log_verbosity default, log_verbosity verbose); +COPY x from stdin (limit 10, limit 10); -- incorrect options COPY x from stdin (format BINARY, delimiter ','); @@ -96,6 +97,9 @@ COPY x to stdout with (header 2); COPY x to stdout with (header '-1'); COPY x from stdin with (header '2.5'); COPY x to stdout with (header '2'); +COPY x from stdin (limit 0); +COPY x from stdin (limit 1.5); +COPY x to stdout (limit 1); -- too many columns in column list: should fail COPY x (a, b, c, d, e, d, c) from stdin; @@ -164,6 +168,46 @@ COPY x from stdin WHERE a IN (generate_series(1,5)); COPY x from stdin WHERE a = row_number() over(b); +-- tests for LIMIT option +CREATE TEMP TABLE copy_limit_basic (a int); + +COPY copy_limit_basic FROM stdin (limit 2); +1 +2 +3 +\. + +SELECT * FROM copy_limit_basic ORDER BY a; + +-- test LIMIT with WHERE option +CREATE TEMP TABLE copy_limit (a int, b text); + +COPY copy_limit FROM stdin (limit 2); +1 one +2 two +3 three +\. + +COPY copy_limit FROM stdin (limit 1) WHERE a > 5; +5 five +6 six +7 seven +\. + +SELECT * FROM copy_limit ORDER BY a; + +-- test LIMIT with ON_ERROR option +CREATE TEMP TABLE copy_limit_err (a int); + +COPY copy_limit_err FROM stdin (on_error ignore, limit 2); +1 +bad +2 +3 +\. + +SELECT * FROM copy_limit_err ORDER BY a; + -- check results of copy in SELECT * FROM x; -- 2.47.3 ^ permalink raw reply [nested|flat] 22+ messages in thread
* Re: Add LIMIT option to COPY FROM 2026-02-03 13:49 Add LIMIT option to COPY FROM Shinya Kato <[email protected]> @ 2026-02-03 14:41 ` Tom Lane <[email protected]> 0 siblings, 0 replies; 22+ messages in thread From: Tom Lane @ 2026-02-03 14:41 UTC (permalink / raw) To: Shinya Kato <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]> Shinya Kato <[email protected]> writes: > I'd like to propose adding a LIMIT option to COPY FROM, which limits > the number of rows to load. Do we really need this? Each random feature we load onto COPY slows it down for everybody. regards, tom lane ^ permalink raw reply [nested|flat] 22+ messages in thread
end of thread, other threads:[~2026-02-03 14:41 UTC | newest] Thread overview: 22+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2020-06-30 12:09 [PATCH v1] Replace min_safe_lsn with "distance" in pg_replication_slots Kyotaro Horiguchi <[email protected]> 2026-02-03 13:49 Add LIMIT option to COPY FROM Shinya Kato <[email protected]> 2026-02-03 14:41 ` Re: Add LIMIT option to COPY FROM Tom Lane <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox