Skip to content

PostgreSQL 13 csvlog records (24 columns) are parsed instead of rejected, and rejected records are counted (#4709) - #4714

Merged
erikdarlingdata merged 1 commit into
devfrom
fix/4709-pg13-csvlog
Sep 29, 2026
Merged

erikdarlingdata merged 1 commit into
devfrom
fix/4709-pg13-csvlog

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Closes #4709.

Why

PostgreSQL 13 writes 24 csvlog columns. leader_pid and query_id arrived in 14. PgServerLogCsvParser required exactly 26 fields, so every PostgreSQL 13 record was rejected. Csvlog is now the preferred reader whenever log_destination lists it, so on a 13 server with both stderr and csvlog enabled the log events, deadlocks and plan capture came back empty. The run also reported one discard however many records were rejected, so a server whose every record failed the shape check looked almost quiet. PostgreSQL 13 is a supported target (docs/postgres-first-target-runbook.md).

The first 24 columns are identical in 13 and 14 through 18 (checked against the PostgreSQL 13 documentation's postgres_log table: 24 columns, backend_type last). The highest index the entry's own members read is 21 (location), so a 24-field record carries everything the parser reads. Nothing shifts.

What changes

  • PerformanceMonitor.Collectors/PgServerLogCsvParser.cs
    • A record with 24 or 26 fields is admitted, any other count is rejected. Two constants replace ExpectedColumnCount.
    • The query_id column (index 25) is carried as PgLogEntry.QueryIdText, and is null on a 24-field record. It is absent, never the last field read as if it were the id.
    • When no parity can be confirmed (the last record of an end-anchored body does not parse, or neither scoring hypothesis parses any record), nothing is emitted, exactly as before, and consumedLength stays 0 so the RDS carry is untouched. What changes is the count: the segments that could not be used are now counted instead of the whole body counting as one discard. At least one is still reported for a non-empty body.
    • Doc comments that said 26 now say 24 or 26 and cite csvlog from PostgreSQL 13 is rejected, so the log collectors report the server as quiet #4709.
  • PerformanceMonitor.Collectors/PgLogEntry.cs: new trailing member QueryIdText (default null), following the Location precedent so every existing construction still compiles.
  • PerformanceMonitor.Collectors/PgPlanCaptureCollector.cs: the query id used to be the text after the last comma of the raw record. On a 24-field record that is the quoted backend_type, so every real capture would have been counted as forged. It now reads QueryIdText. A null id is stored as query id 0, which this collector already uses for "no attribution" (a plan captured on a target that never configured %Q), and a present but unparseable id is still counted as forged. The method that split on the last comma is gone.
  • No edits to PgLogEventsCollector.cs, PgDeadlocksCollector.cs or PgLogFormatCapability.cs: the measurement plumbing already reports whatever count the parser returns, and csvlog stays preferred on PostgreSQL 13 (no fallback to stderr).

Tests

Tests added in PgServerLogCsvParserTests and PgPlanCaptureCsvUnitTests and run against the old code first. Eight test cases failed there:

  • Pg13Record_With24Fields_ParsesIntoOneEntry
  • Pg13Record_MapsEveryFieldTheParserReads_ToTheValueTheSame26FieldRecordGives
  • AMixedBody_OfPg13AndPg14PlusRecords_ParsesEveryRecord
  • ABatchOfNMalformedRecords_ReportsNDiscards with n = 3 and n = 7 (n = 1 passed before, since one record is one discard)
  • NoneMode_MalformedRecordsBeforeATrailingPartial_AreEachReported_AndNothingIsConsumed
  • AResumeOffsetAtALineStartInsideAQuotedMultiLineField_YieldsEachLaterRecordExactlyOnce for the 24-field shape (the 26-field case passes before and after)
  • PgPlanCaptureCsvUnitTests.APg13Record_With24Fields_HasNoQueryIdColumn_SoItYieldsAnOrphanRowNotAForgery

The resume-offset test is the pin for the self-hosted tail marker (#4704): a resume offset at a line start inside a quoted multi-line field still yields every later record exactly once, and never the straddling record's tail, for both column shapes.

  • Targeted classes (csv parser, plan capture, deadlocks, RDS csvlog/plan/deadlock ingestors, tail resume, log events pipeline, doc comment hygiene, measurement seam): 397 run, 0 failed, 28 skipped.
  • Lite.Tests PostgreSQL log and plan classes: 114 run, 0 failed.
  • Full Darling.Tests after a merge check with origin/dev (no drift): 17058 run, 0 failed, 1111 skipped (live classes that need a store), 1 not run. Build: 0 warnings, 0 errors.
  • Live csvlog classes against a real PostgreSQL 13 server were not run: no PostgreSQL 13 server was available and there is no store in this run. The 24-field fixtures are the verified 26-field record cut after backend_type, which is how the two formats differ.

Worth a second look

  • A PostgreSQL 13 auto_explain capture is now stored as an orphan plan (query id 0), where before nothing was stored. The alternative is to drop it silently. Orphans are what the plan_attribution readiness facet already describes, so the orphan route was chosen.
  • Where the last record of an end-anchored body does not parse, the whole body is still dropped as before (the parity is unconfirmed, and emitting from it would let a planted look-alike out). Only the count changed, and it counts the segments that failed the shape check, not any valid ones the gate dropped with them.

CHANGELOG

SECTION: Fixed
ENTRY:

…he real discard count (#4709)

PostgreSQL 13 writes 24 csvlog columns; leader_pid and query_id arrived in 14. The parser required exactly 26, so every PostgreSQL 13 record was rejected and the whole read counted as one discard. It now accepts 24 or 26 fields, carries the query_id column as PgLogEntry.QueryIdText (null when absent), reads the plan capture query id from that member instead of the last comma of the record, and counts the records it dropped when no parity could be confirmed.
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 29, 2026 05:48
@erikdarlingdata
erikdarlingdata merged commit 216a431 into dev Sep 29, 2026
26 of 30 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/4709-pg13-csvlog branch September 29, 2026 05:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant