Write-Ahead Logging (WAL) Architecture in pgrust: A Rust Reimplementation of PostgreSQL's Transaction Log
pgrust implements PostgreSQL's WAL system in safe Rust through the wal and xloginsert crates, providing record assembly, compression, and crash recovery while maintaining binary compatibility with PostgreSQL's transaction log format.
The pgrust project reimplements PostgreSQL's core engine in safe Rust, including its Write-Ahead Logging (WAL) architecture that ensures ACID compliance and crash recovery. This Rust-based WAL system mirrors the original C implementation's semantics while leveraging Rust's memory safety guarantees. The architecture spans multiple crates, with the core logic residing in wal for data types and xloginsert for record construction.
Core WAL Components and Crate Structure
The WAL implementation is distributed across two primary crates that handle distinct responsibilities:
walcrate (crates/_support/types/wal/): Defines the WAL record vocabulary, constants, and decoded structures likeXLogRecordandDecodedXLogRecordinwal/src/wal.rsandwal/src/lib.rs.xloginsertcrate (crates/backend/access/transam/xloginsert/): Provides the WAL insertion API includingXLogBeginInsert,XLogRegisterBuffer, andXLogInsertinxloginsert/src/lib.rs.- Seam layer: The
transam_xlogcrate exposes the low-levelxlog_insert_recordseam that writes assembled records to WAL segment files.
WAL Record Vocabulary and Header Structures
The foundation of pgrust's WAL system lies in its strict adherence to PostgreSQL's binary format, defined in crates/_support/types/wal/src/wal.rs.
Resource Manager IDs and Flags
The crate defines standard resource-manager IDs and flag constants that match PostgreSQL's access/xlog* headers:
/// Resource‑manager IDs
pub const RM_XLOG_ID: RmgrId = 0;
pub const RM_XACT_ID: RmgrId = 1;
pub const RM_SMGR_ID: RmgrId = 2;
/// Flags used in `xl_info`
pub const XLR_INFO_MASK: uint8 = 0x0F;
pub const XLR_SPECIAL_REL_UPDATE: uint8 = 0x01;
pub const XLR_CHECK_CONSISTENCY: uint8 = 0x02;
The XLogRecord Header
Every WAL record begins with the XLogRecord header struct, defined in crates/_support/types/wal/src/lib.rs, which stores metadata including length, transaction ID, and CRC:
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct XLogRecord {
xl_tot_len: uint32,
xl_xid: TransactionId,
xl_prev: XLogRecPtr,
xl_info: uint8,
xl_rmid: RmgrId,
xl_crc: pg_crc32c,
}
During recovery, the system uses DecodedXLogRecord, which contains the header plus the main data slice and an array of DecodedBkpBlock structs describing block references, images, and per-block data.
The WAL Insertion Pipeline
The xloginsert crate implements a five-step insertion workflow that matches PostgreSQL's C implementation exactly. This process is orchestrated through thread-local state managed in XLogInsertState.
Step 1-3: Initialization and Buffer Registration
Record construction begins with XLogBeginInsert(), which verifies WAL insertion is allowed and activates the thread-local state. Buffer registration follows through XLogRegisterBuffer(block_id, buffer, flags), which copies page images:
// 1️⃣ Start a new WAL record
XLogBeginInsert()?;
// 2️⃣ Register the modified page (force a full‑page image)
XLogRegisterBuffer(0, buffer, REGBUF_FORCE_IMAGE)?;
// 3️⃣ Attach any extra payload
XLogRegisterData(data)?;
The alloc_block() function performs the copy, storing the page inside RegBuf.page along with the relation tag, fork number, and block number. Developers can also attach per-block data using XLogRegisterBufData(block_id, data).
Step 4-5: Assembly and Insertion
Before insertion, XLogSetRecordFlags(flags) sets metadata flags like XLOG_INCLUDE_ORIGIN. The XLogInsert(rmid, info) function then triggers XLogRecordAssemble, which performs several critical operations:
- Full-page decision:
XLogCheckBufferNeedsBackupdetermines if a full-page image is required based on the current redo pointer and the page's LSN. - Hole removal: For standard pages, the code computes holes using the
lower/uppervalues from the page header. - Compression: Optionally compresses images using PGLZ via
XLogCompressBackupBlock. - CRC calculation: Computes CRC32C over the header (minus the CRC field) and all data spans.
- Size validation: Enforces the
XLOG_RECORD_MAX_SIZElimit of approximately 1 GB.
The assembled record passes to xlog_insert_record in the transam_xlog_seams module, which atomically writes the spans to WAL segment files while holding the WAL insertion lock.
Recovery and Redo Processing
During crash recovery, the redo functions consume DecodedXLogRecord structures. The decoder provides PostgreSQL-compatible helper methods like info(), xid(), has_block_ref(), and block_image_apply().
Resource managers receive a RedoRecord view containing the info byte, main data slice, and block presence flags. This abstraction, defined in crates/_support/types/wal/src/lib.rs, allows redo functions to apply changes without managing low-level memory unsafe operations.
Practical WAL Insertion Example
The following example demonstrates inserting a custom WAL record for resource manager RM_GENERIC_ID:
use pgrust::backend::access::transam::xloginsert::*;
use pgrust::wal::{RM_GENERIC_ID, XLR_SPECIAL_REL_UPDATE};
fn log_my_event(buffer: Buffer, data: &[u8]) -> PgResult<XLogRecPtr> {
// 1️⃣ Start a new WAL record
XLogBeginInsert()?;
// 2️⃣ Register the modified page (force a full‑page image)
XLogRegisterBuffer(0, buffer, REGBUF_FORCE_IMAGE)?;
// 3️⃣ Attach any extra payload
XLogRegisterData(data)?;
// 4️⃣ Set a flag (e.g., include replication origin)
XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
// 5️⃣ Insert the record – RM = generic, info = special‑rel‑update
XLogInsert(RM_GENERIC_ID, XLR_SPECIAL_REL_UPDATE)
}
For recovery, iterate over decoded blocks:
use pgrust::wal::{DecodedXLogRecord, DecodedBkpBlock};
fn process_record(rec: &DecodedXLogRecord) {
// Access header fields
let xid = rec.xid();
let info = rec.info();
// Iterate over block references
for (i, blk) in rec.blocks().iter().enumerate() {
if blk.has_image() && blk.apply_image() {
// Restore the page image...
}
if let Some(data) = blk.data() {
// Apply per‑block data...
}
}
// Main data payload
let payload = rec.data();
// …handle payload…
}
Summary
- Safe Rust implementation: pgrust replicates PostgreSQL's WAL semantics in safe Rust, eliminating memory safety vulnerabilities while preserving binary compatibility.
- Modular crate structure: The
walcrate handles data types and constants, whilexloginsertmanages record construction and assembly. - Five-step insertion: The
XLogBeginInsert→XLogRegisterBuffer→XLogRegisterData→XLogSetRecordFlags→XLogInsertpipeline mirrors PostgreSQL's exact workflow. - Compression and validation: Records undergo PGLZ compression (via
XLogCompressBackupBlock) and CRC32C validation during assembly. - Recovery support: The
DecodedXLogRecordandRedoRecordabstractions provide type-safe access to WAL data during crash recovery.
Frequently Asked Questions
How does pgrust maintain binary compatibility with PostgreSQL's WAL format?
pgrust defines identical constants, struct layouts, and bit flags to PostgreSQL's C headers in crates/_support/types/wal/src/wal.rs. The XLogRecord struct uses the same field ordering and sizes as the C implementation, ensuring that WAL files produced by pgrust are readable by PostgreSQL and vice versa.
What is the maximum size of a single WAL record in pgrust?
The XLogRecordAssemble function enforces a hard limit of XLOG_RECORD_MAX_SIZE (approximately 1 GB) during record construction. If the assembled record exceeds this size, the insertion returns an error before reaching the xlog_insert_record seam.
How does pgrust handle full-page writes during WAL insertion?
The XLogCheckBufferNeedsBackup function determines whether a full-page image is required based on the current redo pointer and the page's LSN. When REGBUF_FORCE_IMAGE is passed to XLogRegisterBuffer, or when the page LSN indicates it was modified before the last checkpoint, the system copies the entire page image and optionally compresses it using PGLZ.
Which compression algorithm does pgrust use for WAL full-page images?
According to the xloginsert implementation in crates/backend/access/transam/xloginsert/src/lib.rs, pgrust uses PGLZ (PostgreSQL's LZ-based compression) via the XLogCompressBackupBlock function. This matches PostgreSQL's default compression method for full-page images.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →