Repository navigation
Log tail: a rotation in the same second no longer leaves the tail reading the outgoing file (#4719) - #4738
Merged
Conversation
…he outgoing file (#4719) pg_ls_logdir() reports modification as a stat mtime in whole seconds. When a size or age rotation puts the outgoing file's last write and the incoming file's first write in the same second, the two files tie, and the six `newest` CTEs (stderr, csvlog and jsonlog routes, each with a text-read and a binary-read constant) ordered by modification DESC alone, so LIMIT 1 could return the outgoing file. The tail then read nothing new until the new file's mtime moved to a later second. Each `newest` now orders by modification DESC, name DESC. The name is only a tiebreak behind modification. It is safe where log_filename sorts chronologically (the default postgresql-%Y-%m-%d_%H%M%S, RDS postgresql.log.YYYY-MM-DD-HH); where a pattern does not sort by time a tie was already a coin flip, so nothing gets worse. StoreLogSweep already breaks its listing's ties on name. Tests: a text pin over all six constants, and a live pin that runs each constant's own listing and newest CTEs over a two-row listing standing in for pg_ls_logdir() (equal modification, both row orders, plus a strictly newer modification beating a later name). The normalized-SQL baselines and the two byte-for-byte pins of the tail text carry the new order, and the pipeline live test that picks the newest file by hand breaks ties on name too. Refs #4719
…rs' name tiebreak (#4723)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4723. Refs #4719.
Why
pg_ls_logdir()reportsmodificationas a stat mtime in whole seconds. When a size or age rotation puts the outgoing file's last write and the incoming file's first write in the same second, the two files tie. All sixnewestCTEs inPgServerLogTail.csordered bymodification DESCalone, soLIMIT 1could return the OUTGOING file. The tail then treated the old file as current, read nothing new, and stayed there until the new file's mtime moved to a later second. Measured in CI: the resume marker named the older file (postgresql-..._060231.log) while the awaited line was in the newer one, and 54 attempts over 30 s found 0 rows.Two measurements on a local PostgreSQL 18.6 server on Windows (
logging_collectoron,log_destination = 'stderr', defaultlog_filename):modificationadvance while it is written? Yes. 14 log writes 300 ms apart over 4.4 s, reading the current file'smodificationafter each: 06:35:20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, 23, 24, 24 (whole seconds, following the wall clock; the size grew 1331 to 11736 bytes). So the stall on the outgoing file lasts until the next line is logged in a later second, not until the next rotation. That decides the changelog wording below.pg_rotate_logfile(), write a line, all inside one second): 20 of 20 tied onmodification;ORDER BY modification DESC LIMIT 1returned the OUTGOING file 20 of 20;ORDER BY modification DESC, name DESCreturned the new file 20 of 20. (A one-off probe; not committed, because a committed version would depend on landing inside one second.)What changes
PerformanceMonitor.Collectors/PgServerLogTail.cs: all sixnewestCTEs (TailCteSql,TailCteBinarySql,TailCsvCteSql,TailCsvCteBinarySql,TailJsonCteSql,TailJsonCteBinarySql) nowORDER BY modification DESC, name DESC. The name is only a tiebreak behind modification, never the primary order. The existing Target log tail can pick the csvlog/jsonlog sibling instead of the stderr log #3997 paragraph says why, that it is safe wherelog_filenamesorts chronologically (defaultpostgresql-%Y-%m-%d_%H%M%S, RDSpostgresql.log.YYYY-MM-DD-HH), and that where a pattern doesn't sort by time a tie was already a coin flip, so nothing gets worse.StoreLogSweepalready breaks its listing's ties on name (ORDER BY l.modification, l.name).TsqlConventionGuardTests, the byte-for-byte pins inPgLogEventsPipelineTestsandPgServerLogTailBinaryRouteTests, and the newest-file query inPgLogEventsLivePostgresTests.PgDeadlocksResumeLiveTestsalready had the tiebreak.PgServerLogTailNewestTiebreakTests(text pin) andPgServerLogTailNewestTiebreakLivePostgresTests(live pin), described below.PerformanceMonitor.Collectorstail, and grep finds no othermodification DESCpick inLiteorLite.Tests.docs/postgres-first-target-runbook.md: the manual permissions proof says it mirrors the collectors' own read, so it picks the newest file with the samemodification DESC, name DESC.RdsLogSource.csand the rotation live tests are untouched.Test plan
postgresql-2026-09-29_060231.*) when it was listed first.newestCTE, orderedmodification DESC, name DESC.listingandnewestCTEs run over a two-rowVALUESlist in place ofpg_ls_logdir()(two names, the later one newer by the default pattern). Equal modification: the later name wins with the rows in either order. A strictly newer modification beats a later name in either order, so the name can't become the primary order. The twocurrent_settinggates inlistingare server-start settings, so the swapped SQL is given the values of a matching target; the sharedDARLING_TEST_PGserver is enough and no file is read.PgServerLogTail*,PgDeadlocks*,PgPlanCapture*) and the census gates (DocCommentHygiene,StartupCommandTimeoutTests,StorageCommandTimeoutTests,AlertReadFailureSurfaceTests,LivePostgresCollectionHygieneTests,LiveCleanupConversionRatchetTests,McpPayloadContractCensusTests): 453 tests, 0 failed, 23 skipped (they need clusters this run didn't start). The first pass failedLivePostgresCollectionHygieneTestsbecause the live pin sat in a class with nolive-postgrescollection; splitting it into its own...LivePostgresTestsclass fixed that.PgServerLogTailRotationLiveTests,PgDeadlocksResumeLiveTests) on a second local cluster with the logging collector on: 9 of 9 pass, 0 skipped.LogTailOverlapThresholdPinTests,PgDeadlockLogParserTests,PgServerLogPathPinTests,PgPlanCaptureCollectorDefinitionTests,PgLoggingCollectorGateTests): 105 tests, 0 failed.Darling.TestsandLite.Testsbuild with 0 warnings and 0 errors, after merging the latest dev.Darling.Testssuite once against a local PostgreSQL 18.6 server (run on the tree after the first dev merge): 17096 tests, 2 failed, 89 skipped, 1 not run. Neither failure is from this change, and both pass when re-run alone on a freshly created database (2 of 2):StoreSelfMetricsTickRecoveryLiveTests.AFailedStoreLogCapture_...expectspg_ls_logdir()to raise on a server with no log directory; my server still had alogfolder left by the earlier measurement run. That was rig state, not code.DarlingCliCommandsHostCheckTests.CheckSettingsAsync_ByoStore_NothingEverStale_...returned exit code 2 in the full run and did not reproduce alone. I did not find the cause; other suites were loading the same machine.PgLogBurstAndRotationLiveTests, both new classes,TsqlConventionGuardTestsandLivePostgresCollectionHygieneTests: 54 tests, 0 failed, 3 skipped (they need the csvlog and jsonlog rotation clusters). The full suite was not repeated after that merge.CHANGELOG
SECTION: Fixed
ENTRY:
REF:
[PostgreSQL log tail: an old and a new log file sharing one mtime second are not ordered by recency (newest pick has no tiebreak) #4723]: PostgreSQL log tail: an old and a new log file sharing one mtime second are not ordered by recency (newest pick has no tiebreak) #4723