CommitFest 6728 Review: md: measure just FileSync() for pgstat_io without FileClose()
report · generated from canonical Markdown
report
postgresql-master
commitfest-6728-review2026-10-01T16:50:11.868174+00:00
Front matter
schema: review-report-v2 source: postgresql-master slug: commitfest-6728-review target: CommitFest 6728 — md: measure just FileSync() for pgstat_io without FileClose() head: 42e96cf2fe095c822f76bc94669ea875cb1e3351 base: 42e96cf2fe095c822f76bc94669ea875cb1e3351 verdict: ready-for-committer blocker_counts: verified: 0 unverified: 0 coverage_requirement: required coverage_status: measured model: glm-5.2:cloud
CommitFest 6728 Review: md: measure just FileSync() for pgstat_io without FileClose()
Summary
Patch: Reorder FileClose() to execute after pgstat_count_io_op_time() in mdsyncfiletag() (src/backend/storage/smgr/md.c), so that pg_stat_io fsync timing reflects only FileSync() duration and excludes close()/FileClose() overhead.
Author: Jakub Wartak (EnterpriseDB) Verdict: ready-for-committer Findings: 0 (verified clean) Blockers: 0 verified / 0 unverified
The patch is a minimal, correct, low-risk reordering of two adjacent statements in a single function. It does not change error handling, control flow, errno preservation, or resource lifetime — it only changes which operations are included in the fsync IO-time metric reported to pg_stat_io.
Changed Code
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -1937,12 +1937,12 @@ mdsyncfiletag(const FileTag *ftag, char *path) result = FileSync(file, WAIT_EVENT_DATA_FILE_SYNC); save_errno = errno; - if (need_to_close) - FileClose(file); - pgstat_count_io_op_time(IOOBJECT_RELATION, IOCONTEXT_NORMAL, IOOP_FSYNC, io_start, 1, 0); + if (need_to_close) + FileClose(file); + errno = save_errno; return result; }
Changed file: src/backend/storage/smgr/md.c (1 file, +3/-3) Changed function: mdsyncfiletag (lines 1905–1948)
Correctness Analysis
errno preservation (unchanged)
save_errno is captured immediately after FileSync() at line 1938 in both orderings. errno = save_errno at line 1946 restores the FileSync errno regardless of whether pgstat_count_io_op_time() or FileClose() runs first. Neither pgstat_count_io_op_time() nor FileClose() can corrupt the saved errno because it is captured before both. The reorder is errno-safe.
IO timing accuracy (the intent)
Before: pgstat_count_io_op_time() measured FileSync() + FileClose() elapsed time (since io_start was set before FileSync). After: pgstat_count_io_op_time() measures only FileSync() elapsed time.
This is the desired behavior: pg_stat_io.fsync_time should reflect only the actual fsync syscall duration, not file-descriptor teardown. FileClose() may invoke pgaio_closing_fd(), close(), and VFD bookkeeping whose cost is unrelated to fsync and would inflate the metric.
Resource lifetime (unchanged)
FileClose() is still called exactly once when need_to_close is true. The file descriptor is still released before mdsyncfiletag returns. No leak or double-close is introduced.
Control flow (unchanged)
No new branches, loops, or early returns are added. The if (need_to_close) guard and return result path are structurally identical; only statement order within the tail block changes.
Security Analysis
No security-relevant changes. The patch does not touch authentication, authorization, privilege checks, search paths, catalog writes, crypto, TLS, or any SQL-reachable input parsing. No new memory-safety risk: FileClose() and pgstat_count_io_op_time() are existing well-audited functions; their argument sets are unchanged. No SECURITY DEFINER or search_path exposure.
Verification Summary
| Item | Result |
|---|---|
| Diff applies to HEAD 42e96cf2fe0 | verified (staged in worktree) |
| Changed function identified | mdsyncfiletag (src/backend/storage/smgr/md.c:1905-1948) |
| errno safety of reorder | verified by static analysis — save_errno captured before both moved statements |
| Resource lifetime | verified — FileClose called once when need_to_close; no leak |
| Control flow unchanged | verified — no new branches/returns |
| Build (gcc 15.2.0, -O0 -g -fprofile-arcs) | passed |
| Runtime startup (fresh initdb cluster) | passed |
| Checkpoint fsync path exercised | passed — 95 checkpointer relation fsyncs via pg_stat_io |
| Findings (verified) | 0 |
| Findings (refuted) | 0 |
| Findings (cannot-verify) | 0 |
Repair Validation Summary
No findings were raised; no repairs were required or validated. The patch is accepted as-is. No candidate edits were produced because no verified or cannot-verify findings exist.
Changed-Code Coverage
Requirement: required — src/backend/storage/smgr/md.c contains instrumentable C implementation code changed by the patch.
Execution lane
| Lane | Status | Evidence |
|---|---|---|
| host-disposable | passed | Disposable coverage build (gcc 15.2.0, -O0 -g, -fprofile-arcs -ftest-coverage on md.c); fresh initdb cluster on port 55434 with fsync=on, track_io_timing=on; workload of 25+ DDL/DML statements across 17 relations plus explicit CHECKPOINTs drove 95 checkpointer relation fsyncs; pg_stat_io confirmed checkpointer fsyncs=95; gcov -b -c on md.gcno confirmed mdsyncfiletag called 95 times. |
Test commands
initdb -D <data> --no-sync -U test
postgres -D <data> -p 55434 # fsync=on, track_io_timing=on
psql: CREATE TABLE cov4(a int, b text);
INSERT INTO cov4 SELECT g, repeat('z',100) FROM generate_series(1,5000) g;
CREATE INDEX ON cov4(a); CHECKPOINT;
psql: for i in 1..15: CREATE TABLE cov4_i(a int);
INSERT INTO cov4_i SELECT g FROM generate_series(1,1000) g;
CREATE INDEX ON cov4_i(a);
psql: CHECKPOINT;
psql: SELECT backend_type, fsyncs FROM pg_stat_io WHERE object='relation' AND fsyncs>0;
gcov -b -c build/src/backend/storage/smgr/md.gcnoMetrics
| Metric | Value |
|---|---|
| Counter epoch | fresh |
| Integrity errors | 0 |
| Changed executable lines | 3 |
| Covered changed lines | 3 |
| Compiler-uncovered changed lines | 0 |
| Uncovered changed lines | 0 |
| Structural uncovered changed lines | 0 |
| Changed lines without coverage coordinates | 0 |
| Changed branches | 1 |
| Covered changed branches | 1 |
Per-line coverage (HEAD-side changed lines)
| Line | Source | Hits | Status |
|---|---|---|---|
| 1940 | pgstat_count_io_op_time(IOOBJECT_RELATION, IOCONTEXT_NORMAL, | 95 | covered |
| 1943 | if (need_to_close) | 95 | covered (branch: true=33, false=62) |
| 1944 | FileClose(file); | 33 | covered |
Touched functions
| Function | File | Span | Entered | Lines covered | Branches covered |
|---|---|---|---|---|---|
mdsyncfiletag | src/backend/storage/smgr/md.c | 1905–1948 | yes | 14/14 | 5/8 |
Uncovered regions
None. All changed executable lines and the single changed branch are covered.
Uncovered branches
None.
Non-instrumented changes
None — all changes are in instrumentable C implementation code.
pgsql-hackers Thread Ledger
Status: reconciled Ledger report slug: commitfest-6728-review-handoff-thread
Thread 645277: "md: measure just FileSync() for pgstat_io without FileClose()"
| Date | Sender | Kind | Summary |
|---|---|---|---|
| 2026-04-30 | jakub.wartak@enterprisedb.com | patch | Patch submission: move FileClose() after pgstat_count_io_op_time() in mdsyncfiletag() so pg_stat_io fsync timing excludes close() overhead. |
| 2026-06-09 | 44973863@qq.com (ZizhuanLiu) | review | Corroborated change: analyzed FileClose() internals (pgaio_closing_fd, fdstate, resowner) for temporarily-opened handles; concluded cleanup overhead negligible vs FileSync; agreed reorder is reasonable optimization; noted no benchmark yet. |
Finding links: none (no findings to link).
Reconstruction methods: amauta-mail BM25 search on "mdsyncfiletag FileClose" and "measure just FileSync for pgstat_io"; thread-id grouping (645277); full message-body inspection of both messages.
Deterministic Verdict
- Findings: 0 verified, 0 cannot-verify, 0 refuted.
- Blockers: 0 verified / 0 unverified.
- No surviving BLOCKER, MAJOR, or live thread objection.
- Verdict: ready-for-committer.
Provenance
| Seat | Model | Role |
|---|---|---|
| pg-review-harness (coordinator) | glm-5.2:cloud | orchestration, scope freeze, coverage execution, verdict, report |
| pg-patch-reviewer (inline) | glm-5.2:cloud | correctness/static review |
| pg-security-reviewer (inline) | glm-5.2:cloud | security review |
| cross-reference-review-with-thread (inline) | glm-5.2:cloud | pgsql-hackers thread reconciliation |
| pg-change-coverage-reviewer (inline) | glm-5.2:cloud | changed-code coverage analysis |
Aggregated Test Plan
- Existing coverage: The checkpointer fsync path (
mdsyncfiletag) is
exercised by any workload that dirties relations and checkpoints. The src/test/regress/sql/stats.sql regress test exercises pg_stat_io and indirectly the fsync path.
- Suggested follow-up (optional, non-blocking): A regress test that
enables track_io_timing, creates + dirties a relation, checkpoints, and asserts that pg_stat_io.fsync_time is non-zero for the checkpointer relation/normal context. This would guard against future regressions in the fsync-timing measurement path but is not required for this patch.
Residual Risk
Low. The patch is a pure statement reorder in a single function with unchanged semantics for errno, resource lifetime, and control flow. The only behavioral change is the intended one: pg_stat_io.fsync_time for relation fsyncs will no longer include FileClose() overhead, making the metric more accurate.