LCOV - code coverage report
Current view: top level - libs/postgres_ffi/src - wal_generator.rs (source / functions) Coverage Total Hit
Test: 49aa928ec5b4b510172d8b5c6d154da28e70a46c.info Lines: 85.5 % 131 112
Test Date: 2024-11-13 18:23:39 Functions: 51.2 % 41 21

            Line data    Source code
       1              : use std::ffi::{CStr, CString};
       2              : 
       3              : use bytes::{Bytes, BytesMut};
       4              : use crc32c::crc32c_append;
       5              : use utils::lsn::Lsn;
       6              : 
       7              : use super::bindings::{RmgrId, XLogLongPageHeaderData, XLogPageHeaderData, XLOG_PAGE_MAGIC};
       8              : use super::xlog_utils::{
       9              :     XlLogicalMessage, XLOG_RECORD_CRC_OFFS, XLOG_SIZE_OF_XLOG_RECORD, XLP_BKP_REMOVABLE,
      10              :     XLP_FIRST_IS_CONTRECORD,
      11              : };
      12              : use super::XLogRecord;
      13              : use crate::pg_constants::{
      14              :     RM_LOGICALMSG_ID, XLOG_LOGICAL_MESSAGE, XLP_LONG_HEADER, XLR_BLOCK_ID_DATA_LONG,
      15              :     XLR_BLOCK_ID_DATA_SHORT,
      16              : };
      17              : use crate::{WAL_SEGMENT_SIZE, XLOG_BLCKSZ};
      18              : 
      19              : /// A WAL record payload. Will be prefixed by an XLogRecord header when encoded.
      20              : pub struct Record {
      21              :     pub rmid: RmgrId,
      22              :     pub info: u8,
      23              :     pub data: Bytes,
      24              : }
      25              : 
      26              : impl Record {
      27              :     /// Encodes the WAL record including an XLogRecord header. prev_lsn is the start position of
      28              :     /// the previous record in the WAL -- this is ignored by the Safekeeper, but not Postgres.
      29        11366 :     pub fn encode(&self, prev_lsn: Lsn) -> Bytes {
      30              :         // Prefix data with block ID and length.
      31        11366 :         let data_header = Bytes::from(match self.data.len() {
      32            0 :             0 => vec![],
      33        11366 :             1..=255 => vec![XLR_BLOCK_ID_DATA_SHORT, self.data.len() as u8],
      34            0 :             256.. => {
      35            0 :                 let len_bytes = (self.data.len() as u32).to_le_bytes();
      36            0 :                 [&[XLR_BLOCK_ID_DATA_LONG], len_bytes.as_slice()].concat()
      37              :             }
      38              :         });
      39              : 
      40              :         // Construct the WAL record header.
      41        11366 :         let mut header = XLogRecord {
      42        11366 :             xl_tot_len: (XLOG_SIZE_OF_XLOG_RECORD + data_header.len() + self.data.len()) as u32,
      43        11366 :             xl_xid: 0,
      44        11366 :             xl_prev: prev_lsn.into(),
      45        11366 :             xl_info: self.info,
      46        11366 :             xl_rmid: self.rmid,
      47        11366 :             __bindgen_padding_0: [0; 2],
      48        11366 :             xl_crc: 0, // see below
      49        11366 :         };
      50        11366 : 
      51        11366 :         // Compute the CRC checksum for the data, and the header up to the CRC field.
      52        11366 :         let mut crc = 0;
      53        11366 :         crc = crc32c_append(crc, &data_header);
      54        11366 :         crc = crc32c_append(crc, &self.data);
      55        11366 :         crc = crc32c_append(crc, &header.encode().unwrap()[0..XLOG_RECORD_CRC_OFFS]);
      56        11366 :         header.xl_crc = crc;
      57        11366 : 
      58        11366 :         // Encode the final header and record.
      59        11366 :         let header = header.encode().unwrap();
      60        11366 : 
      61        11366 :         [header, data_header, self.data.clone()].concat().into()
      62        11366 :     }
      63              : }
      64              : 
      65              : /// Generates WAL record payloads.
      66              : ///
      67              : /// TODO: currently only provides LogicalMessageGenerator for trivial noop messages. Add a generator
      68              : /// that creates a table and inserts rows.
      69              : pub trait RecordGenerator: Iterator<Item = Record> {}
      70              : 
      71              : impl<I: Iterator<Item = Record>> RecordGenerator for I {}
      72              : 
      73              : /// Generates binary WAL for use in tests and benchmarks. The provided record generator constructs
      74              : /// the WAL records. It is used as an iterator which yields encoded bytes for a single WAL record,
      75              : /// including internal page headers if it spans pages. Concatenating the bytes will yield a
      76              : /// complete, well-formed WAL, which can be chunked at segment boundaries if desired. Not optimized
      77              : /// for performance.
      78              : ///
      79              : /// The WAL format is version-dependant (see e.g. `XLOG_PAGE_MAGIC`), so make sure to import this
      80              : /// for the appropriate Postgres version (e.g. `postgres_ffi::v17::wal_generator::WalGenerator`).
      81              : ///
      82              : /// A WAL is split into 16 MB segments. Each segment is split into 8 KB pages, with headers.
      83              : /// Records are arbitrary length, 8-byte aligned, and may span pages. The layout is e.g.:
      84              : ///
      85              : /// |        Segment 1         |        Segment 2         |        Segment 3         |
      86              : /// | Page 1 | Page 2 | Page 3 | Page 4 | Page 5 | Page 6 | Page 7 | Page 8 | Page 9 |
      87              : /// | R1 |   R2  |R3|  R4  | R5  |  R6  |                 R7            | R8  |
      88              : #[derive(Default)]
      89              : pub struct WalGenerator<R: RecordGenerator> {
      90              :     /// Generates record payloads for the WAL.
      91              :     pub record_generator: R,
      92              :     /// Current LSN to append the next record at.
      93              :     ///
      94              :     /// Callers can modify this (and prev_lsn) to restart generation at a different LSN, but should
      95              :     /// ensure that the LSN is on a valid record boundary (i.e. we can't start appending in the
      96              :     /// middle on an existing record or header, or beyond the end of the existing WAL).
      97              :     pub lsn: Lsn,
      98              :     /// The starting LSN of the previous record. Used in WAL record headers. The Safekeeper doesn't
      99              :     /// care about this, unlike Postgres, but we include it for completeness.
     100              :     pub prev_lsn: Lsn,
     101              : }
     102              : 
     103              : impl<R: RecordGenerator> WalGenerator<R> {
     104              :     // Hardcode the sys and timeline ID. We can make them configurable if we care about them.
     105              :     const SYS_ID: u64 = 0;
     106              :     const TIMELINE_ID: u32 = 1;
     107              : 
     108              :     /// Creates a new WAL generator with the given record generator.
     109         9652 :     pub fn new(record_generator: R) -> WalGenerator<R> {
     110         9652 :         Self {
     111         9652 :             record_generator,
     112         9652 :             lsn: Lsn(0),
     113         9652 :             prev_lsn: Lsn(0),
     114         9652 :         }
     115         9652 :     }
     116              : 
     117              :     /// Appends a record with an arbitrary payload at the current LSN, then increments the LSN.
     118              :     /// Returns the WAL bytes for the record, including page headers and padding, and the start LSN.
     119        11362 :     fn append_record(&mut self, record: Record) -> (Lsn, Bytes) {
     120        11362 :         let record = record.encode(self.prev_lsn);
     121        11362 :         let record = Self::insert_pages(record, self.lsn);
     122        11362 :         let record = Self::pad_record(record, self.lsn);
     123        11362 :         let lsn = self.lsn;
     124        11362 :         self.prev_lsn = self.lsn;
     125        11362 :         self.lsn += record.len() as u64;
     126        11362 :         (lsn, record)
     127        11362 :     }
     128              : 
     129              :     /// Inserts page headers on 8KB page boundaries. Takes the current LSN position where the record
     130              :     /// is to be appended.
     131        11362 :     fn insert_pages(record: Bytes, mut lsn: Lsn) -> Bytes {
     132        11362 :         // Fast path: record fits in current page, and the page already has a header.
     133        11362 :         if lsn.remaining_in_block() as usize >= record.len() && lsn.block_offset() > 0 {
     134        11282 :             return record;
     135           80 :         }
     136           80 : 
     137           80 :         let mut pages = BytesMut::new();
     138           80 :         let mut remaining = record.clone(); // Bytes::clone() is cheap
     139          231 :         while !remaining.is_empty() {
     140              :             // At new page boundary, inject page header.
     141          151 :             if lsn.block_offset() == 0 {
     142           80 :                 let mut page_header = XLogPageHeaderData {
     143           80 :                     xlp_magic: XLOG_PAGE_MAGIC as u16,
     144           80 :                     xlp_info: XLP_BKP_REMOVABLE,
     145           80 :                     xlp_tli: Self::TIMELINE_ID,
     146           80 :                     xlp_pageaddr: lsn.0,
     147           80 :                     xlp_rem_len: 0,
     148           80 :                     __bindgen_padding_0: [0; 4],
     149           80 :                 };
     150           80 :                 // If the record was split across page boundaries, mark as continuation.
     151           80 :                 if remaining.len() < record.len() {
     152           71 :                     page_header.xlp_rem_len = remaining.len() as u32;
     153           71 :                     page_header.xlp_info |= XLP_FIRST_IS_CONTRECORD;
     154           71 :                 }
     155              :                 // At start of segment, use a long page header.
     156           80 :                 let page_header = if lsn.segment_offset(WAL_SEGMENT_SIZE) == 0 {
     157            0 :                     page_header.xlp_info |= XLP_LONG_HEADER;
     158            0 :                     XLogLongPageHeaderData {
     159            0 :                         std: page_header,
     160            0 :                         xlp_sysid: Self::SYS_ID,
     161            0 :                         xlp_seg_size: WAL_SEGMENT_SIZE as u32,
     162            0 :                         xlp_xlog_blcksz: XLOG_BLCKSZ as u32,
     163            0 :                     }
     164            0 :                     .encode()
     165            0 :                     .unwrap()
     166              :                 } else {
     167           80 :                     page_header.encode().unwrap()
     168              :                 };
     169           80 :                 pages.extend_from_slice(&page_header);
     170           80 :                 lsn += page_header.len() as u64;
     171           71 :             }
     172              : 
     173              :             // Append the record up to the next page boundary, if any.
     174          151 :             let page_free = lsn.remaining_in_block() as usize;
     175          151 :             let chunk = remaining.split_to(std::cmp::min(page_free, remaining.len()));
     176          151 :             pages.extend_from_slice(&chunk);
     177          151 :             lsn += chunk.len() as u64;
     178              :         }
     179           80 :         pages.freeze()
     180        11362 :     }
     181              : 
     182              :     /// Records must be 8-byte aligned. Take an encoded record (including any injected page
     183              :     /// boundaries), starting at the given LSN, and add any necessary padding at the end.
     184        11362 :     fn pad_record(record: Bytes, mut lsn: Lsn) -> Bytes {
     185        11362 :         lsn += record.len() as u64;
     186        11362 :         let padding = lsn.calc_padding(8u64) as usize;
     187        11362 :         if padding == 0 {
     188        11362 :             return record;
     189            0 :         }
     190            0 :         [record, Bytes::from(vec![0; padding])].concat().into()
     191        11362 :     }
     192              : }
     193              : 
     194              : /// Generates WAL records as an iterator.
     195              : impl<R: RecordGenerator> Iterator for WalGenerator<R> {
     196              :     type Item = (Lsn, Bytes);
     197              : 
     198            0 :     fn next(&mut self) -> Option<Self::Item> {
     199            0 :         let record = self.record_generator.next()?;
     200            0 :         Some(self.append_record(record))
     201            0 :     }
     202              : }
     203              : 
     204              : /// Generates logical message records (effectively noops) with a fixed message.
     205              : pub struct LogicalMessageGenerator {
     206              :     prefix: CString,
     207              :     message: Vec<u8>,
     208              : }
     209              : 
     210              : impl LogicalMessageGenerator {
     211              :     const DB_ID: u32 = 0; // hardcoded for now
     212              :     const RM_ID: RmgrId = RM_LOGICALMSG_ID;
     213              :     const INFO: u8 = XLOG_LOGICAL_MESSAGE;
     214              : 
     215              :     /// Creates a new LogicalMessageGenerator.
     216         9656 :     pub fn new(prefix: &CStr, message: &[u8]) -> Self {
     217         9656 :         Self {
     218         9656 :             prefix: prefix.to_owned(),
     219         9656 :             message: message.to_owned(),
     220         9656 :         }
     221         9656 :     }
     222              : 
     223              :     /// Encodes a logical message.
     224        11366 :     fn encode(prefix: &CStr, message: &[u8]) -> Bytes {
     225        11366 :         let prefix = prefix.to_bytes_with_nul();
     226        11366 :         let header = XlLogicalMessage {
     227        11366 :             db_id: Self::DB_ID,
     228        11366 :             transactional: 0,
     229        11366 :             prefix_size: prefix.len() as u64,
     230        11366 :             message_size: message.len() as u64,
     231        11366 :         };
     232        11366 :         [&header.encode(), prefix, message].concat().into()
     233        11366 :     }
     234              : }
     235              : 
     236              : impl Iterator for LogicalMessageGenerator {
     237              :     type Item = Record;
     238              : 
     239            4 :     fn next(&mut self) -> Option<Self::Item> {
     240            4 :         Some(Record {
     241            4 :             rmid: Self::RM_ID,
     242            4 :             info: Self::INFO,
     243            4 :             data: Self::encode(&self.prefix, &self.message),
     244            4 :         })
     245            4 :     }
     246              : }
     247              : 
     248              : impl WalGenerator<LogicalMessageGenerator> {
     249              :     /// Convenience method for appending a WAL record with an arbitrary logical message at the
     250              :     /// current WAL LSN position. Returns the start LSN and resulting WAL bytes.
     251        11362 :     pub fn append_logical_message(&mut self, prefix: &CStr, message: &[u8]) -> (Lsn, Bytes) {
     252        11362 :         let record = Record {
     253        11362 :             rmid: LogicalMessageGenerator::RM_ID,
     254        11362 :             info: LogicalMessageGenerator::INFO,
     255        11362 :             data: LogicalMessageGenerator::encode(prefix, message),
     256        11362 :         };
     257        11362 :         self.append_record(record)
     258        11362 :     }
     259              : }
        

Generated by: LCOV version 2.1-beta