Line data Source code
1 : //!
2 : //! Parse PostgreSQL WAL records and store them in a neon Timeline.
3 : //!
4 : //! The pipeline for ingesting WAL looks like this:
5 : //!
6 : //! WAL receiver -> [`wal_decoder`] -> WalIngest -> Repository
7 : //!
8 : //! The WAL receiver receives a stream of WAL from the WAL safekeepers.
9 : //! Records get decoded and interpreted in the [`wal_decoder`] module
10 : //! and then stored to the Repository by WalIngest.
11 : //!
12 : //! The neon Repository can store page versions in two formats: as
13 : //! page images, or a WAL records. [`wal_decoder::models::InterpretedWalRecord::from_bytes_filtered`]
14 : //! extracts page images out of some WAL records, but mostly it's WAL
15 : //! records. If a WAL record modifies multiple pages, WalIngest
16 : //! will call Repository::put_rel_wal_record or put_rel_page_image functions
17 : //! separately for each modified page.
18 : //!
19 : //! To reconstruct a page using a WAL record, the Repository calls the
20 : //! code in walredo.rs. walredo.rs passes most WAL records to the WAL
21 : //! redo Postgres process, but some records it can handle directly with
22 : //! bespoken Rust code.
23 :
24 : use std::collections::HashMap;
25 : use std::sync::Arc;
26 : use std::sync::OnceLock;
27 : use std::time::Duration;
28 : use std::time::Instant;
29 : use std::time::SystemTime;
30 :
31 : use pageserver_api::shard::ShardIdentity;
32 : use postgres_ffi::fsm_logical_to_physical;
33 : use postgres_ffi::walrecord::*;
34 : use postgres_ffi::{dispatch_pgversion, enum_pgversion, enum_pgversion_dispatch, TimestampTz};
35 : use wal_decoder::models::*;
36 :
37 : use anyhow::{bail, Result};
38 : use bytes::{Buf, Bytes};
39 : use tracing::*;
40 : use utils::failpoint_support;
41 : use utils::rate_limit::RateLimit;
42 :
43 : use crate::context::RequestContext;
44 : use crate::metrics::WAL_INGEST;
45 : use crate::pgdatadir_mapping::{DatadirModification, Version};
46 : use crate::span::debug_assert_current_span_has_tenant_and_timeline_id;
47 : use crate::tenant::PageReconstructError;
48 : use crate::tenant::Timeline;
49 : use crate::ZERO_PAGE;
50 : use pageserver_api::key::rel_block_to_key;
51 : use pageserver_api::record::NeonWalRecord;
52 : use pageserver_api::reltag::{BlockNumber, RelTag, SlruKind};
53 : use postgres_ffi::pg_constants;
54 : use postgres_ffi::relfile_utils::{FSM_FORKNUM, INIT_FORKNUM, MAIN_FORKNUM, VISIBILITYMAP_FORKNUM};
55 : use postgres_ffi::TransactionId;
56 : use utils::bin_ser::SerializeError;
57 : use utils::lsn::Lsn;
58 :
59 : enum_pgversion! {CheckPoint, pgv::CheckPoint}
60 :
61 : impl CheckPoint {
62 12 : fn encode(&self) -> Result<Bytes, SerializeError> {
63 12 : enum_pgversion_dispatch!(self, CheckPoint, cp, { cp.encode() })
64 12 : }
65 :
66 291668 : fn update_next_xid(&mut self, xid: u32) -> bool {
67 291668 : enum_pgversion_dispatch!(self, CheckPoint, cp, { cp.update_next_xid(xid) })
68 291668 : }
69 :
70 0 : pub fn update_next_multixid(&mut self, multi_xid: u32, multi_offset: u32) -> bool {
71 0 : enum_pgversion_dispatch!(self, CheckPoint, cp, {
72 0 : cp.update_next_multixid(multi_xid, multi_offset)
73 : })
74 0 : }
75 : }
76 :
77 : /// Temporary limitation of WAL lag warnings after attach
78 : ///
79 : /// After tenant attach, we want to limit WAL lag warnings because
80 : /// we don't look at the WAL until the attach is complete, which
81 : /// might take a while.
82 : pub struct WalLagCooldown {
83 : /// Until when should this limitation apply at all
84 : active_until: std::time::Instant,
85 : /// The maximum lag to suppress. Lags above this limit get reported anyways.
86 : max_lag: Duration,
87 : }
88 :
89 : impl WalLagCooldown {
90 0 : pub fn new(attach_start: Instant, attach_duration: Duration) -> Self {
91 0 : Self {
92 0 : active_until: attach_start + attach_duration * 3 + Duration::from_secs(120),
93 0 : max_lag: attach_duration * 2 + Duration::from_secs(60),
94 0 : }
95 0 : }
96 : }
97 :
98 : pub struct WalIngest {
99 : attach_wal_lag_cooldown: Arc<OnceLock<WalLagCooldown>>,
100 : shard: ShardIdentity,
101 : checkpoint: CheckPoint,
102 : checkpoint_modified: bool,
103 : warn_ingest_lag: WarnIngestLag,
104 : }
105 :
106 : struct WarnIngestLag {
107 : lag_msg_ratelimit: RateLimit,
108 : future_lsn_msg_ratelimit: RateLimit,
109 : timestamp_invalid_msg_ratelimit: RateLimit,
110 : }
111 :
112 : impl WalIngest {
113 24 : pub async fn new(
114 24 : timeline: &Timeline,
115 24 : startpoint: Lsn,
116 24 : ctx: &RequestContext,
117 24 : ) -> anyhow::Result<WalIngest> {
118 : // Fetch the latest checkpoint into memory, so that we can compare with it
119 : // quickly in `ingest_record` and update it when it changes.
120 24 : let checkpoint_bytes = timeline.get_checkpoint(startpoint, ctx).await?;
121 24 : let pgversion = timeline.pg_version;
122 :
123 24 : let checkpoint = dispatch_pgversion!(pgversion, {
124 0 : let checkpoint = pgv::CheckPoint::decode(&checkpoint_bytes)?;
125 0 : trace!("CheckPoint.nextXid = {}", checkpoint.nextXid.value);
126 0 : <pgv::CheckPoint as Into<CheckPoint>>::into(checkpoint)
127 : });
128 :
129 24 : Ok(WalIngest {
130 24 : shard: *timeline.get_shard_identity(),
131 24 : checkpoint,
132 24 : checkpoint_modified: false,
133 24 : attach_wal_lag_cooldown: timeline.attach_wal_lag_cooldown.clone(),
134 24 : warn_ingest_lag: WarnIngestLag {
135 24 : lag_msg_ratelimit: RateLimit::new(std::time::Duration::from_secs(10)),
136 24 : future_lsn_msg_ratelimit: RateLimit::new(std::time::Duration::from_secs(10)),
137 24 : timestamp_invalid_msg_ratelimit: RateLimit::new(std::time::Duration::from_secs(10)),
138 24 : },
139 24 : })
140 24 : }
141 :
142 : /// Ingest an interpreted PostgreSQL WAL record by doing writes to the underlying key value
143 : /// storage of a given timeline.
144 : ///
145 : /// This function updates `lsn` field of `DatadirModification`
146 : ///
147 : /// This function returns `true` if the record was ingested, and `false` if it was filtered out
148 291704 : pub async fn ingest_record(
149 291704 : &mut self,
150 291704 : interpreted: InterpretedWalRecord,
151 291704 : modification: &mut DatadirModification<'_>,
152 291704 : ctx: &RequestContext,
153 291704 : ) -> anyhow::Result<bool> {
154 291704 : WAL_INGEST.records_received.inc();
155 291704 : let prev_len = modification.len();
156 291704 :
157 291704 : modification.set_lsn(interpreted.next_record_lsn)?;
158 :
159 291704 : if matches!(interpreted.flush_uncommitted, FlushUncommittedRecords::Yes) {
160 : // Records of this type should always be preceded by a commit(), as they
161 : // rely on reading data pages back from the Timeline.
162 0 : assert!(!modification.has_dirty_data());
163 291704 : }
164 :
165 291704 : assert!(!self.checkpoint_modified);
166 291704 : if interpreted.xid != pg_constants::INVALID_TRANSACTION_ID
167 291668 : && self.checkpoint.update_next_xid(interpreted.xid)
168 4 : {
169 4 : self.checkpoint_modified = true;
170 291700 : }
171 :
172 291704 : failpoint_support::sleep_millis_async!("wal-ingest-record-sleep");
173 :
174 132 : match interpreted.metadata_record {
175 24 : Some(MetadataRecord::Heapam(rec)) => match rec {
176 24 : HeapamRecord::ClearVmBits(clear_vm_bits) => {
177 24 : self.ingest_clear_vm_bits(clear_vm_bits, modification, ctx)
178 24 : .await?;
179 : }
180 : },
181 0 : Some(MetadataRecord::Neonrmgr(rec)) => match rec {
182 0 : NeonrmgrRecord::ClearVmBits(clear_vm_bits) => {
183 0 : self.ingest_clear_vm_bits(clear_vm_bits, modification, ctx)
184 0 : .await?;
185 : }
186 : },
187 32 : Some(MetadataRecord::Smgr(rec)) => match rec {
188 32 : SmgrRecord::Create(create) => {
189 32 : self.ingest_xlog_smgr_create(create, modification, ctx)
190 32 : .await?;
191 : }
192 0 : SmgrRecord::Truncate(truncate) => {
193 0 : self.ingest_xlog_smgr_truncate(truncate, modification, ctx)
194 0 : .await?;
195 : }
196 : },
197 0 : Some(MetadataRecord::Dbase(rec)) => match rec {
198 0 : DbaseRecord::Create(create) => {
199 0 : self.ingest_xlog_dbase_create(create, modification, ctx)
200 0 : .await?;
201 : }
202 0 : DbaseRecord::Drop(drop) => {
203 0 : self.ingest_xlog_dbase_drop(drop, modification, ctx).await?;
204 : }
205 : },
206 0 : Some(MetadataRecord::Clog(rec)) => match rec {
207 0 : ClogRecord::ZeroPage(zero_page) => {
208 0 : self.ingest_clog_zero_page(zero_page, modification, ctx)
209 0 : .await?;
210 : }
211 0 : ClogRecord::Truncate(truncate) => {
212 0 : self.ingest_clog_truncate(truncate, modification, ctx)
213 0 : .await?;
214 : }
215 : },
216 16 : Some(MetadataRecord::Xact(rec)) => {
217 16 : self.ingest_xact_record(rec, modification, ctx).await?;
218 : }
219 0 : Some(MetadataRecord::MultiXact(rec)) => match rec {
220 0 : MultiXactRecord::ZeroPage(zero_page) => {
221 0 : self.ingest_multixact_zero_page(zero_page, modification, ctx)
222 0 : .await?;
223 : }
224 0 : MultiXactRecord::Create(create) => {
225 0 : self.ingest_multixact_create(modification, &create)?;
226 : }
227 0 : MultiXactRecord::Truncate(truncate) => {
228 0 : self.ingest_multixact_truncate(modification, &truncate, ctx)
229 0 : .await?;
230 : }
231 : },
232 0 : Some(MetadataRecord::Relmap(rec)) => match rec {
233 0 : RelmapRecord::Update(update) => {
234 0 : self.ingest_relmap_update(update, modification, ctx).await?;
235 : }
236 : },
237 60 : Some(MetadataRecord::Xlog(rec)) => match rec {
238 60 : XlogRecord::Raw(raw) => {
239 60 : self.ingest_raw_xlog_record(raw, modification, ctx).await?;
240 : }
241 : },
242 0 : Some(MetadataRecord::LogicalMessage(rec)) => match rec {
243 0 : LogicalMessageRecord::Put(put) => {
244 0 : self.ingest_logical_message_put(put, modification, ctx)
245 0 : .await?;
246 : }
247 : #[cfg(feature = "testing")]
248 : LogicalMessageRecord::Failpoint => {
249 : // This is a convenient way to make the WAL ingestion pause at
250 : // particular point in the WAL. For more fine-grained control,
251 : // we could peek into the message and only pause if it contains
252 : // a particular string, for example, but this is enough for now.
253 0 : failpoint_support::sleep_millis_async!(
254 0 : "pageserver-wal-ingest-logical-message-sleep"
255 0 : );
256 : }
257 : },
258 0 : Some(MetadataRecord::Standby(rec)) => {
259 0 : self.ingest_standby_record(rec).unwrap();
260 0 : }
261 0 : Some(MetadataRecord::Replorigin(rec)) => {
262 0 : self.ingest_replorigin_record(rec, modification).await?;
263 : }
264 291572 : None => {
265 291572 : // There are two cases through which we end up here:
266 291572 : // 1. The resource manager for the original PG WAL record
267 291572 : // is [`pg_constants::RM_TBLSPC_ID`]. This is not a supported
268 291572 : // record type within Neon.
269 291572 : // 2. The resource manager id was unknown to
270 291572 : // [`wal_decoder::decoder::MetadataRecord::from_decoded`].
271 291572 : // TODO(vlad): Tighten this up more once we build confidence
272 291572 : // that case (2) does not happen in the field.
273 291572 : }
274 : }
275 :
276 291704 : modification
277 291704 : .ingest_batch(interpreted.batch, &self.shard, ctx)
278 291704 : .await?;
279 :
280 : // If checkpoint data was updated, store the new version in the repository
281 291704 : if self.checkpoint_modified {
282 12 : let new_checkpoint_bytes = self.checkpoint.encode()?;
283 :
284 12 : modification.put_checkpoint(new_checkpoint_bytes)?;
285 12 : self.checkpoint_modified = false;
286 291692 : }
287 :
288 : // Note that at this point this record is only cached in the modification
289 : // until commit() is called to flush the data into the repository and update
290 : // the latest LSN.
291 :
292 291704 : Ok(modification.len() > prev_len)
293 291704 : }
294 :
295 : /// This is the same as AdjustToFullTransactionId(xid) in PostgreSQL
296 0 : fn adjust_to_full_transaction_id(&self, xid: TransactionId) -> Result<u64> {
297 0 : let next_full_xid =
298 0 : enum_pgversion_dispatch!(&self.checkpoint, CheckPoint, cp, { cp.nextXid.value });
299 :
300 0 : let next_xid = (next_full_xid) as u32;
301 0 : let mut epoch = (next_full_xid >> 32) as u32;
302 0 :
303 0 : if xid > next_xid {
304 : // Wraparound occurred, must be from a prev epoch.
305 0 : if epoch == 0 {
306 0 : bail!("apparent XID wraparound with prepared transaction XID {xid}, nextXid is {next_full_xid}");
307 0 : }
308 0 : epoch -= 1;
309 0 : }
310 :
311 0 : Ok(((epoch as u64) << 32) | xid as u64)
312 0 : }
313 :
314 24 : async fn ingest_clear_vm_bits(
315 24 : &mut self,
316 24 : clear_vm_bits: ClearVmBits,
317 24 : modification: &mut DatadirModification<'_>,
318 24 : ctx: &RequestContext,
319 24 : ) -> anyhow::Result<()> {
320 24 : let ClearVmBits {
321 24 : new_heap_blkno,
322 24 : old_heap_blkno,
323 24 : flags,
324 24 : vm_rel,
325 24 : } = clear_vm_bits;
326 24 : // Clear the VM bits if required.
327 24 : let mut new_vm_blk = new_heap_blkno.map(pg_constants::HEAPBLK_TO_MAPBLOCK);
328 24 : let mut old_vm_blk = old_heap_blkno.map(pg_constants::HEAPBLK_TO_MAPBLOCK);
329 :
330 : // Sometimes, Postgres seems to create heap WAL records with the
331 : // ALL_VISIBLE_CLEARED flag set, even though the bit in the VM page is
332 : // not set. In fact, it's possible that the VM page does not exist at all.
333 : // In that case, we don't want to store a record to clear the VM bit;
334 : // replaying it would fail to find the previous image of the page, because
335 : // it doesn't exist. So check if the VM page(s) exist, and skip the WAL
336 : // record if it doesn't.
337 : //
338 : // TODO: analyze the metrics and tighten this up accordingly. This logic
339 : // implicitly assumes that VM pages see explicit WAL writes before
340 : // implicit ClearVmBits, and will otherwise silently drop updates.
341 24 : let Some(vm_size) = get_relsize(modification, vm_rel, ctx).await? else {
342 0 : WAL_INGEST
343 0 : .clear_vm_bits_unknown
344 0 : .with_label_values(&["relation"])
345 0 : .inc();
346 0 : return Ok(());
347 : };
348 24 : if let Some(blknum) = new_vm_blk {
349 24 : if blknum >= vm_size {
350 0 : WAL_INGEST
351 0 : .clear_vm_bits_unknown
352 0 : .with_label_values(&["new_page"])
353 0 : .inc();
354 0 : new_vm_blk = None;
355 24 : }
356 0 : }
357 24 : if let Some(blknum) = old_vm_blk {
358 0 : if blknum >= vm_size {
359 0 : WAL_INGEST
360 0 : .clear_vm_bits_unknown
361 0 : .with_label_values(&["old_page"])
362 0 : .inc();
363 0 : old_vm_blk = None;
364 0 : }
365 24 : }
366 :
367 24 : if new_vm_blk.is_some() || old_vm_blk.is_some() {
368 24 : if new_vm_blk == old_vm_blk {
369 : // An UPDATE record that needs to clear the bits for both old and the
370 : // new page, both of which reside on the same VM page.
371 0 : self.put_rel_wal_record(
372 0 : modification,
373 0 : vm_rel,
374 0 : new_vm_blk.unwrap(),
375 0 : NeonWalRecord::ClearVisibilityMapFlags {
376 0 : new_heap_blkno,
377 0 : old_heap_blkno,
378 0 : flags,
379 0 : },
380 0 : ctx,
381 0 : )
382 0 : .await?;
383 : } else {
384 : // Clear VM bits for one heap page, or for two pages that reside on
385 : // different VM pages.
386 24 : if let Some(new_vm_blk) = new_vm_blk {
387 24 : self.put_rel_wal_record(
388 24 : modification,
389 24 : vm_rel,
390 24 : new_vm_blk,
391 24 : NeonWalRecord::ClearVisibilityMapFlags {
392 24 : new_heap_blkno,
393 24 : old_heap_blkno: None,
394 24 : flags,
395 24 : },
396 24 : ctx,
397 24 : )
398 24 : .await?;
399 0 : }
400 24 : if let Some(old_vm_blk) = old_vm_blk {
401 0 : self.put_rel_wal_record(
402 0 : modification,
403 0 : vm_rel,
404 0 : old_vm_blk,
405 0 : NeonWalRecord::ClearVisibilityMapFlags {
406 0 : new_heap_blkno: None,
407 0 : old_heap_blkno,
408 0 : flags,
409 0 : },
410 0 : ctx,
411 0 : )
412 0 : .await?;
413 24 : }
414 : }
415 0 : }
416 :
417 24 : Ok(())
418 24 : }
419 :
420 : /// Subroutine of ingest_record(), to handle an XLOG_DBASE_CREATE record.
421 0 : async fn ingest_xlog_dbase_create(
422 0 : &mut self,
423 0 : create: DbaseCreate,
424 0 : modification: &mut DatadirModification<'_>,
425 0 : ctx: &RequestContext,
426 0 : ) -> anyhow::Result<()> {
427 0 : let DbaseCreate {
428 0 : db_id,
429 0 : tablespace_id,
430 0 : src_db_id,
431 0 : src_tablespace_id,
432 0 : } = create;
433 :
434 0 : let rels = modification
435 0 : .tline
436 0 : .list_rels(
437 0 : src_tablespace_id,
438 0 : src_db_id,
439 0 : Version::Modified(modification),
440 0 : ctx,
441 0 : )
442 0 : .await?;
443 :
444 0 : debug!("ingest_xlog_dbase_create: {} rels", rels.len());
445 :
446 : // Copy relfilemap
447 0 : let filemap = modification
448 0 : .tline
449 0 : .get_relmap_file(
450 0 : src_tablespace_id,
451 0 : src_db_id,
452 0 : Version::Modified(modification),
453 0 : ctx,
454 0 : )
455 0 : .await?;
456 0 : modification
457 0 : .put_relmap_file(tablespace_id, db_id, filemap, ctx)
458 0 : .await?;
459 :
460 0 : let mut num_rels_copied = 0;
461 0 : let mut num_blocks_copied = 0;
462 0 : for src_rel in rels {
463 0 : assert_eq!(src_rel.spcnode, src_tablespace_id);
464 0 : assert_eq!(src_rel.dbnode, src_db_id);
465 :
466 0 : let nblocks = modification
467 0 : .tline
468 0 : .get_rel_size(src_rel, Version::Modified(modification), ctx)
469 0 : .await?;
470 0 : let dst_rel = RelTag {
471 0 : spcnode: tablespace_id,
472 0 : dbnode: db_id,
473 0 : relnode: src_rel.relnode,
474 0 : forknum: src_rel.forknum,
475 0 : };
476 0 :
477 0 : modification.put_rel_creation(dst_rel, nblocks, ctx).await?;
478 :
479 : // Copy content
480 0 : debug!("copying rel {} to {}, {} blocks", src_rel, dst_rel, nblocks);
481 0 : for blknum in 0..nblocks {
482 : // Sharding:
483 : // - src and dst are always on the same shard, because they differ only by dbNode, and
484 : // dbNode is not included in the hash inputs for sharding.
485 : // - This WAL command is replayed on all shards, but each shard only copies the blocks
486 : // that belong to it.
487 0 : let src_key = rel_block_to_key(src_rel, blknum);
488 0 : if !self.shard.is_key_local(&src_key) {
489 0 : debug!(
490 0 : "Skipping non-local key {} during XLOG_DBASE_CREATE",
491 : src_key
492 : );
493 0 : continue;
494 0 : }
495 0 : debug!(
496 0 : "copying block {} from {} ({}) to {}",
497 : blknum, src_rel, src_key, dst_rel
498 : );
499 :
500 0 : let content = modification
501 0 : .tline
502 0 : .get_rel_page_at_lsn(
503 0 : src_rel,
504 0 : blknum,
505 0 : Version::Modified(modification),
506 0 : ctx,
507 0 : crate::tenant::storage_layer::IoConcurrency::sequential(),
508 0 : )
509 0 : .await?;
510 0 : modification.put_rel_page_image(dst_rel, blknum, content)?;
511 0 : num_blocks_copied += 1;
512 : }
513 :
514 0 : num_rels_copied += 1;
515 : }
516 :
517 0 : info!(
518 0 : "Created database {}/{}, copied {} blocks in {} rels",
519 : tablespace_id, db_id, num_blocks_copied, num_rels_copied
520 : );
521 0 : Ok(())
522 0 : }
523 :
524 0 : async fn ingest_xlog_dbase_drop(
525 0 : &mut self,
526 0 : dbase_drop: DbaseDrop,
527 0 : modification: &mut DatadirModification<'_>,
528 0 : ctx: &RequestContext,
529 0 : ) -> anyhow::Result<()> {
530 0 : let DbaseDrop {
531 0 : db_id,
532 0 : tablespace_ids,
533 0 : } = dbase_drop;
534 0 : for tablespace_id in tablespace_ids {
535 0 : trace!("Drop db {}, {}", tablespace_id, db_id);
536 0 : modification.drop_dbdir(tablespace_id, db_id, ctx).await?;
537 : }
538 :
539 0 : Ok(())
540 0 : }
541 :
542 32 : async fn ingest_xlog_smgr_create(
543 32 : &mut self,
544 32 : create: SmgrCreate,
545 32 : modification: &mut DatadirModification<'_>,
546 32 : ctx: &RequestContext,
547 32 : ) -> anyhow::Result<()> {
548 32 : let SmgrCreate { rel } = create;
549 32 : self.put_rel_creation(modification, rel, ctx).await?;
550 32 : Ok(())
551 32 : }
552 :
553 : /// Subroutine of ingest_record(), to handle an XLOG_SMGR_TRUNCATE record.
554 : ///
555 : /// This is the same logic as in PostgreSQL's smgr_redo() function.
556 0 : async fn ingest_xlog_smgr_truncate(
557 0 : &mut self,
558 0 : truncate: XlSmgrTruncate,
559 0 : modification: &mut DatadirModification<'_>,
560 0 : ctx: &RequestContext,
561 0 : ) -> anyhow::Result<()> {
562 0 : let XlSmgrTruncate {
563 0 : blkno,
564 0 : rnode,
565 0 : flags,
566 0 : } = truncate;
567 0 :
568 0 : let spcnode = rnode.spcnode;
569 0 : let dbnode = rnode.dbnode;
570 0 : let relnode = rnode.relnode;
571 0 :
572 0 : if flags & pg_constants::SMGR_TRUNCATE_HEAP != 0 {
573 0 : let rel = RelTag {
574 0 : spcnode,
575 0 : dbnode,
576 0 : relnode,
577 0 : forknum: MAIN_FORKNUM,
578 0 : };
579 0 :
580 0 : self.put_rel_truncation(modification, rel, blkno, ctx)
581 0 : .await?;
582 0 : }
583 0 : if flags & pg_constants::SMGR_TRUNCATE_FSM != 0 {
584 0 : let rel = RelTag {
585 0 : spcnode,
586 0 : dbnode,
587 0 : relnode,
588 0 : forknum: FSM_FORKNUM,
589 0 : };
590 0 :
591 0 : // Zero out the last remaining FSM page, if this shard owns it. We are not precise here,
592 0 : // and instead of digging in the FSM bitmap format we just clear the whole page.
593 0 : let fsm_logical_page_no = blkno / pg_constants::SLOTS_PER_FSM_PAGE;
594 0 : let mut fsm_physical_page_no = fsm_logical_to_physical(fsm_logical_page_no);
595 0 : if blkno % pg_constants::SLOTS_PER_FSM_PAGE != 0
596 0 : && self
597 0 : .shard
598 0 : .is_key_local(&rel_block_to_key(rel, fsm_physical_page_no))
599 : {
600 0 : modification.put_rel_page_image_zero(rel, fsm_physical_page_no)?;
601 0 : fsm_physical_page_no += 1;
602 0 : }
603 : // Truncate this shard's view of the FSM relation size, if it even has one.
604 0 : let nblocks = get_relsize(modification, rel, ctx).await?.unwrap_or(0);
605 0 : if nblocks > fsm_physical_page_no {
606 0 : self.put_rel_truncation(modification, rel, fsm_physical_page_no, ctx)
607 0 : .await?;
608 0 : }
609 0 : }
610 0 : if flags & pg_constants::SMGR_TRUNCATE_VM != 0 {
611 0 : let rel = RelTag {
612 0 : spcnode,
613 0 : dbnode,
614 0 : relnode,
615 0 : forknum: VISIBILITYMAP_FORKNUM,
616 0 : };
617 0 :
618 0 : // last remaining block, byte, and bit
619 0 : let mut vm_page_no = blkno / (pg_constants::VM_HEAPBLOCKS_PER_PAGE as u32);
620 0 : let trunc_byte = blkno as usize % pg_constants::VM_HEAPBLOCKS_PER_PAGE
621 0 : / pg_constants::VM_HEAPBLOCKS_PER_BYTE;
622 0 : let trunc_offs = blkno as usize % pg_constants::VM_HEAPBLOCKS_PER_BYTE
623 0 : * pg_constants::VM_BITS_PER_HEAPBLOCK;
624 0 :
625 0 : // Unless the new size is exactly at a visibility map page boundary, the
626 0 : // tail bits in the last remaining map page, representing truncated heap
627 0 : // blocks, need to be cleared. This is not only tidy, but also necessary
628 0 : // because we don't get a chance to clear the bits if the heap is extended
629 0 : // again. Only do this on the shard that owns the page.
630 0 : if (trunc_byte != 0 || trunc_offs != 0)
631 0 : && self.shard.is_key_local(&rel_block_to_key(rel, vm_page_no))
632 : {
633 0 : modification.put_rel_wal_record(
634 0 : rel,
635 0 : vm_page_no,
636 0 : NeonWalRecord::TruncateVisibilityMap {
637 0 : trunc_byte,
638 0 : trunc_offs,
639 0 : },
640 0 : )?;
641 0 : vm_page_no += 1;
642 0 : }
643 : // Truncate this shard's view of the VM relation size, if it even has one.
644 0 : let nblocks = get_relsize(modification, rel, ctx).await?.unwrap_or(0);
645 0 : if nblocks > vm_page_no {
646 0 : self.put_rel_truncation(modification, rel, vm_page_no, ctx)
647 0 : .await?;
648 0 : }
649 0 : }
650 0 : Ok(())
651 0 : }
652 :
653 16 : fn warn_on_ingest_lag(
654 16 : &mut self,
655 16 : conf: &crate::config::PageServerConf,
656 16 : wal_timestamp: TimestampTz,
657 16 : ) {
658 16 : debug_assert_current_span_has_tenant_and_timeline_id();
659 16 : let now = SystemTime::now();
660 16 : let rate_limits = &mut self.warn_ingest_lag;
661 :
662 16 : let ts = enum_pgversion_dispatch!(&self.checkpoint, CheckPoint, _cp, {
663 0 : pgv::xlog_utils::try_from_pg_timestamp(wal_timestamp)
664 : });
665 :
666 16 : match ts {
667 16 : Ok(ts) => {
668 16 : match now.duration_since(ts) {
669 16 : Ok(lag) => {
670 16 : if lag > conf.wait_lsn_timeout {
671 16 : rate_limits.lag_msg_ratelimit.call2(|rate_limit_stats| {
672 4 : if let Some(cooldown) = self.attach_wal_lag_cooldown.get() {
673 0 : if std::time::Instant::now() < cooldown.active_until && lag <= cooldown.max_lag {
674 0 : return;
675 0 : }
676 4 : } else {
677 4 : // Still loading? We shouldn't be here
678 4 : }
679 4 : let lag = humantime::format_duration(lag);
680 4 : warn!(%rate_limit_stats, %lag, "ingesting record with timestamp lagging more than wait_lsn_timeout");
681 16 : })
682 0 : }
683 : }
684 0 : Err(e) => {
685 0 : let delta_t = e.duration();
686 : // determined by prod victoriametrics query: 1000 * (timestamp(node_time_seconds{neon_service="pageserver"}) - node_time_seconds)
687 : // => https://www.robustperception.io/time-metric-from-the-node-exporter/
688 : const IGNORED_DRIFT: Duration = Duration::from_millis(100);
689 0 : if delta_t > IGNORED_DRIFT {
690 0 : let delta_t = humantime::format_duration(delta_t);
691 0 : rate_limits.future_lsn_msg_ratelimit.call2(|rate_limit_stats| {
692 0 : warn!(%rate_limit_stats, %delta_t, "ingesting record with timestamp from future");
693 0 : })
694 0 : }
695 : }
696 : };
697 : }
698 0 : Err(error) => {
699 0 : rate_limits.timestamp_invalid_msg_ratelimit.call2(|rate_limit_stats| {
700 0 : warn!(%rate_limit_stats, %error, "ingesting record with invalid timestamp, cannot calculate lag and will fail find-lsn-for-timestamp type queries");
701 0 : })
702 : }
703 : }
704 16 : }
705 :
706 : /// Subroutine of ingest_record(), to handle an XLOG_XACT_* records.
707 : ///
708 16 : async fn ingest_xact_record(
709 16 : &mut self,
710 16 : record: XactRecord,
711 16 : modification: &mut DatadirModification<'_>,
712 16 : ctx: &RequestContext,
713 16 : ) -> anyhow::Result<()> {
714 16 : let (xact_common, is_commit, is_prepared) = match record {
715 0 : XactRecord::Prepare(XactPrepare { xl_xid, data }) => {
716 0 : let xid: u64 = if modification.tline.pg_version >= 17 {
717 0 : self.adjust_to_full_transaction_id(xl_xid)?
718 : } else {
719 0 : xl_xid as u64
720 : };
721 0 : return modification.put_twophase_file(xid, data, ctx).await;
722 : }
723 16 : XactRecord::Commit(common) => (common, true, false),
724 0 : XactRecord::Abort(common) => (common, false, false),
725 0 : XactRecord::CommitPrepared(common) => (common, true, true),
726 0 : XactRecord::AbortPrepared(common) => (common, false, true),
727 : };
728 :
729 : let XactCommon {
730 16 : parsed,
731 16 : origin_id,
732 16 : xl_xid,
733 16 : lsn,
734 16 : } = xact_common;
735 16 :
736 16 : // Record update of CLOG pages
737 16 : let mut pageno = parsed.xid / pg_constants::CLOG_XACTS_PER_PAGE;
738 16 : let mut segno = pageno / pg_constants::SLRU_PAGES_PER_SEGMENT;
739 16 : let mut rpageno = pageno % pg_constants::SLRU_PAGES_PER_SEGMENT;
740 16 : let mut page_xids: Vec<TransactionId> = vec![parsed.xid];
741 16 :
742 16 : self.warn_on_ingest_lag(modification.tline.conf, parsed.xact_time);
743 :
744 16 : for subxact in &parsed.subxacts {
745 0 : let subxact_pageno = subxact / pg_constants::CLOG_XACTS_PER_PAGE;
746 0 : if subxact_pageno != pageno {
747 : // This subxact goes to different page. Write the record
748 : // for all the XIDs on the previous page, and continue
749 : // accumulating XIDs on this new page.
750 0 : modification.put_slru_wal_record(
751 0 : SlruKind::Clog,
752 0 : segno,
753 0 : rpageno,
754 0 : if is_commit {
755 0 : NeonWalRecord::ClogSetCommitted {
756 0 : xids: page_xids,
757 0 : timestamp: parsed.xact_time,
758 0 : }
759 : } else {
760 0 : NeonWalRecord::ClogSetAborted { xids: page_xids }
761 : },
762 0 : )?;
763 0 : page_xids = Vec::new();
764 0 : }
765 0 : pageno = subxact_pageno;
766 0 : segno = pageno / pg_constants::SLRU_PAGES_PER_SEGMENT;
767 0 : rpageno = pageno % pg_constants::SLRU_PAGES_PER_SEGMENT;
768 0 : page_xids.push(*subxact);
769 : }
770 16 : modification.put_slru_wal_record(
771 16 : SlruKind::Clog,
772 16 : segno,
773 16 : rpageno,
774 16 : if is_commit {
775 16 : NeonWalRecord::ClogSetCommitted {
776 16 : xids: page_xids,
777 16 : timestamp: parsed.xact_time,
778 16 : }
779 : } else {
780 0 : NeonWalRecord::ClogSetAborted { xids: page_xids }
781 : },
782 0 : )?;
783 :
784 : // Group relations to drop by dbNode. This map will contain all relations that _might_
785 : // exist, we will reduce it to which ones really exist later. This map can be huge if
786 : // the transaction touches a huge number of relations (there is no bound on this in
787 : // postgres).
788 16 : let mut drop_relations: HashMap<(u32, u32), Vec<RelTag>> = HashMap::new();
789 :
790 16 : for xnode in &parsed.xnodes {
791 0 : for forknum in MAIN_FORKNUM..=INIT_FORKNUM {
792 0 : let rel = RelTag {
793 0 : forknum,
794 0 : spcnode: xnode.spcnode,
795 0 : dbnode: xnode.dbnode,
796 0 : relnode: xnode.relnode,
797 0 : };
798 0 : drop_relations
799 0 : .entry((xnode.spcnode, xnode.dbnode))
800 0 : .or_default()
801 0 : .push(rel);
802 0 : }
803 : }
804 :
805 : // Execute relation drops in a batch: the number may be huge, so deleting individually is prohibitively expensive
806 16 : modification.put_rel_drops(drop_relations, ctx).await?;
807 :
808 16 : if origin_id != 0 {
809 0 : modification
810 0 : .set_replorigin(origin_id, parsed.origin_lsn)
811 0 : .await?;
812 16 : }
813 :
814 16 : if is_prepared {
815 : // Remove twophase file. see RemoveTwoPhaseFile() in postgres code
816 0 : trace!(
817 0 : "Drop twophaseFile for xid {} parsed_xact.xid {} here at {}",
818 : xl_xid,
819 : parsed.xid,
820 : lsn,
821 : );
822 :
823 0 : let xid: u64 = if modification.tline.pg_version >= 17 {
824 0 : self.adjust_to_full_transaction_id(parsed.xid)?
825 : } else {
826 0 : parsed.xid as u64
827 : };
828 0 : modification.drop_twophase_file(xid, ctx).await?;
829 16 : }
830 :
831 16 : Ok(())
832 16 : }
833 :
834 0 : async fn ingest_clog_truncate(
835 0 : &mut self,
836 0 : truncate: ClogTruncate,
837 0 : modification: &mut DatadirModification<'_>,
838 0 : ctx: &RequestContext,
839 0 : ) -> anyhow::Result<()> {
840 0 : let ClogTruncate {
841 0 : pageno,
842 0 : oldest_xid,
843 0 : oldest_xid_db,
844 0 : } = truncate;
845 0 :
846 0 : info!(
847 0 : "RM_CLOG_ID truncate pageno {} oldestXid {} oldestXidDB {}",
848 : pageno, oldest_xid, oldest_xid_db
849 : );
850 :
851 : // In Postgres, oldestXid and oldestXidDB are updated in memory when the CLOG is
852 : // truncated, but a checkpoint record with the updated values isn't written until
853 : // later. In Neon, a server can start at any LSN, not just on a checkpoint record,
854 : // so we keep the oldestXid and oldestXidDB up-to-date.
855 0 : enum_pgversion_dispatch!(&mut self.checkpoint, CheckPoint, cp, {
856 0 : cp.oldestXid = oldest_xid;
857 0 : cp.oldestXidDB = oldest_xid_db;
858 0 : });
859 0 : self.checkpoint_modified = true;
860 :
861 : // TODO Treat AdvanceOldestClogXid() or write a comment why we don't need it
862 :
863 0 : let latest_page_number =
864 0 : enum_pgversion_dispatch!(self.checkpoint, CheckPoint, cp, { cp.nextXid.value }) as u32
865 : / pg_constants::CLOG_XACTS_PER_PAGE;
866 :
867 : // Now delete all segments containing pages between xlrec.pageno
868 : // and latest_page_number.
869 :
870 : // First, make an important safety check:
871 : // the current endpoint page must not be eligible for removal.
872 : // See SimpleLruTruncate() in slru.c
873 0 : if dispatch_pgversion!(modification.tline.pg_version, {
874 0 : pgv::nonrelfile_utils::clogpage_precedes(latest_page_number, pageno)
875 : }) {
876 0 : info!("could not truncate directory pg_xact apparent wraparound");
877 0 : return Ok(());
878 0 : }
879 0 :
880 0 : // Iterate via SLRU CLOG segments and drop segments that we're ready to truncate
881 0 : //
882 0 : // We cannot pass 'lsn' to the Timeline.list_nonrels(), or it
883 0 : // will block waiting for the last valid LSN to advance up to
884 0 : // it. So we use the previous record's LSN in the get calls
885 0 : // instead.
886 0 : if modification.tline.get_shard_identity().is_shard_zero() {
887 0 : for segno in modification
888 0 : .tline
889 0 : .list_slru_segments(SlruKind::Clog, Version::Modified(modification), ctx)
890 0 : .await?
891 : {
892 0 : let segpage = segno * pg_constants::SLRU_PAGES_PER_SEGMENT;
893 :
894 0 : let may_delete = dispatch_pgversion!(modification.tline.pg_version, {
895 0 : pgv::nonrelfile_utils::slru_may_delete_clogsegment(segpage, pageno)
896 : });
897 :
898 0 : if may_delete {
899 0 : modification
900 0 : .drop_slru_segment(SlruKind::Clog, segno, ctx)
901 0 : .await?;
902 0 : trace!("Drop CLOG segment {:>04X}", segno);
903 0 : }
904 : }
905 0 : }
906 :
907 0 : Ok(())
908 0 : }
909 :
910 0 : async fn ingest_clog_zero_page(
911 0 : &mut self,
912 0 : zero_page: ClogZeroPage,
913 0 : modification: &mut DatadirModification<'_>,
914 0 : ctx: &RequestContext,
915 0 : ) -> anyhow::Result<()> {
916 0 : let ClogZeroPage { segno, rpageno } = zero_page;
917 0 :
918 0 : self.put_slru_page_image(
919 0 : modification,
920 0 : SlruKind::Clog,
921 0 : segno,
922 0 : rpageno,
923 0 : ZERO_PAGE.clone(),
924 0 : ctx,
925 0 : )
926 0 : .await
927 0 : }
928 :
929 0 : fn ingest_multixact_create(
930 0 : &mut self,
931 0 : modification: &mut DatadirModification,
932 0 : xlrec: &XlMultiXactCreate,
933 0 : ) -> Result<()> {
934 0 : // Create WAL record for updating the multixact-offsets page
935 0 : let pageno = xlrec.mid / pg_constants::MULTIXACT_OFFSETS_PER_PAGE as u32;
936 0 : let segno = pageno / pg_constants::SLRU_PAGES_PER_SEGMENT;
937 0 : let rpageno = pageno % pg_constants::SLRU_PAGES_PER_SEGMENT;
938 0 :
939 0 : modification.put_slru_wal_record(
940 0 : SlruKind::MultiXactOffsets,
941 0 : segno,
942 0 : rpageno,
943 0 : NeonWalRecord::MultixactOffsetCreate {
944 0 : mid: xlrec.mid,
945 0 : moff: xlrec.moff,
946 0 : },
947 0 : )?;
948 :
949 : // Create WAL records for the update of each affected multixact-members page
950 0 : let mut members = xlrec.members.iter();
951 0 : let mut offset = xlrec.moff;
952 : loop {
953 0 : let pageno = offset / pg_constants::MULTIXACT_MEMBERS_PER_PAGE as u32;
954 0 :
955 0 : // How many members fit on this page?
956 0 : let page_remain = pg_constants::MULTIXACT_MEMBERS_PER_PAGE as u32
957 0 : - offset % pg_constants::MULTIXACT_MEMBERS_PER_PAGE as u32;
958 0 :
959 0 : let mut this_page_members: Vec<MultiXactMember> = Vec::new();
960 0 : for _ in 0..page_remain {
961 0 : if let Some(m) = members.next() {
962 0 : this_page_members.push(m.clone());
963 0 : } else {
964 0 : break;
965 : }
966 : }
967 0 : if this_page_members.is_empty() {
968 : // all done
969 0 : break;
970 0 : }
971 0 : let n_this_page = this_page_members.len();
972 0 :
973 0 : modification.put_slru_wal_record(
974 0 : SlruKind::MultiXactMembers,
975 0 : pageno / pg_constants::SLRU_PAGES_PER_SEGMENT,
976 0 : pageno % pg_constants::SLRU_PAGES_PER_SEGMENT,
977 0 : NeonWalRecord::MultixactMembersCreate {
978 0 : moff: offset,
979 0 : members: this_page_members,
980 0 : },
981 0 : )?;
982 :
983 : // Note: The multixact members can wrap around, even within one WAL record.
984 0 : offset = offset.wrapping_add(n_this_page as u32);
985 : }
986 0 : let next_offset = offset;
987 0 : assert!(xlrec.moff.wrapping_add(xlrec.nmembers) == next_offset);
988 :
989 : // Update next-multi-xid and next-offset
990 : //
991 : // NB: In PostgreSQL, the next-multi-xid stored in the control file is allowed to
992 : // go to 0, and it's fixed up by skipping to FirstMultiXactId in functions that
993 : // read it, like GetNewMultiXactId(). This is different from how nextXid is
994 : // incremented! nextXid skips over < FirstNormalTransactionId when the the value
995 : // is stored, so it's never 0 in a checkpoint.
996 : //
997 : // I don't know why it's done that way, it seems less error-prone to skip over 0
998 : // when the value is stored rather than when it's read. But let's do it the same
999 : // way here.
1000 0 : let next_multi_xid = xlrec.mid.wrapping_add(1);
1001 0 :
1002 0 : if self
1003 0 : .checkpoint
1004 0 : .update_next_multixid(next_multi_xid, next_offset)
1005 0 : {
1006 0 : self.checkpoint_modified = true;
1007 0 : }
1008 :
1009 : // Also update the next-xid with the highest member. According to the comments in
1010 : // multixact_redo(), this shouldn't be necessary, but let's do the same here.
1011 0 : let max_mbr_xid = xlrec.members.iter().fold(None, |acc, mbr| {
1012 0 : if let Some(max_xid) = acc {
1013 0 : if mbr.xid.wrapping_sub(max_xid) as i32 > 0 {
1014 0 : Some(mbr.xid)
1015 : } else {
1016 0 : acc
1017 : }
1018 : } else {
1019 0 : Some(mbr.xid)
1020 : }
1021 0 : });
1022 :
1023 0 : if let Some(max_xid) = max_mbr_xid {
1024 0 : if self.checkpoint.update_next_xid(max_xid) {
1025 0 : self.checkpoint_modified = true;
1026 0 : }
1027 0 : }
1028 0 : Ok(())
1029 0 : }
1030 :
1031 0 : async fn ingest_multixact_truncate(
1032 0 : &mut self,
1033 0 : modification: &mut DatadirModification<'_>,
1034 0 : xlrec: &XlMultiXactTruncate,
1035 0 : ctx: &RequestContext,
1036 0 : ) -> Result<()> {
1037 0 : let (maxsegment, startsegment, endsegment) =
1038 0 : enum_pgversion_dispatch!(&mut self.checkpoint, CheckPoint, cp, {
1039 0 : cp.oldestMulti = xlrec.end_trunc_off;
1040 0 : cp.oldestMultiDB = xlrec.oldest_multi_db;
1041 0 : let maxsegment: i32 = pgv::nonrelfile_utils::mx_offset_to_member_segment(
1042 0 : pg_constants::MAX_MULTIXACT_OFFSET,
1043 0 : );
1044 0 : let startsegment: i32 =
1045 0 : pgv::nonrelfile_utils::mx_offset_to_member_segment(xlrec.start_trunc_memb);
1046 0 : let endsegment: i32 =
1047 0 : pgv::nonrelfile_utils::mx_offset_to_member_segment(xlrec.end_trunc_memb);
1048 0 : (maxsegment, startsegment, endsegment)
1049 : });
1050 :
1051 0 : self.checkpoint_modified = true;
1052 0 :
1053 0 : // PerformMembersTruncation
1054 0 : let mut segment: i32 = startsegment;
1055 0 :
1056 0 : // Delete all the segments except the last one. The last segment can still
1057 0 : // contain, possibly partially, valid data.
1058 0 : if modification.tline.get_shard_identity().is_shard_zero() {
1059 0 : while segment != endsegment {
1060 0 : modification
1061 0 : .drop_slru_segment(SlruKind::MultiXactMembers, segment as u32, ctx)
1062 0 : .await?;
1063 :
1064 : /* move to next segment, handling wraparound correctly */
1065 0 : if segment == maxsegment {
1066 0 : segment = 0;
1067 0 : } else {
1068 0 : segment += 1;
1069 0 : }
1070 : }
1071 0 : }
1072 :
1073 : // Truncate offsets
1074 : // FIXME: this did not handle wraparound correctly
1075 :
1076 0 : Ok(())
1077 0 : }
1078 :
1079 0 : async fn ingest_multixact_zero_page(
1080 0 : &mut self,
1081 0 : zero_page: MultiXactZeroPage,
1082 0 : modification: &mut DatadirModification<'_>,
1083 0 : ctx: &RequestContext,
1084 0 : ) -> Result<()> {
1085 0 : let MultiXactZeroPage {
1086 0 : slru_kind,
1087 0 : segno,
1088 0 : rpageno,
1089 0 : } = zero_page;
1090 0 : self.put_slru_page_image(
1091 0 : modification,
1092 0 : slru_kind,
1093 0 : segno,
1094 0 : rpageno,
1095 0 : ZERO_PAGE.clone(),
1096 0 : ctx,
1097 0 : )
1098 0 : .await
1099 0 : }
1100 :
1101 0 : async fn ingest_relmap_update(
1102 0 : &mut self,
1103 0 : update: RelmapUpdate,
1104 0 : modification: &mut DatadirModification<'_>,
1105 0 : ctx: &RequestContext,
1106 0 : ) -> Result<()> {
1107 0 : let RelmapUpdate { update, buf } = update;
1108 0 :
1109 0 : modification
1110 0 : .put_relmap_file(update.tsid, update.dbid, buf, ctx)
1111 0 : .await
1112 0 : }
1113 :
1114 60 : async fn ingest_raw_xlog_record(
1115 60 : &mut self,
1116 60 : raw_record: RawXlogRecord,
1117 60 : modification: &mut DatadirModification<'_>,
1118 60 : ctx: &RequestContext,
1119 60 : ) -> Result<()> {
1120 60 : let RawXlogRecord { info, lsn, mut buf } = raw_record;
1121 60 : let pg_version = modification.tline.pg_version;
1122 60 :
1123 60 : if info == pg_constants::XLOG_PARAMETER_CHANGE {
1124 4 : if let CheckPoint::V17(cp) = &mut self.checkpoint {
1125 0 : let rec = v17::XlParameterChange::decode(&mut buf);
1126 0 : cp.wal_level = rec.wal_level;
1127 0 : self.checkpoint_modified = true;
1128 4 : }
1129 56 : } else if info == pg_constants::XLOG_END_OF_RECOVERY {
1130 0 : if let CheckPoint::V17(cp) = &mut self.checkpoint {
1131 0 : let rec = v17::XlEndOfRecovery::decode(&mut buf);
1132 0 : cp.wal_level = rec.wal_level;
1133 0 : self.checkpoint_modified = true;
1134 0 : }
1135 56 : }
1136 :
1137 60 : enum_pgversion_dispatch!(&mut self.checkpoint, CheckPoint, cp, {
1138 0 : if info == pg_constants::XLOG_NEXTOID {
1139 0 : let next_oid = buf.get_u32_le();
1140 0 : if cp.nextOid != next_oid {
1141 0 : cp.nextOid = next_oid;
1142 0 : self.checkpoint_modified = true;
1143 0 : }
1144 0 : } else if info == pg_constants::XLOG_CHECKPOINT_ONLINE
1145 0 : || info == pg_constants::XLOG_CHECKPOINT_SHUTDOWN
1146 : {
1147 0 : let mut checkpoint_bytes = [0u8; pgv::xlog_utils::SIZEOF_CHECKPOINT];
1148 0 : buf.copy_to_slice(&mut checkpoint_bytes);
1149 0 : let xlog_checkpoint = pgv::CheckPoint::decode(&checkpoint_bytes)?;
1150 0 : trace!(
1151 0 : "xlog_checkpoint.oldestXid={}, checkpoint.oldestXid={}",
1152 : xlog_checkpoint.oldestXid,
1153 : cp.oldestXid
1154 : );
1155 0 : if (cp.oldestXid.wrapping_sub(xlog_checkpoint.oldestXid) as i32) < 0 {
1156 0 : cp.oldestXid = xlog_checkpoint.oldestXid;
1157 0 : }
1158 0 : trace!(
1159 0 : "xlog_checkpoint.oldestActiveXid={}, checkpoint.oldestActiveXid={}",
1160 : xlog_checkpoint.oldestActiveXid,
1161 : cp.oldestActiveXid
1162 : );
1163 :
1164 : // A shutdown checkpoint has `oldestActiveXid == InvalidTransactionid`,
1165 : // because at shutdown, all in-progress transactions will implicitly
1166 : // end. Postgres startup code knows that, and allows hot standby to start
1167 : // immediately from a shutdown checkpoint.
1168 : //
1169 : // In Neon, Postgres hot standby startup always behaves as if starting from
1170 : // an online checkpoint. It needs a valid `oldestActiveXid` value, so
1171 : // instead of overwriting self.checkpoint.oldestActiveXid with
1172 : // InvalidTransactionid from the checkpoint WAL record, update it to a
1173 : // proper value, knowing that there are no in-progress transactions at this
1174 : // point, except for prepared transactions.
1175 : //
1176 : // See also the neon code changes in the InitWalRecovery() function.
1177 0 : if xlog_checkpoint.oldestActiveXid == pg_constants::INVALID_TRANSACTION_ID
1178 0 : && info == pg_constants::XLOG_CHECKPOINT_SHUTDOWN
1179 : {
1180 0 : let oldest_active_xid = if pg_version >= 17 {
1181 0 : let mut oldest_active_full_xid = cp.nextXid.value;
1182 0 : for xid in modification.tline.list_twophase_files(lsn, ctx).await? {
1183 0 : if xid < oldest_active_full_xid {
1184 0 : oldest_active_full_xid = xid;
1185 0 : }
1186 : }
1187 0 : oldest_active_full_xid as u32
1188 : } else {
1189 0 : let mut oldest_active_xid = cp.nextXid.value as u32;
1190 0 : for xid in modification.tline.list_twophase_files(lsn, ctx).await? {
1191 0 : let narrow_xid = xid as u32;
1192 0 : if (narrow_xid.wrapping_sub(oldest_active_xid) as i32) < 0 {
1193 0 : oldest_active_xid = narrow_xid;
1194 0 : }
1195 : }
1196 0 : oldest_active_xid
1197 : };
1198 0 : cp.oldestActiveXid = oldest_active_xid;
1199 0 : } else {
1200 0 : cp.oldestActiveXid = xlog_checkpoint.oldestActiveXid;
1201 0 : }
1202 :
1203 : // Write a new checkpoint key-value pair on every checkpoint record, even
1204 : // if nothing really changed. Not strictly required, but it seems nice to
1205 : // have some trace of the checkpoint records in the layer files at the same
1206 : // LSNs.
1207 0 : self.checkpoint_modified = true;
1208 0 : }
1209 : });
1210 :
1211 60 : Ok(())
1212 60 : }
1213 :
1214 0 : async fn ingest_logical_message_put(
1215 0 : &mut self,
1216 0 : put: PutLogicalMessage,
1217 0 : modification: &mut DatadirModification<'_>,
1218 0 : ctx: &RequestContext,
1219 0 : ) -> Result<()> {
1220 0 : let PutLogicalMessage { path, buf } = put;
1221 0 : modification.put_file(path.as_str(), &buf, ctx).await
1222 0 : }
1223 :
1224 0 : fn ingest_standby_record(&mut self, record: StandbyRecord) -> Result<()> {
1225 0 : match record {
1226 0 : StandbyRecord::RunningXacts(running_xacts) => {
1227 0 : enum_pgversion_dispatch!(&mut self.checkpoint, CheckPoint, cp, {
1228 0 : cp.oldestActiveXid = running_xacts.oldest_running_xid;
1229 0 : });
1230 :
1231 0 : self.checkpoint_modified = true;
1232 0 : }
1233 0 : }
1234 0 :
1235 0 : Ok(())
1236 0 : }
1237 :
1238 0 : async fn ingest_replorigin_record(
1239 0 : &mut self,
1240 0 : record: ReploriginRecord,
1241 0 : modification: &mut DatadirModification<'_>,
1242 0 : ) -> Result<()> {
1243 0 : match record {
1244 0 : ReploriginRecord::Set(set) => {
1245 0 : modification
1246 0 : .set_replorigin(set.node_id, set.remote_lsn)
1247 0 : .await?;
1248 : }
1249 0 : ReploriginRecord::Drop(drop) => {
1250 0 : modification.drop_replorigin(drop.node_id).await?;
1251 : }
1252 : }
1253 :
1254 0 : Ok(())
1255 0 : }
1256 :
1257 36 : async fn put_rel_creation(
1258 36 : &mut self,
1259 36 : modification: &mut DatadirModification<'_>,
1260 36 : rel: RelTag,
1261 36 : ctx: &RequestContext,
1262 36 : ) -> Result<()> {
1263 36 : modification.put_rel_creation(rel, 0, ctx).await?;
1264 36 : Ok(())
1265 36 : }
1266 :
1267 : #[cfg(test)]
1268 544804 : async fn put_rel_page_image(
1269 544804 : &mut self,
1270 544804 : modification: &mut DatadirModification<'_>,
1271 544804 : rel: RelTag,
1272 544804 : blknum: BlockNumber,
1273 544804 : img: Bytes,
1274 544804 : ctx: &RequestContext,
1275 544804 : ) -> Result<(), PageReconstructError> {
1276 544804 : self.handle_rel_extend(modification, rel, blknum, ctx)
1277 544804 : .await?;
1278 544804 : modification.put_rel_page_image(rel, blknum, img)?;
1279 544804 : Ok(())
1280 544804 : }
1281 :
1282 24 : async fn put_rel_wal_record(
1283 24 : &mut self,
1284 24 : modification: &mut DatadirModification<'_>,
1285 24 : rel: RelTag,
1286 24 : blknum: BlockNumber,
1287 24 : rec: NeonWalRecord,
1288 24 : ctx: &RequestContext,
1289 24 : ) -> Result<()> {
1290 24 : self.handle_rel_extend(modification, rel, blknum, ctx)
1291 24 : .await?;
1292 24 : modification.put_rel_wal_record(rel, blknum, rec)?;
1293 24 : Ok(())
1294 24 : }
1295 :
1296 12024 : async fn put_rel_truncation(
1297 12024 : &mut self,
1298 12024 : modification: &mut DatadirModification<'_>,
1299 12024 : rel: RelTag,
1300 12024 : nblocks: BlockNumber,
1301 12024 : ctx: &RequestContext,
1302 12024 : ) -> anyhow::Result<()> {
1303 12024 : modification.put_rel_truncation(rel, nblocks, ctx).await?;
1304 12024 : Ok(())
1305 12024 : }
1306 :
1307 544828 : async fn handle_rel_extend(
1308 544828 : &mut self,
1309 544828 : modification: &mut DatadirModification<'_>,
1310 544828 : rel: RelTag,
1311 544828 : blknum: BlockNumber,
1312 544828 : ctx: &RequestContext,
1313 544828 : ) -> Result<(), PageReconstructError> {
1314 544828 : let new_nblocks = blknum + 1;
1315 : // Check if the relation exists. We implicitly create relations on first
1316 : // record.
1317 544828 : let old_nblocks = modification.create_relation_if_required(rel, ctx).await?;
1318 :
1319 544828 : if new_nblocks > old_nblocks {
1320 : //info!("extending {} {} to {}", rel, old_nblocks, new_nblocks);
1321 544796 : modification.put_rel_extend(rel, new_nblocks, ctx).await?;
1322 :
1323 544796 : let mut key = rel_block_to_key(rel, blknum);
1324 544796 :
1325 544796 : // fill the gap with zeros
1326 544796 : let mut gap_blocks_filled: u64 = 0;
1327 544796 : for gap_blknum in old_nblocks..blknum {
1328 5996 : key.field6 = gap_blknum;
1329 5996 :
1330 5996 : if self.shard.get_shard_number(&key) != self.shard.number {
1331 0 : continue;
1332 5996 : }
1333 5996 :
1334 5996 : modification.put_rel_page_image_zero(rel, gap_blknum)?;
1335 5996 : gap_blocks_filled += 1;
1336 : }
1337 :
1338 544796 : WAL_INGEST
1339 544796 : .gap_blocks_zeroed_on_rel_extend
1340 544796 : .inc_by(gap_blocks_filled);
1341 544796 :
1342 544796 : // Log something when relation extends cause use to fill gaps
1343 544796 : // with zero pages. Logging is rate limited per pg version to
1344 544796 : // avoid skewing.
1345 544796 : if gap_blocks_filled > 0 {
1346 : use once_cell::sync::Lazy;
1347 : use std::sync::Mutex;
1348 : use utils::rate_limit::RateLimit;
1349 :
1350 : struct RateLimitPerPgVersion {
1351 : rate_limiters: [Lazy<Mutex<RateLimit>>; 4],
1352 : }
1353 :
1354 : impl RateLimitPerPgVersion {
1355 0 : const fn new() -> Self {
1356 0 : Self {
1357 0 : rate_limiters: [const {
1358 4 : Lazy::new(|| Mutex::new(RateLimit::new(Duration::from_secs(30))))
1359 0 : }; 4],
1360 0 : }
1361 0 : }
1362 :
1363 8 : const fn rate_limiter(
1364 8 : &self,
1365 8 : pg_version: u32,
1366 8 : ) -> Option<&Lazy<Mutex<RateLimit>>> {
1367 : const MIN_PG_VERSION: u32 = 14;
1368 : const MAX_PG_VERSION: u32 = 17;
1369 :
1370 8 : if pg_version < MIN_PG_VERSION || pg_version > MAX_PG_VERSION {
1371 0 : return None;
1372 8 : }
1373 8 :
1374 8 : Some(&self.rate_limiters[(pg_version - MIN_PG_VERSION) as usize])
1375 8 : }
1376 : }
1377 :
1378 : static LOGGED: RateLimitPerPgVersion = RateLimitPerPgVersion::new();
1379 8 : if let Some(rate_limiter) = LOGGED.rate_limiter(modification.tline.pg_version) {
1380 8 : if let Ok(mut locked) = rate_limiter.try_lock() {
1381 8 : locked.call(|| {
1382 4 : info!(
1383 0 : lsn=%modification.get_lsn(),
1384 0 : pg_version=%modification.tline.pg_version,
1385 0 : rel=%rel,
1386 0 : "Filled {} gap blocks on rel extend to {} from {}",
1387 : gap_blocks_filled,
1388 : new_nblocks,
1389 : old_nblocks);
1390 8 : });
1391 8 : }
1392 0 : }
1393 544788 : }
1394 32 : }
1395 544828 : Ok(())
1396 544828 : }
1397 :
1398 0 : async fn put_slru_page_image(
1399 0 : &mut self,
1400 0 : modification: &mut DatadirModification<'_>,
1401 0 : kind: SlruKind,
1402 0 : segno: u32,
1403 0 : blknum: BlockNumber,
1404 0 : img: Bytes,
1405 0 : ctx: &RequestContext,
1406 0 : ) -> Result<()> {
1407 0 : if !self.shard.is_shard_zero() {
1408 0 : return Ok(());
1409 0 : }
1410 0 :
1411 0 : self.handle_slru_extend(modification, kind, segno, blknum, ctx)
1412 0 : .await?;
1413 0 : modification.put_slru_page_image(kind, segno, blknum, img)?;
1414 0 : Ok(())
1415 0 : }
1416 :
1417 0 : async fn handle_slru_extend(
1418 0 : &mut self,
1419 0 : modification: &mut DatadirModification<'_>,
1420 0 : kind: SlruKind,
1421 0 : segno: u32,
1422 0 : blknum: BlockNumber,
1423 0 : ctx: &RequestContext,
1424 0 : ) -> anyhow::Result<()> {
1425 0 : // we don't use a cache for this like we do for relations. SLRUS are explcitly
1426 0 : // extended with ZEROPAGE records, not with commit records, so it happens
1427 0 : // a lot less frequently.
1428 0 :
1429 0 : let new_nblocks = blknum + 1;
1430 : // Check if the relation exists. We implicitly create relations on first
1431 : // record.
1432 : // TODO: would be nice if to be more explicit about it
1433 0 : let old_nblocks = if !modification
1434 0 : .tline
1435 0 : .get_slru_segment_exists(kind, segno, Version::Modified(modification), ctx)
1436 0 : .await?
1437 : {
1438 : // create it with 0 size initially, the logic below will extend it
1439 0 : modification
1440 0 : .put_slru_segment_creation(kind, segno, 0, ctx)
1441 0 : .await?;
1442 0 : 0
1443 : } else {
1444 0 : modification
1445 0 : .tline
1446 0 : .get_slru_segment_size(kind, segno, Version::Modified(modification), ctx)
1447 0 : .await?
1448 : };
1449 :
1450 0 : if new_nblocks > old_nblocks {
1451 0 : trace!(
1452 0 : "extending SLRU {:?} seg {} from {} to {} blocks",
1453 : kind,
1454 : segno,
1455 : old_nblocks,
1456 : new_nblocks
1457 : );
1458 0 : modification.put_slru_extend(kind, segno, new_nblocks)?;
1459 :
1460 : // fill the gap with zeros
1461 0 : for gap_blknum in old_nblocks..blknum {
1462 0 : modification.put_slru_page_image_zero(kind, segno, gap_blknum)?;
1463 : }
1464 0 : }
1465 0 : Ok(())
1466 0 : }
1467 : }
1468 :
1469 : /// Returns the size of the relation as of this modification, or None if the relation doesn't exist.
1470 : ///
1471 : /// This is only accurate on shard 0. On other shards, it will return the size up to the highest
1472 : /// page number stored in the shard, or None if the shard does not have any pages for it.
1473 24 : async fn get_relsize(
1474 24 : modification: &DatadirModification<'_>,
1475 24 : rel: RelTag,
1476 24 : ctx: &RequestContext,
1477 24 : ) -> Result<Option<BlockNumber>, PageReconstructError> {
1478 24 : if !modification
1479 24 : .tline
1480 24 : .get_rel_exists(rel, Version::Modified(modification), ctx)
1481 24 : .await?
1482 : {
1483 0 : return Ok(None);
1484 24 : }
1485 24 : modification
1486 24 : .tline
1487 24 : .get_rel_size(rel, Version::Modified(modification), ctx)
1488 24 : .await
1489 24 : .map(Some)
1490 24 : }
1491 :
1492 : #[allow(clippy::bool_assert_comparison)]
1493 : #[cfg(test)]
1494 : mod tests {
1495 : use super::*;
1496 : use crate::tenant::harness::*;
1497 : use crate::tenant::remote_timeline_client::{remote_initdb_archive_path, INITDB_PATH};
1498 : use crate::tenant::storage_layer::IoConcurrency;
1499 : use postgres_ffi::RELSEG_SIZE;
1500 :
1501 : use crate::DEFAULT_PG_VERSION;
1502 :
1503 : /// Arbitrary relation tag, for testing.
1504 : const TESTREL_A: RelTag = RelTag {
1505 : spcnode: 0,
1506 : dbnode: 111,
1507 : relnode: 1000,
1508 : forknum: 0,
1509 : };
1510 :
1511 24 : fn assert_current_logical_size(_timeline: &Timeline, _lsn: Lsn) {
1512 24 : // TODO
1513 24 : }
1514 :
1515 : #[tokio::test]
1516 4 : async fn test_zeroed_checkpoint_decodes_correctly() -> Result<()> {
1517 16 : for i in 14..=16 {
1518 12 : dispatch_pgversion!(i, {
1519 4 : pgv::CheckPoint::decode(&pgv::ZERO_CHECKPOINT)?;
1520 4 : });
1521 4 : }
1522 4 :
1523 4 : Ok(())
1524 4 : }
1525 :
1526 16 : async fn init_walingest_test(tline: &Timeline, ctx: &RequestContext) -> Result<WalIngest> {
1527 16 : let mut m = tline.begin_modification(Lsn(0x10));
1528 16 : m.put_checkpoint(dispatch_pgversion!(
1529 16 : tline.pg_version,
1530 0 : pgv::ZERO_CHECKPOINT.clone()
1531 0 : ))?;
1532 16 : m.put_relmap_file(0, 111, Bytes::from(""), ctx).await?; // dummy relmapper file
1533 16 : m.commit(ctx).await?;
1534 16 : let walingest = WalIngest::new(tline, Lsn(0x10), ctx).await?;
1535 :
1536 16 : Ok(walingest)
1537 16 : }
1538 :
1539 : #[tokio::test]
1540 4 : async fn test_relsize() -> Result<()> {
1541 4 : let (tenant, ctx) = TenantHarness::create("test_relsize").await?.load().await;
1542 4 : let io_concurrency = IoConcurrency::spawn_for_test();
1543 4 : let tline = tenant
1544 4 : .create_test_timeline(TIMELINE_ID, Lsn(8), DEFAULT_PG_VERSION, &ctx)
1545 4 : .await?;
1546 4 : let mut walingest = init_walingest_test(&tline, &ctx).await?;
1547 4 :
1548 4 : let mut m = tline.begin_modification(Lsn(0x20));
1549 4 : walingest.put_rel_creation(&mut m, TESTREL_A, &ctx).await?;
1550 4 : walingest
1551 4 : .put_rel_page_image(&mut m, TESTREL_A, 0, test_img("foo blk 0 at 2"), &ctx)
1552 4 : .await?;
1553 4 : m.commit(&ctx).await?;
1554 4 : let mut m = tline.begin_modification(Lsn(0x30));
1555 4 : walingest
1556 4 : .put_rel_page_image(&mut m, TESTREL_A, 0, test_img("foo blk 0 at 3"), &ctx)
1557 4 : .await?;
1558 4 : m.commit(&ctx).await?;
1559 4 : let mut m = tline.begin_modification(Lsn(0x40));
1560 4 : walingest
1561 4 : .put_rel_page_image(&mut m, TESTREL_A, 1, test_img("foo blk 1 at 4"), &ctx)
1562 4 : .await?;
1563 4 : m.commit(&ctx).await?;
1564 4 : let mut m = tline.begin_modification(Lsn(0x50));
1565 4 : walingest
1566 4 : .put_rel_page_image(&mut m, TESTREL_A, 2, test_img("foo blk 2 at 5"), &ctx)
1567 4 : .await?;
1568 4 : m.commit(&ctx).await?;
1569 4 :
1570 4 : assert_current_logical_size(&tline, Lsn(0x50));
1571 4 :
1572 4 : let test_span = tracing::info_span!(parent: None, "test",
1573 4 : tenant_id=%tline.tenant_shard_id.tenant_id,
1574 0 : shard_id=%tline.tenant_shard_id.shard_slug(),
1575 0 : timeline_id=%tline.timeline_id);
1576 4 :
1577 4 : // The relation was created at LSN 2, not visible at LSN 1 yet.
1578 4 : assert_eq!(
1579 4 : tline
1580 4 : .get_rel_exists(TESTREL_A, Version::Lsn(Lsn(0x10)), &ctx)
1581 4 : .await?,
1582 4 : false
1583 4 : );
1584 4 : assert!(tline
1585 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x10)), &ctx)
1586 4 : .await
1587 4 : .is_err());
1588 4 : assert_eq!(
1589 4 : tline
1590 4 : .get_rel_exists(TESTREL_A, Version::Lsn(Lsn(0x20)), &ctx)
1591 4 : .await?,
1592 4 : true
1593 4 : );
1594 4 : assert_eq!(
1595 4 : tline
1596 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x20)), &ctx)
1597 4 : .await?,
1598 4 : 1
1599 4 : );
1600 4 : assert_eq!(
1601 4 : tline
1602 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x50)), &ctx)
1603 4 : .await?,
1604 4 : 3
1605 4 : );
1606 4 :
1607 4 : // Check page contents at each LSN
1608 4 : assert_eq!(
1609 4 : tline
1610 4 : .get_rel_page_at_lsn(
1611 4 : TESTREL_A,
1612 4 : 0,
1613 4 : Version::Lsn(Lsn(0x20)),
1614 4 : &ctx,
1615 4 : io_concurrency.clone()
1616 4 : )
1617 4 : .instrument(test_span.clone())
1618 4 : .await?,
1619 4 : test_img("foo blk 0 at 2")
1620 4 : );
1621 4 :
1622 4 : assert_eq!(
1623 4 : tline
1624 4 : .get_rel_page_at_lsn(
1625 4 : TESTREL_A,
1626 4 : 0,
1627 4 : Version::Lsn(Lsn(0x30)),
1628 4 : &ctx,
1629 4 : io_concurrency.clone()
1630 4 : )
1631 4 : .instrument(test_span.clone())
1632 4 : .await?,
1633 4 : test_img("foo blk 0 at 3")
1634 4 : );
1635 4 :
1636 4 : assert_eq!(
1637 4 : tline
1638 4 : .get_rel_page_at_lsn(
1639 4 : TESTREL_A,
1640 4 : 0,
1641 4 : Version::Lsn(Lsn(0x40)),
1642 4 : &ctx,
1643 4 : io_concurrency.clone()
1644 4 : )
1645 4 : .instrument(test_span.clone())
1646 4 : .await?,
1647 4 : test_img("foo blk 0 at 3")
1648 4 : );
1649 4 : assert_eq!(
1650 4 : tline
1651 4 : .get_rel_page_at_lsn(
1652 4 : TESTREL_A,
1653 4 : 1,
1654 4 : Version::Lsn(Lsn(0x40)),
1655 4 : &ctx,
1656 4 : io_concurrency.clone()
1657 4 : )
1658 4 : .instrument(test_span.clone())
1659 4 : .await?,
1660 4 : test_img("foo blk 1 at 4")
1661 4 : );
1662 4 :
1663 4 : assert_eq!(
1664 4 : tline
1665 4 : .get_rel_page_at_lsn(
1666 4 : TESTREL_A,
1667 4 : 0,
1668 4 : Version::Lsn(Lsn(0x50)),
1669 4 : &ctx,
1670 4 : io_concurrency.clone()
1671 4 : )
1672 4 : .instrument(test_span.clone())
1673 4 : .await?,
1674 4 : test_img("foo blk 0 at 3")
1675 4 : );
1676 4 : assert_eq!(
1677 4 : tline
1678 4 : .get_rel_page_at_lsn(
1679 4 : TESTREL_A,
1680 4 : 1,
1681 4 : Version::Lsn(Lsn(0x50)),
1682 4 : &ctx,
1683 4 : io_concurrency.clone()
1684 4 : )
1685 4 : .instrument(test_span.clone())
1686 4 : .await?,
1687 4 : test_img("foo blk 1 at 4")
1688 4 : );
1689 4 : assert_eq!(
1690 4 : tline
1691 4 : .get_rel_page_at_lsn(
1692 4 : TESTREL_A,
1693 4 : 2,
1694 4 : Version::Lsn(Lsn(0x50)),
1695 4 : &ctx,
1696 4 : io_concurrency.clone()
1697 4 : )
1698 4 : .instrument(test_span.clone())
1699 4 : .await?,
1700 4 : test_img("foo blk 2 at 5")
1701 4 : );
1702 4 :
1703 4 : // Truncate last block
1704 4 : let mut m = tline.begin_modification(Lsn(0x60));
1705 4 : walingest
1706 4 : .put_rel_truncation(&mut m, TESTREL_A, 2, &ctx)
1707 4 : .await?;
1708 4 : m.commit(&ctx).await?;
1709 4 : assert_current_logical_size(&tline, Lsn(0x60));
1710 4 :
1711 4 : // Check reported size and contents after truncation
1712 4 : assert_eq!(
1713 4 : tline
1714 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x60)), &ctx)
1715 4 : .await?,
1716 4 : 2
1717 4 : );
1718 4 : assert_eq!(
1719 4 : tline
1720 4 : .get_rel_page_at_lsn(
1721 4 : TESTREL_A,
1722 4 : 0,
1723 4 : Version::Lsn(Lsn(0x60)),
1724 4 : &ctx,
1725 4 : io_concurrency.clone()
1726 4 : )
1727 4 : .instrument(test_span.clone())
1728 4 : .await?,
1729 4 : test_img("foo blk 0 at 3")
1730 4 : );
1731 4 : assert_eq!(
1732 4 : tline
1733 4 : .get_rel_page_at_lsn(
1734 4 : TESTREL_A,
1735 4 : 1,
1736 4 : Version::Lsn(Lsn(0x60)),
1737 4 : &ctx,
1738 4 : io_concurrency.clone()
1739 4 : )
1740 4 : .instrument(test_span.clone())
1741 4 : .await?,
1742 4 : test_img("foo blk 1 at 4")
1743 4 : );
1744 4 :
1745 4 : // should still see the truncated block with older LSN
1746 4 : assert_eq!(
1747 4 : tline
1748 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x50)), &ctx)
1749 4 : .await?,
1750 4 : 3
1751 4 : );
1752 4 : assert_eq!(
1753 4 : tline
1754 4 : .get_rel_page_at_lsn(
1755 4 : TESTREL_A,
1756 4 : 2,
1757 4 : Version::Lsn(Lsn(0x50)),
1758 4 : &ctx,
1759 4 : io_concurrency.clone()
1760 4 : )
1761 4 : .instrument(test_span.clone())
1762 4 : .await?,
1763 4 : test_img("foo blk 2 at 5")
1764 4 : );
1765 4 :
1766 4 : // Truncate to zero length
1767 4 : let mut m = tline.begin_modification(Lsn(0x68));
1768 4 : walingest
1769 4 : .put_rel_truncation(&mut m, TESTREL_A, 0, &ctx)
1770 4 : .await?;
1771 4 : m.commit(&ctx).await?;
1772 4 : assert_eq!(
1773 4 : tline
1774 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x68)), &ctx)
1775 4 : .await?,
1776 4 : 0
1777 4 : );
1778 4 :
1779 4 : // Extend from 0 to 2 blocks, leaving a gap
1780 4 : let mut m = tline.begin_modification(Lsn(0x70));
1781 4 : walingest
1782 4 : .put_rel_page_image(&mut m, TESTREL_A, 1, test_img("foo blk 1"), &ctx)
1783 4 : .await?;
1784 4 : m.commit(&ctx).await?;
1785 4 : assert_eq!(
1786 4 : tline
1787 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x70)), &ctx)
1788 4 : .await?,
1789 4 : 2
1790 4 : );
1791 4 : assert_eq!(
1792 4 : tline
1793 4 : .get_rel_page_at_lsn(
1794 4 : TESTREL_A,
1795 4 : 0,
1796 4 : Version::Lsn(Lsn(0x70)),
1797 4 : &ctx,
1798 4 : io_concurrency.clone()
1799 4 : )
1800 4 : .instrument(test_span.clone())
1801 4 : .await?,
1802 4 : ZERO_PAGE
1803 4 : );
1804 4 : assert_eq!(
1805 4 : tline
1806 4 : .get_rel_page_at_lsn(
1807 4 : TESTREL_A,
1808 4 : 1,
1809 4 : Version::Lsn(Lsn(0x70)),
1810 4 : &ctx,
1811 4 : io_concurrency.clone()
1812 4 : )
1813 4 : .instrument(test_span.clone())
1814 4 : .await?,
1815 4 : test_img("foo blk 1")
1816 4 : );
1817 4 :
1818 4 : // Extend a lot more, leaving a big gap that spans across segments
1819 4 : let mut m = tline.begin_modification(Lsn(0x80));
1820 4 : walingest
1821 4 : .put_rel_page_image(&mut m, TESTREL_A, 1500, test_img("foo blk 1500"), &ctx)
1822 4 : .await?;
1823 4 : m.commit(&ctx).await?;
1824 4 : assert_eq!(
1825 4 : tline
1826 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x80)), &ctx)
1827 4 : .await?,
1828 4 : 1501
1829 4 : );
1830 5996 : for blk in 2..1500 {
1831 5992 : assert_eq!(
1832 5992 : tline
1833 5992 : .get_rel_page_at_lsn(
1834 5992 : TESTREL_A,
1835 5992 : blk,
1836 5992 : Version::Lsn(Lsn(0x80)),
1837 5992 : &ctx,
1838 5992 : io_concurrency.clone()
1839 5992 : )
1840 5992 : .instrument(test_span.clone())
1841 5992 : .await?,
1842 5992 : ZERO_PAGE
1843 4 : );
1844 4 : }
1845 4 : assert_eq!(
1846 4 : tline
1847 4 : .get_rel_page_at_lsn(
1848 4 : TESTREL_A,
1849 4 : 1500,
1850 4 : Version::Lsn(Lsn(0x80)),
1851 4 : &ctx,
1852 4 : io_concurrency.clone()
1853 4 : )
1854 4 : .instrument(test_span.clone())
1855 4 : .await?,
1856 4 : test_img("foo blk 1500")
1857 4 : );
1858 4 :
1859 4 : Ok(())
1860 4 : }
1861 :
1862 : // Test what happens if we dropped a relation
1863 : // and then created it again within the same layer.
1864 : #[tokio::test]
1865 4 : async fn test_drop_extend() -> Result<()> {
1866 4 : let (tenant, ctx) = TenantHarness::create("test_drop_extend")
1867 4 : .await?
1868 4 : .load()
1869 4 : .await;
1870 4 : let tline = tenant
1871 4 : .create_test_timeline(TIMELINE_ID, Lsn(8), DEFAULT_PG_VERSION, &ctx)
1872 4 : .await?;
1873 4 : let mut walingest = init_walingest_test(&tline, &ctx).await?;
1874 4 :
1875 4 : let mut m = tline.begin_modification(Lsn(0x20));
1876 4 : walingest
1877 4 : .put_rel_page_image(&mut m, TESTREL_A, 0, test_img("foo blk 0 at 2"), &ctx)
1878 4 : .await?;
1879 4 : m.commit(&ctx).await?;
1880 4 :
1881 4 : // Check that rel exists and size is correct
1882 4 : assert_eq!(
1883 4 : tline
1884 4 : .get_rel_exists(TESTREL_A, Version::Lsn(Lsn(0x20)), &ctx)
1885 4 : .await?,
1886 4 : true
1887 4 : );
1888 4 : assert_eq!(
1889 4 : tline
1890 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x20)), &ctx)
1891 4 : .await?,
1892 4 : 1
1893 4 : );
1894 4 :
1895 4 : // Drop rel
1896 4 : let mut m = tline.begin_modification(Lsn(0x30));
1897 4 : let mut rel_drops = HashMap::new();
1898 4 : rel_drops.insert((TESTREL_A.spcnode, TESTREL_A.dbnode), vec![TESTREL_A]);
1899 4 : m.put_rel_drops(rel_drops, &ctx).await?;
1900 4 : m.commit(&ctx).await?;
1901 4 :
1902 4 : // Check that rel is not visible anymore
1903 4 : assert_eq!(
1904 4 : tline
1905 4 : .get_rel_exists(TESTREL_A, Version::Lsn(Lsn(0x30)), &ctx)
1906 4 : .await?,
1907 4 : false
1908 4 : );
1909 4 :
1910 4 : // FIXME: should fail
1911 4 : //assert!(tline.get_rel_size(TESTREL_A, Lsn(0x30), false)?.is_none());
1912 4 :
1913 4 : // Re-create it
1914 4 : let mut m = tline.begin_modification(Lsn(0x40));
1915 4 : walingest
1916 4 : .put_rel_page_image(&mut m, TESTREL_A, 0, test_img("foo blk 0 at 4"), &ctx)
1917 4 : .await?;
1918 4 : m.commit(&ctx).await?;
1919 4 :
1920 4 : // Check that rel exists and size is correct
1921 4 : assert_eq!(
1922 4 : tline
1923 4 : .get_rel_exists(TESTREL_A, Version::Lsn(Lsn(0x40)), &ctx)
1924 4 : .await?,
1925 4 : true
1926 4 : );
1927 4 : assert_eq!(
1928 4 : tline
1929 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x40)), &ctx)
1930 4 : .await?,
1931 4 : 1
1932 4 : );
1933 4 :
1934 4 : Ok(())
1935 4 : }
1936 :
1937 : // Test what happens if we truncated a relation
1938 : // so that one of its segments was dropped
1939 : // and then extended it again within the same layer.
1940 : #[tokio::test]
1941 4 : async fn test_truncate_extend() -> Result<()> {
1942 4 : let (tenant, ctx) = TenantHarness::create("test_truncate_extend")
1943 4 : .await?
1944 4 : .load()
1945 4 : .await;
1946 4 : let io_concurrency = IoConcurrency::spawn_for_test();
1947 4 : let tline = tenant
1948 4 : .create_test_timeline(TIMELINE_ID, Lsn(8), DEFAULT_PG_VERSION, &ctx)
1949 4 : .await?;
1950 4 : let mut walingest = init_walingest_test(&tline, &ctx).await?;
1951 4 :
1952 4 : // Create a 20 MB relation (the size is arbitrary)
1953 4 : let relsize = 20 * 1024 * 1024 / 8192;
1954 4 : let mut m = tline.begin_modification(Lsn(0x20));
1955 10240 : for blkno in 0..relsize {
1956 10240 : let data = format!("foo blk {} at {}", blkno, Lsn(0x20));
1957 10240 : walingest
1958 10240 : .put_rel_page_image(&mut m, TESTREL_A, blkno, test_img(&data), &ctx)
1959 10240 : .await?;
1960 4 : }
1961 4 : m.commit(&ctx).await?;
1962 4 :
1963 4 : let test_span = tracing::info_span!(parent: None, "test",
1964 4 : tenant_id=%tline.tenant_shard_id.tenant_id,
1965 0 : shard_id=%tline.tenant_shard_id.shard_slug(),
1966 0 : timeline_id=%tline.timeline_id);
1967 4 :
1968 4 : // The relation was created at LSN 20, not visible at LSN 1 yet.
1969 4 : assert_eq!(
1970 4 : tline
1971 4 : .get_rel_exists(TESTREL_A, Version::Lsn(Lsn(0x10)), &ctx)
1972 4 : .await?,
1973 4 : false
1974 4 : );
1975 4 : assert!(tline
1976 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x10)), &ctx)
1977 4 : .await
1978 4 : .is_err());
1979 4 :
1980 4 : assert_eq!(
1981 4 : tline
1982 4 : .get_rel_exists(TESTREL_A, Version::Lsn(Lsn(0x20)), &ctx)
1983 4 : .await?,
1984 4 : true
1985 4 : );
1986 4 : assert_eq!(
1987 4 : tline
1988 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x20)), &ctx)
1989 4 : .await?,
1990 4 : relsize
1991 4 : );
1992 4 :
1993 4 : // Check relation content
1994 10240 : for blkno in 0..relsize {
1995 10240 : let lsn = Lsn(0x20);
1996 10240 : let data = format!("foo blk {} at {}", blkno, lsn);
1997 10240 : assert_eq!(
1998 10240 : tline
1999 10240 : .get_rel_page_at_lsn(
2000 10240 : TESTREL_A,
2001 10240 : blkno,
2002 10240 : Version::Lsn(lsn),
2003 10240 : &ctx,
2004 10240 : io_concurrency.clone()
2005 10240 : )
2006 10240 : .instrument(test_span.clone())
2007 10240 : .await?,
2008 10240 : test_img(&data)
2009 4 : );
2010 4 : }
2011 4 :
2012 4 : // Truncate relation so that second segment was dropped
2013 4 : // - only leave one page
2014 4 : let mut m = tline.begin_modification(Lsn(0x60));
2015 4 : walingest
2016 4 : .put_rel_truncation(&mut m, TESTREL_A, 1, &ctx)
2017 4 : .await?;
2018 4 : m.commit(&ctx).await?;
2019 4 :
2020 4 : // Check reported size and contents after truncation
2021 4 : assert_eq!(
2022 4 : tline
2023 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x60)), &ctx)
2024 4 : .await?,
2025 4 : 1
2026 4 : );
2027 4 :
2028 8 : for blkno in 0..1 {
2029 4 : let lsn = Lsn(0x20);
2030 4 : let data = format!("foo blk {} at {}", blkno, lsn);
2031 4 : assert_eq!(
2032 4 : tline
2033 4 : .get_rel_page_at_lsn(
2034 4 : TESTREL_A,
2035 4 : blkno,
2036 4 : Version::Lsn(Lsn(0x60)),
2037 4 : &ctx,
2038 4 : io_concurrency.clone()
2039 4 : )
2040 4 : .instrument(test_span.clone())
2041 4 : .await?,
2042 4 : test_img(&data)
2043 4 : );
2044 4 : }
2045 4 :
2046 4 : // should still see all blocks with older LSN
2047 4 : assert_eq!(
2048 4 : tline
2049 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x50)), &ctx)
2050 4 : .await?,
2051 4 : relsize
2052 4 : );
2053 10240 : for blkno in 0..relsize {
2054 10240 : let lsn = Lsn(0x20);
2055 10240 : let data = format!("foo blk {} at {}", blkno, lsn);
2056 10240 : assert_eq!(
2057 10240 : tline
2058 10240 : .get_rel_page_at_lsn(
2059 10240 : TESTREL_A,
2060 10240 : blkno,
2061 10240 : Version::Lsn(Lsn(0x50)),
2062 10240 : &ctx,
2063 10240 : io_concurrency.clone()
2064 10240 : )
2065 10240 : .instrument(test_span.clone())
2066 10240 : .await?,
2067 10240 : test_img(&data)
2068 4 : );
2069 4 : }
2070 4 :
2071 4 : // Extend relation again.
2072 4 : // Add enough blocks to create second segment
2073 4 : let lsn = Lsn(0x80);
2074 4 : let mut m = tline.begin_modification(lsn);
2075 10240 : for blkno in 0..relsize {
2076 10240 : let data = format!("foo blk {} at {}", blkno, lsn);
2077 10240 : walingest
2078 10240 : .put_rel_page_image(&mut m, TESTREL_A, blkno, test_img(&data), &ctx)
2079 10240 : .await?;
2080 4 : }
2081 4 : m.commit(&ctx).await?;
2082 4 :
2083 4 : assert_eq!(
2084 4 : tline
2085 4 : .get_rel_exists(TESTREL_A, Version::Lsn(Lsn(0x80)), &ctx)
2086 4 : .await?,
2087 4 : true
2088 4 : );
2089 4 : assert_eq!(
2090 4 : tline
2091 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(0x80)), &ctx)
2092 4 : .await?,
2093 4 : relsize
2094 4 : );
2095 4 : // Check relation content
2096 10240 : for blkno in 0..relsize {
2097 10240 : let lsn = Lsn(0x80);
2098 10240 : let data = format!("foo blk {} at {}", blkno, lsn);
2099 10240 : assert_eq!(
2100 10240 : tline
2101 10240 : .get_rel_page_at_lsn(
2102 10240 : TESTREL_A,
2103 10240 : blkno,
2104 10240 : Version::Lsn(Lsn(0x80)),
2105 10240 : &ctx,
2106 10240 : io_concurrency.clone()
2107 10240 : )
2108 10240 : .instrument(test_span.clone())
2109 10240 : .await?,
2110 10240 : test_img(&data)
2111 4 : );
2112 4 : }
2113 4 :
2114 4 : Ok(())
2115 4 : }
2116 :
2117 : /// Test get_relsize() and truncation with a file larger than 1 GB, so that it's
2118 : /// split into multiple 1 GB segments in Postgres.
2119 : #[tokio::test]
2120 4 : async fn test_large_rel() -> Result<()> {
2121 4 : let (tenant, ctx) = TenantHarness::create("test_large_rel").await?.load().await;
2122 4 : let tline = tenant
2123 4 : .create_test_timeline(TIMELINE_ID, Lsn(8), DEFAULT_PG_VERSION, &ctx)
2124 4 : .await?;
2125 4 : let mut walingest = init_walingest_test(&tline, &ctx).await?;
2126 4 :
2127 4 : let mut lsn = 0x10;
2128 524292 : for blknum in 0..RELSEG_SIZE + 1 {
2129 524292 : lsn += 0x10;
2130 524292 : let mut m = tline.begin_modification(Lsn(lsn));
2131 524292 : let img = test_img(&format!("foo blk {} at {}", blknum, Lsn(lsn)));
2132 524292 : walingest
2133 524292 : .put_rel_page_image(&mut m, TESTREL_A, blknum as BlockNumber, img, &ctx)
2134 524292 : .await?;
2135 524292 : m.commit(&ctx).await?;
2136 4 : }
2137 4 :
2138 4 : assert_current_logical_size(&tline, Lsn(lsn));
2139 4 :
2140 4 : assert_eq!(
2141 4 : tline
2142 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(lsn)), &ctx)
2143 4 : .await?,
2144 4 : RELSEG_SIZE + 1
2145 4 : );
2146 4 :
2147 4 : // Truncate one block
2148 4 : lsn += 0x10;
2149 4 : let mut m = tline.begin_modification(Lsn(lsn));
2150 4 : walingest
2151 4 : .put_rel_truncation(&mut m, TESTREL_A, RELSEG_SIZE, &ctx)
2152 4 : .await?;
2153 4 : m.commit(&ctx).await?;
2154 4 : assert_eq!(
2155 4 : tline
2156 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(lsn)), &ctx)
2157 4 : .await?,
2158 4 : RELSEG_SIZE
2159 4 : );
2160 4 : assert_current_logical_size(&tline, Lsn(lsn));
2161 4 :
2162 4 : // Truncate another block
2163 4 : lsn += 0x10;
2164 4 : let mut m = tline.begin_modification(Lsn(lsn));
2165 4 : walingest
2166 4 : .put_rel_truncation(&mut m, TESTREL_A, RELSEG_SIZE - 1, &ctx)
2167 4 : .await?;
2168 4 : m.commit(&ctx).await?;
2169 4 : assert_eq!(
2170 4 : tline
2171 4 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(lsn)), &ctx)
2172 4 : .await?,
2173 4 : RELSEG_SIZE - 1
2174 4 : );
2175 4 : assert_current_logical_size(&tline, Lsn(lsn));
2176 4 :
2177 4 : // Truncate to 1500, and then truncate all the way down to 0, one block at a time
2178 4 : // This tests the behavior at segment boundaries
2179 4 : let mut size: i32 = 3000;
2180 12008 : while size >= 0 {
2181 12004 : lsn += 0x10;
2182 12004 : let mut m = tline.begin_modification(Lsn(lsn));
2183 12004 : walingest
2184 12004 : .put_rel_truncation(&mut m, TESTREL_A, size as BlockNumber, &ctx)
2185 12004 : .await?;
2186 12004 : m.commit(&ctx).await?;
2187 12004 : assert_eq!(
2188 12004 : tline
2189 12004 : .get_rel_size(TESTREL_A, Version::Lsn(Lsn(lsn)), &ctx)
2190 12004 : .await?,
2191 12004 : size as BlockNumber
2192 4 : );
2193 4 :
2194 12004 : size -= 1;
2195 4 : }
2196 4 : assert_current_logical_size(&tline, Lsn(lsn));
2197 4 :
2198 4 : Ok(())
2199 4 : }
2200 :
2201 : /// Replay a wal segment file taken directly from safekeepers.
2202 : ///
2203 : /// This test is useful for benchmarking since it allows us to profile only
2204 : /// the walingest code in a single-threaded executor, and iterate more quickly
2205 : /// without waiting for unrelated steps.
2206 : #[tokio::test]
2207 4 : async fn test_ingest_real_wal() {
2208 4 : use crate::tenant::harness::*;
2209 4 : use postgres_ffi::waldecoder::WalStreamDecoder;
2210 4 : use postgres_ffi::WAL_SEGMENT_SIZE;
2211 4 :
2212 4 : // Define test data path and constants.
2213 4 : //
2214 4 : // Steps to reconstruct the data, if needed:
2215 4 : // 1. Run the pgbench python test
2216 4 : // 2. Take the first wal segment file from safekeeper
2217 4 : // 3. Compress it using `zstd --long input_file`
2218 4 : // 4. Copy initdb.tar.zst from local_fs_remote_storage
2219 4 : // 5. Grep sk logs for "restart decoder" to get startpoint
2220 4 : // 6. Run just the decoder from this test to get the endpoint.
2221 4 : // It's the last LSN the decoder will output.
2222 4 : let pg_version = 15; // The test data was generated by pg15
2223 4 : let path = "test_data/sk_wal_segment_from_pgbench";
2224 4 : let wal_segment_path = format!("{path}/000000010000000000000001.zst");
2225 4 : let source_initdb_path = format!("{path}/{INITDB_PATH}");
2226 4 : let startpoint = Lsn::from_hex("14AEC08").unwrap();
2227 4 : let _endpoint = Lsn::from_hex("1FFFF98").unwrap();
2228 4 :
2229 4 : let harness = TenantHarness::create("test_ingest_real_wal").await.unwrap();
2230 4 : let span = harness
2231 4 : .span()
2232 4 : .in_scope(|| info_span!("timeline_span", timeline_id=%TIMELINE_ID));
2233 4 : let (tenant, ctx) = harness.load().await;
2234 4 :
2235 4 : let remote_initdb_path =
2236 4 : remote_initdb_archive_path(&tenant.tenant_shard_id().tenant_id, &TIMELINE_ID);
2237 4 : let initdb_path = harness.remote_fs_dir.join(remote_initdb_path.get_path());
2238 4 :
2239 4 : std::fs::create_dir_all(initdb_path.parent().unwrap())
2240 4 : .expect("creating test dir should work");
2241 4 : std::fs::copy(source_initdb_path, initdb_path).expect("copying the initdb.tar.zst works");
2242 4 :
2243 4 : // Bootstrap a real timeline. We can't use create_test_timeline because
2244 4 : // it doesn't create a real checkpoint, and Walingest::new tries to parse
2245 4 : // the garbage data.
2246 4 : let tline = tenant
2247 4 : .bootstrap_timeline_test(TIMELINE_ID, pg_version, Some(TIMELINE_ID), &ctx)
2248 4 : .await
2249 4 : .unwrap();
2250 4 :
2251 4 : // We fully read and decompress this into memory before decoding
2252 4 : // to get a more accurate perf profile of the decoder.
2253 4 : let bytes = {
2254 4 : use async_compression::tokio::bufread::ZstdDecoder;
2255 4 : let file = tokio::fs::File::open(wal_segment_path).await.unwrap();
2256 4 : let reader = tokio::io::BufReader::new(file);
2257 4 : let decoder = ZstdDecoder::new(reader);
2258 4 : let mut reader = tokio::io::BufReader::new(decoder);
2259 4 : let mut buffer = Vec::new();
2260 4 : tokio::io::copy_buf(&mut reader, &mut buffer).await.unwrap();
2261 4 : buffer
2262 4 : };
2263 4 :
2264 4 : // TODO start a profiler too
2265 4 : let started_at = std::time::Instant::now();
2266 4 :
2267 4 : // Initialize walingest
2268 4 : let xlogoff: usize = startpoint.segment_offset(WAL_SEGMENT_SIZE);
2269 4 : let mut decoder = WalStreamDecoder::new(startpoint, pg_version);
2270 4 : let mut walingest = WalIngest::new(tline.as_ref(), startpoint, &ctx)
2271 4 : .await
2272 4 : .unwrap();
2273 4 : let mut modification = tline.begin_modification(startpoint);
2274 4 : println!("decoding {} bytes", bytes.len() - xlogoff);
2275 4 :
2276 4 : // Decode and ingest wal. We process the wal in chunks because
2277 4 : // that's what happens when we get bytes from safekeepers.
2278 949372 : for chunk in bytes[xlogoff..].chunks(50) {
2279 949372 : decoder.feed_bytes(chunk);
2280 1241072 : while let Some((lsn, recdata)) = decoder.poll_decode().unwrap() {
2281 291700 : let interpreted = InterpretedWalRecord::from_bytes_filtered(
2282 291700 : recdata,
2283 291700 : &[*modification.tline.get_shard_identity()],
2284 291700 : lsn,
2285 291700 : modification.tline.pg_version,
2286 291700 : )
2287 291700 : .unwrap()
2288 291700 : .remove(modification.tline.get_shard_identity())
2289 291700 : .unwrap();
2290 291700 :
2291 291700 : walingest
2292 291700 : .ingest_record(interpreted, &mut modification, &ctx)
2293 291700 : .instrument(span.clone())
2294 291700 : .await
2295 291700 : .unwrap();
2296 4 : }
2297 949372 : modification.commit(&ctx).await.unwrap();
2298 4 : }
2299 4 :
2300 4 : let duration = started_at.elapsed();
2301 4 : println!("done in {:?}", duration);
2302 4 : }
2303 : }
|