Technical Analysis: pg_current_vxact_id() — Exposing Virtual Transaction IDs via SQL
Core Problem
PostgreSQL assigns every backend a Virtual Transaction ID (VXID) — a lightweight identifier composed of procNumber/localTransactionId — that uniquely identifies a transaction within the lifetime of a session. Unlike regular XIDs (which are only assigned to writing transactions and consume shared resources), VXIDs are universal: every transaction gets one, including read-only transactions that never need a permanent XID.
Despite being fundamental to PostgreSQL's internal transaction tracking, VXIDs have no direct SQL accessor. They appear in:
pg_locks.virtualtransactionandpg_locks.virtualxid- Server log output via the
%vplaceholder inlog_line_prefix - Internal lock manager and transaction management code
The only current way to retrieve your own VXID from SQL is an indirect query through the lock subsystem:
SELECT virtualtransaction FROM pg_locks WHERE pid = pg_backend_pid() LIMIT 1;
This is semantically confusing (querying locks to get a transaction identity), expensive (O(n) scan over all locks with tuple construction), and inconsistent with the API patterns established by pg_backend_pid() and pg_current_xact_id().
Proposed Solution
The patch adds a new built-in function pg_current_vxact_id() that performs a direct O(1) read from MyProc (the backend's PGPROC structure in shared memory) and returns the VXID as text in the format "procNumber/lxid" (e.g., "3/42").
Implementation Details
- Location:
src/backend/utils/adt/xid8funcs.c— placed alongsidepg_current_xact_id()and related functions - Mechanism: Reads
MyProc->vxid.procNumberandMyProc->vxid.lxiddirectly — no locks, no iteration, no tuple construction - Return type:
text(matching the format used inelog.c's%vplaceholder andpg_locks.virtualtransaction) - Buffer: 32-byte stack buffer, sufficient for maximum output of 23 bytes (consistent with
VXIDGetDatum()inlockfuncs.c) - Size: ~20 lines of implementation code
Evolution Across Versions
v1 (2025-12-08): Initial implementation with OID 5101.
v2 (2026-01-06):
- Rebased on master
- Changed OID from 5101 to 9538 (following the
unused_oidsbest practice of using 8000-9999 range during development)
v3 (2026-02-05):
- Added
#define VXID_FMT "%d/%u"macro tolock.hto eliminate format string duplication across three files (lockfuncs.c,elog.c,xid8funcs.c) - Updated all three files to use the macro
- Changed documentation terminology from "localTransactionId" to "localXID" for consistency with existing user-facing docs (
xact.sgml,config.sgml)
v4 (pending): Minor pgindent comment reflow needed in xid8funcs.c.
Key Design Decisions and Tradeoffs
1. The "Convenience vs. Necessity" Debate
The central tension in this thread is whether the function provides enough value to justify its existence given that pg_locks already exposes the same information. Michael Paquier raised this directly:
"This is replacing one SQL in a given session by another... I don't see the need for this function, except simplicity in retrieving a session's state with less characters typed at the end?"
Pavlo's counter-arguments centered on three axes:
- Performance: O(1) direct memory read vs. O(n) lock table scan with tuple construction
- Semantic clarity: Querying the lock subsystem to get a transaction identity is conceptually wrong
- API consistency: VXIDs are the only fundamental transaction identifier without a direct accessor
Robert Haas acknowledged the argument's validity but noted it's "all theoretical" — no one demonstrated a real-world scenario where the pg_locks approach was actually problematic. He expressed a general concern about accumulating functions "that can theoretically be justified but in reality get very little use."
2. Format String Consolidation (VXID_FMT)
Henson Choi's review identified that the VXID format string "%d/%u" was duplicated in three places. The v3 patch introduces:
#define VXID_FMT "%d/%u"
in lock.h, which all three consuming files already include transitively. This is a minor but positive improvement to maintainability — if the VXID format ever changes, there's now a single point of truth.
3. Documentation Terminology
An interesting inconsistency was surfaced: the C code uses localTransactionId (the actual struct field name, appearing 30+ times) while user-facing SGML documentation uses localXID (3 occurrences in xact.sgml and config.sgml). The patch adopts localXID for user-facing docs, which is the correct choice for audience-appropriate terminology.
4. OID Selection
The initial submission used OID 5101, which Michael Paquier flagged as not following the unused_oids script's guidance to use the 8000-9999 range during development (since OIDs get renumbered during beta anyway). This was corrected to 9538 in v2.
Architectural Significance
While this is a small patch, it touches on broader design philosophy questions:
-
API completeness vs. minimalism: PostgreSQL has historically favored providing multiple access paths to the same information (e.g.,
NOTIFY/pg_notify(),SHOW/current_setting()). This patch extends that pattern to VXIDs. -
The VXID as a first-class concept: VXIDs are arguably more fundamental than XIDs — every transaction gets one, they're used for lock tracking, log correlation, and internal conflict resolution. Yet they lack the API surface that XIDs enjoy. This patch partially addresses that asymmetry.
-
Connection pooling implications: In pooled environments where PIDs are reused across logical sessions, VXIDs provide more precise transaction-level correlation with server logs. The
%vlog placeholder exists precisely for this reason, but applications had no way to obtain the same identifier for self-correlation.
Current Status
As of the last message (2026-05-14), the patch is in a near-ready state:
- v3 has received positive review from both Henson Choi (reviewer) and Robert Haas (committer)
- One minor
pgindentreflow is needed for v4 - Robert indicates he would commit it post-feature-freeze with consensus
- Michael Paquier expressed skepticism but explicitly invited being "outvoted"
- The patch is effectively waiting on: (a) a trivial v4 from Pavlo, and (b) sufficient community consensus to overcome Michael's concern