From ce24ccb94b57be86b74ba386b886e3fb3558ded4 Mon Sep 17 00:00:00 2001 From: duguorong Date: Sun, 8 Oct 2023 00:31:08 +0800 Subject: [PATCH 01/23] chore: add the "serde_assert" crate --- Cargo.lock | 11 +++++++++++ Cargo.toml | 1 + libs/utils/Cargo.toml | 1 + 3 files changed, 13 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index f7598a79cffe..94d9c3d2a9cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4690,6 +4690,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_assert" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda563240c1288b044209be1f0d38bb4d15044fb3e00dc354fbc922ab4733e80" +dependencies = [ + "hashbrown 0.13.2", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.183" @@ -5976,6 +5986,7 @@ dependencies = [ "routerify", "sentry", "serde", + "serde_assert", "serde_json", "serde_with", "signal-hook", diff --git a/Cargo.toml b/Cargo.toml index a0be7bb9ac36..98f86fd8b74d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,6 +124,7 @@ sentry = { version = "0.31", default-features = false, features = ["backtrace", serde = { version = "1.0", features = ["derive"] } serde_json = "1" serde_with = "2.0" +serde_assert = "0.5.0" sha2 = "0.10.2" signal-hook = "0.3" smallvec = "1.11" diff --git a/libs/utils/Cargo.toml b/libs/utils/Cargo.toml index 27df0265b412..3f4ef2abebad 100644 --- a/libs/utils/Cargo.toml +++ b/libs/utils/Cargo.toml @@ -55,6 +55,7 @@ bytes.workspace = true criterion.workspace = true hex-literal.workspace = true camino-tempfile.workspace = true +serde_assert.workspace = true [[bench]] name = "benchmarks" From c541c69a15491a7daa4b7b2a7ccd86852278467e Mon Sep 17 00:00:00 2001 From: duguorong Date: Sun, 8 Oct 2023 00:32:37 +0800 Subject: [PATCH 02/23] feat: impl custom serde for "Id" --- libs/utils/src/id.rs | 175 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 1 deletion(-) diff --git a/libs/utils/src/id.rs b/libs/utils/src/id.rs index ec13c2f96f3c..40e3a00ed445 100644 --- a/libs/utils/src/id.rs +++ b/libs/utils/src/id.rs @@ -3,6 +3,7 @@ use std::{fmt, str::FromStr}; use anyhow::Context; use hex::FromHex; use rand::Rng; +use serde::de::Visitor; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -20,9 +21,64 @@ pub enum IdError { /// /// Use `#[serde_as(as = "DisplayFromStr")]` to (de)serialize it as hex string instead: `ad50847381e248feaac9876cc71ae418`. /// Check the `serde_with::serde_as` documentation for options for more complex types. -#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] struct Id([u8; 16]); +impl Serialize for Id { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + if serializer.is_human_readable() { + serializer.collect_str(self) + } else { + self.0.serialize(serializer) + } + } +} + +impl<'de> Deserialize<'de> for Id { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct IdVisitor; + + impl<'de> Visitor<'de> for IdVisitor { + type Value = Id; + + // TODO: improve the "expecting" description + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str( + "value in either sequence form([u8; 16]) or serde_json form(hex string)", + ) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let s = serde::de::value::SeqAccessDeserializer::new(seq); + let id: [u8; 16] = Deserialize::deserialize(s)?; + Ok(Id::from(id)) + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Id::from_str(v).map_err(E::custom) + } + } + + if deserializer.is_human_readable() { + deserializer.deserialize_str(IdVisitor) + } else { + deserializer.deserialize_tuple(16, IdVisitor) + } + } +} + impl Id { pub fn get_from_buf(buf: &mut impl bytes::Buf) -> Id { let mut arr = [0u8; 16]; @@ -308,3 +364,120 @@ impl fmt::Display for NodeId { write!(f, "{}", self.0) } } + +#[cfg(test)] +mod tests { + use serde_assert::{Deserializer, Serializer, Token, Tokens}; + + use crate::bin_ser::BeSer; + + use super::*; + + #[test] + fn test_id_serde_non_human_readable() { + let original_id = Id([ + 173, 80, 132, 115, 129, 226, 72, 254, 170, 201, 135, 108, 199, 26, 228, 24, + ]); + let expected_tokens = Tokens(vec![ + Token::Tuple { len: 16 }, + Token::U8(173), + Token::U8(80), + Token::U8(132), + Token::U8(115), + Token::U8(129), + Token::U8(226), + Token::U8(72), + Token::U8(254), + Token::U8(170), + Token::U8(201), + Token::U8(135), + Token::U8(108), + Token::U8(199), + Token::U8(26), + Token::U8(228), + Token::U8(24), + Token::TupleEnd, + ]); + + let serializer = Serializer::builder().is_human_readable(false).build(); + let serialized_tokens = original_id.serialize(&serializer).unwrap(); + assert_eq!(serialized_tokens, expected_tokens); + + let mut deserializer = Deserializer::builder() + .is_human_readable(false) + .tokens(serialized_tokens) + .build(); + let deserialized_id = Id::deserialize(&mut deserializer).unwrap(); + assert_eq!(deserialized_id, original_id); + } + + #[test] + fn test_id_serde_human_readable() { + let original_id = Id([ + 173, 80, 132, 115, 129, 226, 72, 254, 170, 201, 135, 108, 199, 26, 228, 24, + ]); + let expected_tokens = Tokens(vec![Token::Str(String::from( + "ad50847381e248feaac9876cc71ae418", + ))]); + + let serializer = Serializer::builder().is_human_readable(true).build(); + let serialized_tokens = original_id.serialize(&serializer).unwrap(); + assert_eq!(serialized_tokens, expected_tokens); + + let mut deserializer = Deserializer::builder() + .is_human_readable(true) + .tokens(Tokens(vec![Token::Str(String::from( + "ad50847381e248feaac9876cc71ae418", + ))])) + .build(); + assert_eq!(Id::deserialize(&mut deserializer).unwrap(), original_id); + } + + #[test] + fn test_id_bincode_serde() { + let id_arr = [ + 173, 80, 132, 115, 129, 226, 72, 254, 170, 201, 135, 108, 199, 26, 228, 24, + ]; + + let original_id = Id(id_arr); + let expected_bytes = id_arr.to_vec(); + + let ser_bytes = original_id.ser().unwrap(); + assert!(ser_bytes == expected_bytes); + + let des_id = Id::des(&ser_bytes).unwrap(); + assert!(des_id == original_id); + } + + #[test] + fn test_tenant_id_bincode_serde() { + let id_arr = [ + 173, 80, 132, 115, 129, 226, 72, 254, 170, 201, 135, 108, 199, 26, 228, 24, + ]; + + let original_tenant_id = TenantId(Id(id_arr)); + let expected_bytes = id_arr.to_vec(); + + let ser_bytes = original_tenant_id.ser().unwrap(); + assert_eq!(ser_bytes, expected_bytes); + + let des_tenant_id = TenantId::des(&ser_bytes).unwrap(); + assert_eq!(des_tenant_id, original_tenant_id); + } + + #[test] + fn test_timeline_id_bincode_serde() { + let id_arr = [ + 173, 80, 132, 115, 129, 226, 72, 254, 170, 201, 135, 108, 199, 26, 228, 24, + ]; + + let original_timeline_id = TimelineId(Id(id_arr)); + let expected_bytes = id_arr.to_vec(); + + let ser_bytes = original_timeline_id.ser().unwrap(); + assert_eq!(ser_bytes, expected_bytes); + + let des_timeline_id = TimelineId::des(&expected_bytes).unwrap(); + assert_eq!(des_timeline_id, original_timeline_id); + } +} From 59e0164219caf149216075f6aaeaf96cf1719e4f Mon Sep 17 00:00:00 2001 From: duguorong Date: Sun, 8 Oct 2023 00:32:57 +0800 Subject: [PATCH 03/23] feat: impl custom serde for "Lsn" --- libs/utils/src/lsn.rs | 152 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 3 deletions(-) diff --git a/libs/utils/src/lsn.rs b/libs/utils/src/lsn.rs index 7d9baf7d49fd..5d861af5e437 100644 --- a/libs/utils/src/lsn.rs +++ b/libs/utils/src/lsn.rs @@ -1,7 +1,7 @@ #![warn(missing_docs)] use camino::Utf8Path; -use serde::{Deserialize, Serialize}; +use serde::{de::Visitor, Deserialize, Serialize}; use std::fmt; use std::ops::{Add, AddAssign}; use std::str::FromStr; @@ -13,10 +13,60 @@ use crate::seqwait::MonotonicCounter; pub const XLOG_BLCKSZ: u32 = 8192; /// A Postgres LSN (Log Sequence Number), also known as an XLogRecPtr -#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Hash, Serialize, Deserialize)] -#[serde(transparent)] +#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Hash)] pub struct Lsn(pub u64); +impl Serialize for Lsn { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + if serializer.is_human_readable() { + serializer.collect_str(self) + } else { + self.0.serialize(serializer) + } + } +} + +impl<'de> Deserialize<'de> for Lsn { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct LsnVisitor; + + impl<'de> Visitor<'de> for LsnVisitor { + type Value = Lsn; + + // TODO: improve the "expecting" description + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("u64 value in either bincode form(u64) or serde_json form({upper_u32_hex}/{lower_u32_hex})") + } + + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + Ok(Lsn(v)) + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Lsn::from_str(v).map_err(|e| E::custom(e)) + } + } + + if deserializer.is_human_readable() { + deserializer.deserialize_str(LsnVisitor) + } else { + deserializer.deserialize_u64(LsnVisitor) + } + } +} + /// We tried to parse an LSN from a string, but failed #[derive(Debug, PartialEq, Eq, thiserror::Error)] #[error("LsnParseError")] @@ -264,8 +314,13 @@ impl MonotonicCounter for RecordLsn { #[cfg(test)] mod tests { + use crate::bin_ser::BeSer; + use super::*; + use serde::ser::Serialize; + use serde_assert::{Deserializer, Serializer, Token, Tokens}; + #[test] fn test_lsn_strings() { assert_eq!("12345678/AAAA5555".parse(), Ok(Lsn(0x12345678AAAA5555))); @@ -341,4 +396,95 @@ mod tests { assert_eq!(lsn.fetch_max(Lsn(6000)), Lsn(5678)); assert_eq!(lsn.fetch_max(Lsn(5000)), Lsn(6000)); } + + #[test] + fn test_lsn_serde() { + let original_lsn = Lsn(0x0123456789abcdef); + let expected_readable_tokens = Tokens(vec![Token::U64(0x0123456789abcdef)]); + let expected_non_readable_tokens = + Tokens(vec![Token::Str(String::from("1234567/89ABCDEF"))]); + + // Testing human_readable ser/de + let serializer = Serializer::builder().is_human_readable(false).build(); + let readable_ser_tokens = original_lsn.serialize(&serializer).unwrap(); + assert_eq!(readable_ser_tokens, expected_readable_tokens); + + let mut deserializer = Deserializer::builder() + .is_human_readable(false) + .tokens(readable_ser_tokens) + .build(); + let des_lsn = Lsn::deserialize(&mut deserializer).unwrap(); + assert_eq!(des_lsn, original_lsn); + + // Testing NON human_readable ser/de + let serializer = Serializer::builder().is_human_readable(true).build(); + let non_readable_ser_tokens = original_lsn.serialize(&serializer).unwrap(); + assert_eq!(non_readable_ser_tokens, expected_non_readable_tokens); + + let mut deserializer = Deserializer::builder() + .is_human_readable(true) + .tokens(non_readable_ser_tokens) + .build(); + let des_lsn = Lsn::deserialize(&mut deserializer).unwrap(); + assert_eq!(des_lsn, original_lsn); + + // Testing mismatching ser/de + let serializer = Serializer::builder().is_human_readable(false).build(); + let non_readable_ser_tokens = original_lsn.serialize(&serializer).unwrap(); + + let mut deserializer = Deserializer::builder() + .is_human_readable(true) + .tokens(non_readable_ser_tokens) + .build(); + Lsn::deserialize(&mut deserializer).unwrap_err(); + + let serializer = Serializer::builder().is_human_readable(true).build(); + let readable_ser_tokens = original_lsn.serialize(&serializer).unwrap(); + + let mut deserializer = Deserializer::builder() + .is_human_readable(false) + .tokens(readable_ser_tokens) + .build(); + Lsn::deserialize(&mut deserializer).unwrap_err(); + } + + #[test] + fn test_lsn_ensure_roundtrip() { + let original_lsn = Lsn(0xaaaabbbb); + + let serializer = Serializer::builder().is_human_readable(false).build(); + let ser_tokens = original_lsn.serialize(&serializer).unwrap(); + + let mut deserializer = Deserializer::builder() + .is_human_readable(false) + .tokens(ser_tokens) + .build(); + + let des_lsn = Lsn::deserialize(&mut deserializer).unwrap(); + assert_eq!(des_lsn, original_lsn); + } + + #[test] + fn test_lsn_bincode_serde() { + let lsn = Lsn(0x0123456789abcdef); + let expected_bytes = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]; + + let ser_bytes = lsn.ser().unwrap(); + assert_eq!(ser_bytes, expected_bytes); + + let des_lsn = Lsn::des(&ser_bytes).unwrap(); + assert_eq!(des_lsn, lsn); + } + + #[test] + fn test_lsn_bincode_ensure_roundtrip() { + let original_lsn = Lsn(0x01_02_03_04_05_06_07_08); + let expected_bytes = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]; + + let ser_bytes = original_lsn.ser().unwrap(); + assert_eq!(ser_bytes, expected_bytes); + + let des_lsn = Lsn::des(&ser_bytes).unwrap(); + assert_eq!(des_lsn, original_lsn); + } } From 7450e970243df71805266c96d9b35bc69b0a5d2f Mon Sep 17 00:00:00 2001 From: duguorong Date: Sun, 8 Oct 2023 00:35:06 +0800 Subject: [PATCH 04/23] test: add serde tests for "TimelineMetadata" --- pageserver/src/tenant/metadata.rs | 119 ++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/pageserver/src/tenant/metadata.rs b/pageserver/src/tenant/metadata.rs index 75ffe0969614..38fd42674605 100644 --- a/pageserver/src/tenant/metadata.rs +++ b/pageserver/src/tenant/metadata.rs @@ -406,4 +406,123 @@ mod tests { METADATA_OLD_FORMAT_VERSION, METADATA_FORMAT_VERSION ); } + + #[test] + fn test_metadata_bincode_serde() { + let original_metadata = TimelineMetadata::new( + Lsn(0x200), + Some(Lsn(0x100)), + Some(TIMELINE_ID), + Lsn(0), + Lsn(0), + Lsn(0), + // Any version will do here, so use the default + crate::DEFAULT_PG_VERSION, + ); + let metadata_bytes = original_metadata + .to_bytes() + .expect("Cannot create bytes array from metadata"); + + let metadata_bincode_be_bytes = original_metadata + .ser() + .expect("Cannot serialize the metadata"); + + // 8 bytes for the length of the vector + assert_eq!(metadata_bincode_be_bytes.len(), 8 + metadata_bytes.len()); + + let expected_bincode_bytes = { + let mut temp = vec![]; + let len_bytes = metadata_bytes.len().to_be_bytes(); + temp.extend_from_slice(&len_bytes); + temp.extend_from_slice(&metadata_bytes); + temp + }; + assert_eq!(metadata_bincode_be_bytes, expected_bincode_bytes); + + let deserialized_metadata = TimelineMetadata::des(&metadata_bincode_be_bytes).unwrap(); + // Deserialized metadata has the metadata header, which is different from the serialized one. + // Reference: TimelineMetaData::to_bytes() + let expected_metadata = { + let mut temp_metadata = original_metadata; + let body_bytes = temp_metadata + .body + .ser() + .expect("Cannot serialize the metadata body"); + let metadata_size = METADATA_HDR_SIZE + body_bytes.len(); + let hdr = TimelineMetadataHeader { + size: metadata_size as u16, + format_version: METADATA_FORMAT_VERSION, + checksum: crc32c::crc32c(&body_bytes), + }; + temp_metadata.hdr = hdr; + temp_metadata + }; + assert_eq!(deserialized_metadata, expected_metadata); + } + + #[test] + fn test_metadata_bincode_serde_ensure_roundtrip() { + let original_metadata = TimelineMetadata::new( + Lsn(0x200), + Some(Lsn(0x100)), + Some(TIMELINE_ID), + Lsn(0), + Lsn(0), + Lsn(0), + // Any version will do here, so use the default + crate::DEFAULT_PG_VERSION, + ); + let expected_bytes = vec![ + /* bincode length encoding bytes */ + 0, 0, 0, 0, 0, 0, 2, 0, // 8 bytes for the length of the serialized vector + /* TimelineMetadataHeader */ + 4, 37, 101, 34, 0, 70, 0, 4, // checksum, size, format_version (4 + 2 + 2) + /* TimelineMetadataBodyV2 */ + 0, 0, 0, 0, 0, 0, 2, 0, // disk_consistent_lsn (8 bytes) + 1, 0, 0, 0, 0, 0, 0, 1, 0, // prev_record_lsn (9 bytes) + 1, 17, 34, 51, 68, 85, 102, 119, 136, 17, 34, 51, 68, 85, 102, 119, + 136, // ancestor_timeline (17 bytes) + 0, 0, 0, 0, 0, 0, 0, 0, // ancestor_lsn (8 bytes) + 0, 0, 0, 0, 0, 0, 0, 0, // latest_gc_cutoff_lsn (8 bytes) + 0, 0, 0, 0, 0, 0, 0, 0, // initdb_lsn (8 bytes) + 0, 0, 0, 15, // pg_version (4 bytes) + /* padding bytes */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + ]; + let metadata_ser_bytes = original_metadata.ser().unwrap(); + assert_eq!(metadata_ser_bytes, expected_bytes); + + let expected_metadata = { + let mut temp_metadata = original_metadata; + let body_bytes = temp_metadata + .body + .ser() + .expect("Cannot serialize the metadata body"); + let metadata_size = METADATA_HDR_SIZE + body_bytes.len(); + let hdr = TimelineMetadataHeader { + size: metadata_size as u16, + format_version: METADATA_FORMAT_VERSION, + checksum: crc32c::crc32c(&body_bytes), + }; + temp_metadata.hdr = hdr; + temp_metadata + }; + let des_metadata = TimelineMetadata::des(&metadata_ser_bytes).unwrap(); + assert_eq!(des_metadata, expected_metadata); + } } From a223e6be4af7d644af5f6ffcdd887e8ad4d8622e Mon Sep 17 00:00:00 2001 From: duguorong Date: Sun, 8 Oct 2023 00:38:47 +0800 Subject: [PATCH 05/23] refactor: remove "#[serde_as]" attr --- control_plane/src/attachment_service.rs | 3 -- control_plane/src/endpoint.rs | 4 -- control_plane/src/local_env.rs | 4 -- libs/compute_api/src/spec.rs | 11 ++--- libs/pageserver_api/src/control_api.rs | 7 --- libs/pageserver_api/src/models.rs | 47 ++----------------- libs/safekeeper_api/src/models.rs | 12 ----- libs/utils/src/auth.rs | 3 -- libs/utils/src/pageserver_feedback.rs | 5 -- pageserver/src/consumption_metrics/metrics.rs | 4 -- pageserver/src/consumption_metrics/upload.rs | 4 -- pageserver/src/deletion_queue.rs | 3 -- pageserver/src/http/routes.rs | 2 - .../tenant/remote_timeline_client/index.rs | 3 -- pageserver/src/tenant/size.rs | 13 ----- pageserver/src/tenant/timeline.rs | 3 -- s3_scrubber/src/cloud_admin_api.rs | 3 -- safekeeper/src/debug_dump.rs | 4 -- safekeeper/src/http/routes.rs | 12 ----- safekeeper/src/pull_timeline.rs | 5 -- safekeeper/src/send_wal.rs | 3 -- safekeeper/src/timeline.rs | 4 -- 22 files changed, 8 insertions(+), 151 deletions(-) diff --git a/control_plane/src/attachment_service.rs b/control_plane/src/attachment_service.rs index ca632c5eb676..fcefe0e43189 100644 --- a/control_plane/src/attachment_service.rs +++ b/control_plane/src/attachment_service.rs @@ -2,7 +2,6 @@ use crate::{background_process, local_env::LocalEnv}; use anyhow::anyhow; use camino::Utf8PathBuf; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DisplayFromStr}; use std::{path::PathBuf, process::Child}; use utils::id::{NodeId, TenantId}; @@ -14,10 +13,8 @@ pub struct AttachmentService { const COMMAND: &str = "attachment_service"; -#[serde_as] #[derive(Serialize, Deserialize)] pub struct AttachHookRequest { - #[serde_as(as = "DisplayFromStr")] pub tenant_id: TenantId, pub node_id: Option, } diff --git a/control_plane/src/endpoint.rs b/control_plane/src/endpoint.rs index cb16f48829dc..4443fd870432 100644 --- a/control_plane/src/endpoint.rs +++ b/control_plane/src/endpoint.rs @@ -46,7 +46,6 @@ use std::time::Duration; use anyhow::{anyhow, bail, Context, Result}; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DisplayFromStr}; use utils::id::{NodeId, TenantId, TimelineId}; use crate::local_env::LocalEnv; @@ -57,13 +56,10 @@ use compute_api::responses::{ComputeState, ComputeStatus}; use compute_api::spec::{Cluster, ComputeMode, ComputeSpec}; // contents of a endpoint.json file -#[serde_as] #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct EndpointConf { endpoint_id: String, - #[serde_as(as = "DisplayFromStr")] tenant_id: TenantId, - #[serde_as(as = "DisplayFromStr")] timeline_id: TimelineId, mode: ComputeMode, pg_port: u16, diff --git a/control_plane/src/local_env.rs b/control_plane/src/local_env.rs index 45a7469787e5..b9c8aeddcb80 100644 --- a/control_plane/src/local_env.rs +++ b/control_plane/src/local_env.rs @@ -8,7 +8,6 @@ use anyhow::{bail, ensure, Context}; use postgres_backend::AuthType; use reqwest::Url; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DisplayFromStr}; use std::collections::HashMap; use std::env; use std::fs; @@ -33,7 +32,6 @@ pub const DEFAULT_PG_VERSION: u32 = 15; // to 'neon_local init --config=' option. See control_plane/simple.conf for // an example. // -#[serde_as] #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct LocalEnv { // Base directory for all the nodes (the pageserver, safekeepers and @@ -59,7 +57,6 @@ pub struct LocalEnv { // Default tenant ID to use with the 'neon_local' command line utility, when // --tenant_id is not explicitly specified. #[serde(default)] - #[serde_as(as = "Option")] pub default_tenant_id: Option, // used to issue tokens during e.g pg start @@ -84,7 +81,6 @@ pub struct LocalEnv { // A `HashMap>` would be more appropriate here, // but deserialization into a generic toml object as `toml::Value::try_from` fails with an error. // https://toml.io/en/v1.0.0 does not contain a concept of "a table inside another table". - #[serde_as(as = "HashMap<_, Vec<(DisplayFromStr, DisplayFromStr)>>")] branch_name_mappings: HashMap>, } diff --git a/libs/compute_api/src/spec.rs b/libs/compute_api/src/spec.rs index cfbd50d38a2b..ad88ceacad83 100644 --- a/libs/compute_api/src/spec.rs +++ b/libs/compute_api/src/spec.rs @@ -6,7 +6,6 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DisplayFromStr}; use utils::id::{TenantId, TimelineId}; use utils::lsn::Lsn; @@ -19,7 +18,6 @@ pub type PgIdent = String; /// Cluster spec or configuration represented as an optional number of /// delta operations + final cluster state description. -#[serde_as] #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct ComputeSpec { pub format_version: f32, @@ -50,12 +48,12 @@ pub struct ComputeSpec { // these, and instead set the "neon.tenant_id", "neon.timeline_id", // etc. GUCs in cluster.settings. TODO: Once the control plane has been // updated to fill these fields, we can make these non optional. - #[serde_as(as = "Option")] pub tenant_id: Option, - #[serde_as(as = "Option")] + pub timeline_id: Option, - #[serde_as(as = "Option")] + pub pageserver_connstring: Option, + #[serde(default)] pub safekeeper_connstrings: Vec, @@ -140,14 +138,13 @@ impl RemoteExtSpec { } } -#[serde_as] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub enum ComputeMode { /// A read-write node #[default] Primary, /// A read-only node, pinned at a particular LSN - Static(#[serde_as(as = "DisplayFromStr")] Lsn), + Static(Lsn), /// A read-only node that follows the tip of the branch in hot standby mode /// /// Future versions may want to distinguish between replicas with hot standby diff --git a/libs/pageserver_api/src/control_api.rs b/libs/pageserver_api/src/control_api.rs index 3819111a5b98..8232e81b9887 100644 --- a/libs/pageserver_api/src/control_api.rs +++ b/libs/pageserver_api/src/control_api.rs @@ -4,7 +4,6 @@ //! See docs/rfcs/025-generation-numbers.md use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DisplayFromStr}; use utils::id::{NodeId, TenantId}; #[derive(Serialize, Deserialize)] @@ -12,10 +11,8 @@ pub struct ReAttachRequest { pub node_id: NodeId, } -#[serde_as] #[derive(Serialize, Deserialize)] pub struct ReAttachResponseTenant { - #[serde_as(as = "DisplayFromStr")] pub id: TenantId, pub gen: u32, } @@ -25,10 +22,8 @@ pub struct ReAttachResponse { pub tenants: Vec, } -#[serde_as] #[derive(Serialize, Deserialize)] pub struct ValidateRequestTenant { - #[serde_as(as = "DisplayFromStr")] pub id: TenantId, pub gen: u32, } @@ -43,10 +38,8 @@ pub struct ValidateResponse { pub tenants: Vec, } -#[serde_as] #[derive(Serialize, Deserialize)] pub struct ValidateResponseTenant { - #[serde_as(as = "DisplayFromStr")] pub id: TenantId, pub valid: bool, } diff --git a/libs/pageserver_api/src/models.rs b/libs/pageserver_api/src/models.rs index e667645c0a26..cb99dc0a5530 100644 --- a/libs/pageserver_api/src/models.rs +++ b/libs/pageserver_api/src/models.rs @@ -6,7 +6,7 @@ use std::{ use byteorder::{BigEndian, ReadBytesExt}; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DisplayFromStr}; +use serde_with::serde_as; use strum_macros; use utils::{ completion, @@ -174,25 +174,19 @@ pub enum TimelineState { Broken { reason: String, backtrace: String }, } -#[serde_as] #[derive(Serialize, Deserialize)] pub struct TimelineCreateRequest { - #[serde_as(as = "DisplayFromStr")] pub new_timeline_id: TimelineId, #[serde(default)] - #[serde_as(as = "Option")] pub ancestor_timeline_id: Option, #[serde(default)] - #[serde_as(as = "Option")] pub ancestor_start_lsn: Option, pub pg_version: Option, } -#[serde_as] #[derive(Serialize, Deserialize, Debug)] #[serde(deny_unknown_fields)] pub struct TenantCreateRequest { - #[serde_as(as = "DisplayFromStr")] pub new_tenant_id: TenantId, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] @@ -201,7 +195,6 @@ pub struct TenantCreateRequest { pub config: TenantConfig, // as we have a flattened field, we should reject all unknown fields in it } -#[serde_as] #[derive(Deserialize, Debug)] #[serde(deny_unknown_fields)] pub struct TenantLoadRequest { @@ -278,31 +271,26 @@ pub struct LocationConfig { pub tenant_conf: TenantConfig, } -#[serde_as] #[derive(Serialize, Deserialize)] #[serde(transparent)] -pub struct TenantCreateResponse(#[serde_as(as = "DisplayFromStr")] pub TenantId); +pub struct TenantCreateResponse(pub TenantId); #[derive(Serialize)] pub struct StatusResponse { pub id: NodeId, } -#[serde_as] #[derive(Serialize, Deserialize, Debug)] #[serde(deny_unknown_fields)] pub struct TenantLocationConfigRequest { - #[serde_as(as = "DisplayFromStr")] pub tenant_id: TenantId, #[serde(flatten)] pub config: LocationConfig, // as we have a flattened field, we should reject all unknown fields in it } -#[serde_as] #[derive(Serialize, Deserialize, Debug)] #[serde(deny_unknown_fields)] pub struct TenantConfigRequest { - #[serde_as(as = "DisplayFromStr")] pub tenant_id: TenantId, #[serde(flatten)] pub config: TenantConfig, // as we have a flattened field, we should reject all unknown fields in it @@ -374,10 +362,8 @@ pub enum TenantAttachmentStatus { Failed { reason: String }, } -#[serde_as] #[derive(Serialize, Deserialize, Clone)] pub struct TenantInfo { - #[serde_as(as = "DisplayFromStr")] pub id: TenantId, // NB: intentionally not part of OpenAPI, we don't want to commit to a specific set of TenantState's pub state: TenantState, @@ -388,33 +374,22 @@ pub struct TenantInfo { } /// This represents the output of the "timeline_detail" and "timeline_list" API calls. -#[serde_as] #[derive(Debug, Serialize, Deserialize, Clone)] pub struct TimelineInfo { - #[serde_as(as = "DisplayFromStr")] pub tenant_id: TenantId, - #[serde_as(as = "DisplayFromStr")] pub timeline_id: TimelineId, - #[serde_as(as = "Option")] pub ancestor_timeline_id: Option, - #[serde_as(as = "Option")] pub ancestor_lsn: Option, - #[serde_as(as = "DisplayFromStr")] pub last_record_lsn: Lsn, - #[serde_as(as = "Option")] pub prev_record_lsn: Option, - #[serde_as(as = "DisplayFromStr")] pub latest_gc_cutoff_lsn: Lsn, - #[serde_as(as = "DisplayFromStr")] pub disk_consistent_lsn: Lsn, /// The LSN that we have succesfully uploaded to remote storage - #[serde_as(as = "DisplayFromStr")] pub remote_consistent_lsn: Lsn, /// The LSN that we are advertizing to safekeepers - #[serde_as(as = "DisplayFromStr")] pub remote_consistent_lsn_visible: Lsn, pub current_logical_size: Option, // is None when timeline is Unloaded @@ -426,7 +401,6 @@ pub struct TimelineInfo { pub timeline_dir_layer_file_size_sum: Option, pub wal_source_connstr: Option, - #[serde_as(as = "Option")] pub last_received_msg_lsn: Option, /// the timestamp (in microseconds) of the last received message pub last_received_msg_ts: Option, @@ -523,23 +497,13 @@ pub struct LayerAccessStats { pub residence_events_history: HistoryBufferWithDropCounter, } -#[serde_as] #[derive(Debug, Clone, Serialize)] #[serde(tag = "kind")] pub enum InMemoryLayerInfo { - Open { - #[serde_as(as = "DisplayFromStr")] - lsn_start: Lsn, - }, - Frozen { - #[serde_as(as = "DisplayFromStr")] - lsn_start: Lsn, - #[serde_as(as = "DisplayFromStr")] - lsn_end: Lsn, - }, + Open { lsn_start: Lsn }, + Frozen { lsn_start: Lsn, lsn_end: Lsn }, } -#[serde_as] #[derive(Debug, Clone, Serialize)] #[serde(tag = "kind")] pub enum HistoricLayerInfo { @@ -547,9 +511,7 @@ pub enum HistoricLayerInfo { layer_file_name: String, layer_file_size: u64, - #[serde_as(as = "DisplayFromStr")] lsn_start: Lsn, - #[serde_as(as = "DisplayFromStr")] lsn_end: Lsn, remote: bool, access_stats: LayerAccessStats, @@ -558,7 +520,6 @@ pub enum HistoricLayerInfo { layer_file_name: String, layer_file_size: u64, - #[serde_as(as = "DisplayFromStr")] lsn_start: Lsn, remote: bool, access_stats: LayerAccessStats, diff --git a/libs/safekeeper_api/src/models.rs b/libs/safekeeper_api/src/models.rs index eaea266f321c..786712deb1c5 100644 --- a/libs/safekeeper_api/src/models.rs +++ b/libs/safekeeper_api/src/models.rs @@ -1,23 +1,18 @@ use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DisplayFromStr}; use utils::{ id::{NodeId, TenantId, TimelineId}, lsn::Lsn, }; -#[serde_as] #[derive(Serialize, Deserialize)] pub struct TimelineCreateRequest { - #[serde_as(as = "DisplayFromStr")] pub tenant_id: TenantId, - #[serde_as(as = "DisplayFromStr")] pub timeline_id: TimelineId, pub peer_ids: Option>, pub pg_version: u32, pub system_id: Option, pub wal_seg_size: Option, - #[serde_as(as = "DisplayFromStr")] pub commit_lsn: Lsn, // If not passed, it is assigned to the beginning of commit_lsn segment. pub local_start_lsn: Option, @@ -28,7 +23,6 @@ fn lsn_invalid() -> Lsn { } /// Data about safekeeper's timeline, mirrors broker.proto. -#[serde_as] #[derive(Debug, Clone, Deserialize, Serialize)] pub struct SkTimelineInfo { /// Term. @@ -36,25 +30,19 @@ pub struct SkTimelineInfo { /// Term of the last entry. pub last_log_term: Option, /// LSN of the last record. - #[serde_as(as = "DisplayFromStr")] #[serde(default = "lsn_invalid")] pub flush_lsn: Lsn, /// Up to which LSN safekeeper regards its WAL as committed. - #[serde_as(as = "DisplayFromStr")] #[serde(default = "lsn_invalid")] pub commit_lsn: Lsn, /// LSN up to which safekeeper has backed WAL. - #[serde_as(as = "DisplayFromStr")] #[serde(default = "lsn_invalid")] pub backup_lsn: Lsn, /// LSN of last checkpoint uploaded by pageserver. - #[serde_as(as = "DisplayFromStr")] #[serde(default = "lsn_invalid")] pub remote_consistent_lsn: Lsn, - #[serde_as(as = "DisplayFromStr")] #[serde(default = "lsn_invalid")] pub peer_horizon_lsn: Lsn, - #[serde_as(as = "DisplayFromStr")] #[serde(default = "lsn_invalid")] pub local_start_lsn: Lsn, /// A connection string to use for WAL receiving. diff --git a/libs/utils/src/auth.rs b/libs/utils/src/auth.rs index 54b90fa07079..37299e4e7fb9 100644 --- a/libs/utils/src/auth.rs +++ b/libs/utils/src/auth.rs @@ -9,7 +9,6 @@ use jsonwebtoken::{ decode, encode, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation, }; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DisplayFromStr}; use crate::id::TenantId; @@ -32,11 +31,9 @@ pub enum Scope { } /// JWT payload. See docs/authentication.md for the format -#[serde_as] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct Claims { #[serde(default)] - #[serde_as(as = "Option")] pub tenant_id: Option, pub scope: Scope, } diff --git a/libs/utils/src/pageserver_feedback.rs b/libs/utils/src/pageserver_feedback.rs index a3b53201d305..c9fbdde92847 100644 --- a/libs/utils/src/pageserver_feedback.rs +++ b/libs/utils/src/pageserver_feedback.rs @@ -3,7 +3,6 @@ use std::time::{Duration, SystemTime}; use bytes::{Buf, BufMut, Bytes, BytesMut}; use pq_proto::{read_cstr, PG_EPOCH}; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DisplayFromStr}; use tracing::{trace, warn}; use crate::lsn::Lsn; @@ -15,21 +14,17 @@ use crate::lsn::Lsn; /// /// serde Serialize is used only for human readable dump to json (e.g. in /// safekeepers debug_dump). -#[serde_as] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct PageserverFeedback { /// Last known size of the timeline. Used to enforce timeline size limit. pub current_timeline_size: u64, /// LSN last received and ingested by the pageserver. Controls backpressure. - #[serde_as(as = "DisplayFromStr")] pub last_received_lsn: Lsn, /// LSN up to which data is persisted by the pageserver to its local disc. /// Controls backpressure. - #[serde_as(as = "DisplayFromStr")] pub disk_consistent_lsn: Lsn, /// LSN up to which data is persisted by the pageserver on s3; safekeepers /// consider WAL before it can be removed. - #[serde_as(as = "DisplayFromStr")] pub remote_consistent_lsn: Lsn, // Serialize with RFC3339 format. #[serde(with = "serde_systemtime")] diff --git a/pageserver/src/consumption_metrics/metrics.rs b/pageserver/src/consumption_metrics/metrics.rs index 652dd98683d6..c22f218976fe 100644 --- a/pageserver/src/consumption_metrics/metrics.rs +++ b/pageserver/src/consumption_metrics/metrics.rs @@ -3,7 +3,6 @@ use anyhow::Context; use chrono::{DateTime, Utc}; use consumption_metrics::EventType; use futures::stream::StreamExt; -use serde_with::serde_as; use std::{sync::Arc, time::SystemTime}; use utils::{ id::{TenantId, TimelineId}, @@ -42,13 +41,10 @@ pub(super) enum Name { /// /// This is a denormalization done at the MetricsKey const methods; these should not be constructed /// elsewhere. -#[serde_with::serde_as] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] pub(crate) struct MetricsKey { - #[serde_as(as = "serde_with::DisplayFromStr")] pub(super) tenant_id: TenantId, - #[serde_as(as = "Option")] #[serde(skip_serializing_if = "Option::is_none")] pub(super) timeline_id: Option, diff --git a/pageserver/src/consumption_metrics/upload.rs b/pageserver/src/consumption_metrics/upload.rs index d69d43a2a8c8..322ed95cc875 100644 --- a/pageserver/src/consumption_metrics/upload.rs +++ b/pageserver/src/consumption_metrics/upload.rs @@ -1,5 +1,4 @@ use consumption_metrics::{Event, EventChunk, IdempotencyKey, CHUNK_SIZE}; -use serde_with::serde_as; use tokio_util::sync::CancellationToken; use tracing::Instrument; @@ -7,12 +6,9 @@ use super::{metrics::Name, Cache, MetricsKey, RawMetric}; use utils::id::{TenantId, TimelineId}; /// How the metrics from pageserver are identified. -#[serde_with::serde_as] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq)] struct Ids { - #[serde_as(as = "serde_with::DisplayFromStr")] pub(super) tenant_id: TenantId, - #[serde_as(as = "Option")] #[serde(skip_serializing_if = "Option::is_none")] pub(super) timeline_id: Option, } diff --git a/pageserver/src/deletion_queue.rs b/pageserver/src/deletion_queue.rs index 22efa23f105b..83f5770924e5 100644 --- a/pageserver/src/deletion_queue.rs +++ b/pageserver/src/deletion_queue.rs @@ -17,7 +17,6 @@ use hex::FromHex; use remote_storage::{GenericRemoteStorage, RemotePath}; use serde::Deserialize; use serde::Serialize; -use serde_with::serde_as; use thiserror::Error; use tokio; use tokio_util::sync::CancellationToken; @@ -214,7 +213,6 @@ where /// during recovery as startup. const TEMP_SUFFIX: &str = "tmp"; -#[serde_as] #[derive(Debug, Serialize, Deserialize)] struct DeletionList { /// Serialization version, for future use @@ -243,7 +241,6 @@ struct DeletionList { validated: bool, } -#[serde_as] #[derive(Debug, Serialize, Deserialize)] struct DeletionHeader { /// Serialization version, for future use diff --git a/pageserver/src/http/routes.rs b/pageserver/src/http/routes.rs index 2c46d733d678..ec243098703c 100644 --- a/pageserver/src/http/routes.rs +++ b/pageserver/src/http/routes.rs @@ -811,10 +811,8 @@ async fn tenant_size_handler( } /// The type resides in the pageserver not to expose `ModelInputs`. - #[serde_with::serde_as] #[derive(serde::Serialize)] struct TenantHistorySize { - #[serde_as(as = "serde_with::DisplayFromStr")] id: TenantId, /// Size is a mixture of WAL and logical size, so the unit is bytes. /// diff --git a/pageserver/src/tenant/remote_timeline_client/index.rs b/pageserver/src/tenant/remote_timeline_client/index.rs index 7cf963ca9dd3..0f2d747beede 100644 --- a/pageserver/src/tenant/remote_timeline_client/index.rs +++ b/pageserver/src/tenant/remote_timeline_client/index.rs @@ -6,7 +6,6 @@ use std::collections::HashMap; use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DisplayFromStr}; use utils::bin_ser::SerializeError; use crate::tenant::metadata::TimelineMetadata; @@ -58,7 +57,6 @@ impl LayerFileMetadata { /// /// This type needs to be backwards and forwards compatible. When changing the fields, /// remember to add a test case for the changed version. -#[serde_as] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct IndexPart { /// Debugging aid describing the version of this type. @@ -78,7 +76,6 @@ pub struct IndexPart { // 'disk_consistent_lsn' is a copy of the 'disk_consistent_lsn' in the metadata. // It's duplicated for convenience when reading the serialized structure, but is // private because internally we would read from metadata instead. - #[serde_as(as = "DisplayFromStr")] disk_consistent_lsn: Lsn, #[serde(rename = "metadata_bytes")] diff --git a/pageserver/src/tenant/size.rs b/pageserver/src/tenant/size.rs index e737d3f59c0b..5e8ee2b99e0c 100644 --- a/pageserver/src/tenant/size.rs +++ b/pageserver/src/tenant/size.rs @@ -29,7 +29,6 @@ use tenant_size_model::{Segment, StorageModel}; /// needs. We will convert this into a StorageModel when it's time to perform /// the calculation. /// -#[serde_with::serde_as] #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct ModelInputs { pub segments: Vec, @@ -37,11 +36,9 @@ pub struct ModelInputs { } /// A [`Segment`], with some extra information for display purposes -#[serde_with::serde_as] #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct SegmentMeta { pub segment: Segment, - #[serde_as(as = "serde_with::DisplayFromStr")] pub timeline_id: TimelineId, pub kind: LsnKind, } @@ -77,32 +74,22 @@ pub enum LsnKind { /// Collect all relevant LSNs to the inputs. These will only be helpful in the serialized form as /// part of [`ModelInputs`] from the HTTP api, explaining the inputs. -#[serde_with::serde_as] #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct TimelineInputs { - #[serde_as(as = "serde_with::DisplayFromStr")] pub timeline_id: TimelineId, - #[serde_as(as = "Option")] pub ancestor_id: Option, - #[serde_as(as = "serde_with::DisplayFromStr")] ancestor_lsn: Lsn, - #[serde_as(as = "serde_with::DisplayFromStr")] last_record: Lsn, - #[serde_as(as = "serde_with::DisplayFromStr")] latest_gc_cutoff: Lsn, - #[serde_as(as = "serde_with::DisplayFromStr")] horizon_cutoff: Lsn, - #[serde_as(as = "serde_with::DisplayFromStr")] pitr_cutoff: Lsn, /// Cutoff point based on GC settings - #[serde_as(as = "serde_with::DisplayFromStr")] next_gc_cutoff: Lsn, /// Cutoff point calculated from the user-supplied 'max_retention_period' - #[serde_as(as = "Option")] retention_param_cutoff: Option, } diff --git a/pageserver/src/tenant/timeline.rs b/pageserver/src/tenant/timeline.rs index 4608683c4a7c..baa97b6cf9cc 100644 --- a/pageserver/src/tenant/timeline.rs +++ b/pageserver/src/tenant/timeline.rs @@ -2936,13 +2936,10 @@ struct CompactLevel0Phase1StatsBuilder { new_deltas_size: Option, } -#[serde_as] #[derive(serde::Serialize)] struct CompactLevel0Phase1Stats { version: u64, - #[serde_as(as = "serde_with::DisplayFromStr")] tenant_id: TenantId, - #[serde_as(as = "serde_with::DisplayFromStr")] timeline_id: TimelineId, read_lock_acquisition_micros: RecordedDuration, read_lock_held_spawn_blocking_startup_micros: RecordedDuration, diff --git a/s3_scrubber/src/cloud_admin_api.rs b/s3_scrubber/src/cloud_admin_api.rs index 1b28dc4dff0b..298eeea563fa 100644 --- a/s3_scrubber/src/cloud_admin_api.rs +++ b/s3_scrubber/src/cloud_admin_api.rs @@ -153,7 +153,6 @@ pub struct ProjectData { pub maintenance_set: Option, } -#[serde_with::serde_as] #[derive(Debug, serde::Deserialize)] pub struct BranchData { pub id: BranchId, @@ -161,12 +160,10 @@ pub struct BranchData { pub updated_at: DateTime, pub name: String, pub project_id: ProjectId, - #[serde_as(as = "serde_with::DisplayFromStr")] pub timeline_id: TimelineId, #[serde(default)] pub parent_id: Option, #[serde(default)] - #[serde_as(as = "Option")] pub parent_lsn: Option, pub default: bool, pub deleted: bool, diff --git a/safekeeper/src/debug_dump.rs b/safekeeper/src/debug_dump.rs index ee9d7118c6aa..26eb54fd1593 100644 --- a/safekeeper/src/debug_dump.rs +++ b/safekeeper/src/debug_dump.rs @@ -13,7 +13,6 @@ use postgres_ffi::XLogSegNo; use serde::Deserialize; use serde::Serialize; -use serde_with::{serde_as, DisplayFromStr}; use utils::id::NodeId; use utils::id::TenantTimelineId; use utils::id::{TenantId, TimelineId}; @@ -74,12 +73,9 @@ pub struct Config { pub wal_backup_enabled: bool, } -#[serde_as] #[derive(Debug, Serialize, Deserialize)] pub struct Timeline { - #[serde_as(as = "DisplayFromStr")] pub tenant_id: TenantId, - #[serde_as(as = "DisplayFromStr")] pub timeline_id: TimelineId, pub control_file: Option, pub memory: Option, diff --git a/safekeeper/src/http/routes.rs b/safekeeper/src/http/routes.rs index 940ac82df6ad..620ce0fe4406 100644 --- a/safekeeper/src/http/routes.rs +++ b/safekeeper/src/http/routes.rs @@ -4,7 +4,6 @@ use once_cell::sync::Lazy; use postgres_ffi::WAL_SEGMENT_SIZE; use safekeeper_api::models::SkTimelineInfo; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DisplayFromStr}; use std::collections::{HashMap, HashSet}; use std::fmt; use std::str::FromStr; @@ -66,7 +65,6 @@ fn get_conf(request: &Request) -> &SafeKeeperConf { #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct TermSwitchApiEntry { pub term: Term, - #[serde_as(as = "DisplayFromStr")] pub lsn: Lsn, } @@ -88,28 +86,18 @@ pub struct AcceptorStateStatus { } /// Info about timeline on safekeeper ready for reporting. -#[serde_as] #[derive(Debug, Serialize, Deserialize)] pub struct TimelineStatus { - #[serde_as(as = "DisplayFromStr")] pub tenant_id: TenantId, - #[serde_as(as = "DisplayFromStr")] pub timeline_id: TimelineId, pub acceptor_state: AcceptorStateStatus, pub pg_info: ServerInfo, - #[serde_as(as = "DisplayFromStr")] pub flush_lsn: Lsn, - #[serde_as(as = "DisplayFromStr")] pub timeline_start_lsn: Lsn, - #[serde_as(as = "DisplayFromStr")] pub local_start_lsn: Lsn, - #[serde_as(as = "DisplayFromStr")] pub commit_lsn: Lsn, - #[serde_as(as = "DisplayFromStr")] pub backup_lsn: Lsn, - #[serde_as(as = "DisplayFromStr")] pub peer_horizon_lsn: Lsn, - #[serde_as(as = "DisplayFromStr")] pub remote_consistent_lsn: Lsn, pub peers: Vec, pub walsenders: Vec, diff --git a/safekeeper/src/pull_timeline.rs b/safekeeper/src/pull_timeline.rs index 1343bba5ccb5..921032bba160 100644 --- a/safekeeper/src/pull_timeline.rs +++ b/safekeeper/src/pull_timeline.rs @@ -5,8 +5,6 @@ use tokio::io::AsyncWriteExt; use tracing::info; use utils::id::{TenantId, TenantTimelineId, TimelineId}; -use serde_with::{serde_as, DisplayFromStr}; - use crate::{ control_file, debug_dump, http::routes::TimelineStatus, @@ -15,12 +13,9 @@ use crate::{ }; /// Info about timeline on safekeeper ready for reporting. -#[serde_as] #[derive(Debug, Serialize, Deserialize)] pub struct Request { - #[serde_as(as = "DisplayFromStr")] pub tenant_id: TenantId, - #[serde_as(as = "DisplayFromStr")] pub timeline_id: TimelineId, pub http_hosts: Vec, } diff --git a/safekeeper/src/send_wal.rs b/safekeeper/src/send_wal.rs index cd765dcbce23..44f14f8c7e84 100644 --- a/safekeeper/src/send_wal.rs +++ b/safekeeper/src/send_wal.rs @@ -16,7 +16,6 @@ use postgres_ffi::get_current_timestamp; use postgres_ffi::{TimestampTz, MAX_SEND_SIZE}; use pq_proto::{BeMessage, WalSndKeepAlive, XLogDataBody}; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DisplayFromStr}; use tokio::io::{AsyncRead, AsyncWrite}; use utils::id::TenantTimelineId; use utils::lsn::AtomicLsn; @@ -313,10 +312,8 @@ impl WalSendersShared { } // Serialized is used only for pretty printing in json. -#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WalSenderState { - #[serde_as(as = "DisplayFromStr")] ttid: TenantTimelineId, addr: SocketAddr, conn_id: ConnectionId, diff --git a/safekeeper/src/timeline.rs b/safekeeper/src/timeline.rs index 96d844fa7afb..8d7aadbdc472 100644 --- a/safekeeper/src/timeline.rs +++ b/safekeeper/src/timeline.rs @@ -5,10 +5,8 @@ use anyhow::{anyhow, bail, Result}; use camino::Utf8PathBuf; use postgres_ffi::XLogSegNo; use serde::{Deserialize, Serialize}; -use serde_with::serde_as; use tokio::fs; -use serde_with::DisplayFromStr; use std::cmp::max; use std::sync::Arc; use std::time::Duration; @@ -42,7 +40,6 @@ use crate::SafeKeeperConf; use crate::{debug_dump, wal_storage}; /// Things safekeeper should know about timeline state on peers. -#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PeerInfo { pub sk_id: NodeId, @@ -56,7 +53,6 @@ pub struct PeerInfo { pub commit_lsn: Lsn, /// Since which LSN safekeeper has WAL. TODO: remove this once we fill new /// sk since backup_lsn. - #[serde_as(as = "DisplayFromStr")] pub local_start_lsn: Lsn, /// When info was received. Serde annotations are not very useful but make /// the code compile -- we don't rely on this field externally. From 3bd6c6036905b860db94d9ece16a7f6d9672c387 Mon Sep 17 00:00:00 2001 From: Joonas Koivunen Date: Mon, 9 Oct 2023 16:41:27 +0000 Subject: [PATCH 06/23] lsn: add serde_as_u64 mod --- libs/utils/src/lsn.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/libs/utils/src/lsn.rs b/libs/utils/src/lsn.rs index 5d861af5e437..9fc696fdf06e 100644 --- a/libs/utils/src/lsn.rs +++ b/libs/utils/src/lsn.rs @@ -67,6 +67,49 @@ impl<'de> Deserialize<'de> for Lsn { } } +/// Allows (de)serialization of an `Lsn` always as `u64`. +/// +/// ### Example +/// +/// ```rust +/// # use serde::{Serialize, Deserialize}; +/// use utils::lsn::Lsn; +/// +/// #[derive(Partialeq, Serialize, Deserialize)] +/// struct Foo { +/// #[serde(with = "utils::lsn::as_u64")] +/// always_u64: Lsn, +/// } +/// +/// let orig = Foo { always_u64: Lsn(1234) }; +/// +/// let res = serde_json::to_string(&).unwrap(); +/// assert_eq!(res, r#"{"always_u64": 1234}"#); +/// +/// let foo = serde_json::from_str::(&res).unwrap(); +/// assert_eq!(res, orig); +/// ``` +/// +pub mod serde_as_u64 { + use super::Lsn; + + /// Serializes the Lsn as u64 disregarding the human readability of the format. + /// + /// Meant to be used via `#[serde(with = "...")]` or `#[serde(serialize_with = "...")]`. + pub fn serialize(lsn: &Lsn, serializer: S) -> Result { + use serde::Serialize; + lsn.0.serialize(serializer) + } + + /// Deserializes the Lsn as u64 disregarding the human readability of the format. + /// + /// Meant to be used via `#[serde(with = "...")]` or `#[serde(deserialize_with = "...")]`. + pub fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { + use serde::Deserialize; + u64::deserialize(deserializer).map(Lsn) + } +} + /// We tried to parse an LSN from a string, but failed #[derive(Debug, PartialEq, Eq, thiserror::Error)] #[error("LsnParseError")] From e07ee53b4d0719a3c14b6518182d9c3256dd753b Mon Sep 17 00:00:00 2001 From: Joonas Koivunen Date: Mon, 9 Oct 2023 16:41:45 +0000 Subject: [PATCH 07/23] safekeeper: retain u64 in json serialization --- safekeeper/src/json_ctrl.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/safekeeper/src/json_ctrl.rs b/safekeeper/src/json_ctrl.rs index 2ad2ca7706d0..303bdd67fef8 100644 --- a/safekeeper/src/json_ctrl.rs +++ b/safekeeper/src/json_ctrl.rs @@ -44,8 +44,11 @@ pub struct AppendLogicalMessage { // fields from AppendRequestHeader pub term: Term, + #[serde(with = "utils::lsn::serde_as_u64")] pub epoch_start_lsn: Lsn, + #[serde(with = "utils::lsn::serde_as_u64")] pub begin_lsn: Lsn, + #[serde(with = "utils::lsn::serde_as_u64")] pub truncate_lsn: Lsn, pub pg_version: u32, } From 234a0f8d87bfd2ed2e45a80de81c56d8d03eac8a Mon Sep 17 00:00:00 2001 From: Joonas Koivunen Date: Mon, 9 Oct 2023 08:30:06 +0000 Subject: [PATCH 08/23] chore: needless mut --- libs/utils/src/id.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/utils/src/id.rs b/libs/utils/src/id.rs index 40e3a00ed445..9b57fd0f6c01 100644 --- a/libs/utils/src/id.rs +++ b/libs/utils/src/id.rs @@ -54,7 +54,7 @@ impl<'de> Deserialize<'de> for Id { ) } - fn visit_seq(self, mut seq: A) -> Result + fn visit_seq(self, seq: A) -> Result where A: serde::de::SeqAccess<'de>, { From d6c1f95cb994c4ad383c136167baddee0643f9fe Mon Sep 17 00:00:00 2001 From: Joonas Koivunen Date: Thu, 12 Oct 2023 19:26:28 +0000 Subject: [PATCH 09/23] fixup: add serde_as_u64 mod --- libs/utils/src/lsn.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/utils/src/lsn.rs b/libs/utils/src/lsn.rs index 9fc696fdf06e..1e017a7732d9 100644 --- a/libs/utils/src/lsn.rs +++ b/libs/utils/src/lsn.rs @@ -75,19 +75,19 @@ impl<'de> Deserialize<'de> for Lsn { /// # use serde::{Serialize, Deserialize}; /// use utils::lsn::Lsn; /// -/// #[derive(Partialeq, Serialize, Deserialize)] +/// #[derive(PartialEq, Serialize, Deserialize, Debug)] /// struct Foo { -/// #[serde(with = "utils::lsn::as_u64")] +/// #[serde(with = "utils::lsn::serde_as_u64")] /// always_u64: Lsn, /// } /// /// let orig = Foo { always_u64: Lsn(1234) }; /// -/// let res = serde_json::to_string(&).unwrap(); -/// assert_eq!(res, r#"{"always_u64": 1234}"#); +/// let res = serde_json::to_string(&orig).unwrap(); +/// assert_eq!(res, r#"{"always_u64":1234}"#); /// /// let foo = serde_json::from_str::(&res).unwrap(); -/// assert_eq!(res, orig); +/// assert_eq!(foo, orig); /// ``` /// pub mod serde_as_u64 { From 4904a2e78c7df815d215dd9ae35c033db6c4ca39 Mon Sep 17 00:00:00 2001 From: duguorong Date: Fri, 13 Oct 2023 10:30:36 +0800 Subject: [PATCH 10/23] chore: improve the "Id" bincode tests by macro --- libs/utils/src/id.rs | 46 ++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/libs/utils/src/id.rs b/libs/utils/src/id.rs index 9b57fd0f6c01..fc549565df90 100644 --- a/libs/utils/src/id.rs +++ b/libs/utils/src/id.rs @@ -433,51 +433,43 @@ mod tests { assert_eq!(Id::deserialize(&mut deserializer).unwrap(), original_id); } + macro_rules! roundtrip_type { + ($type:ty, $expected_bytes:expr) => {{ + let expected_bytes: [u8; 16] = $expected_bytes; + let original_id = <$type>::from(expected_bytes); + + let ser_bytes = original_id.ser().unwrap(); + assert_eq!(ser_bytes, expected_bytes); + + let des_id = <$type>::des(&ser_bytes).unwrap(); + assert_eq!(des_id, original_id); + }}; + } + #[test] fn test_id_bincode_serde() { - let id_arr = [ + let expected_bytes = [ 173, 80, 132, 115, 129, 226, 72, 254, 170, 201, 135, 108, 199, 26, 228, 24, ]; - let original_id = Id(id_arr); - let expected_bytes = id_arr.to_vec(); - - let ser_bytes = original_id.ser().unwrap(); - assert!(ser_bytes == expected_bytes); - - let des_id = Id::des(&ser_bytes).unwrap(); - assert!(des_id == original_id); + roundtrip_type!(Id, expected_bytes); } #[test] fn test_tenant_id_bincode_serde() { - let id_arr = [ + let expected_bytes = [ 173, 80, 132, 115, 129, 226, 72, 254, 170, 201, 135, 108, 199, 26, 228, 24, ]; - let original_tenant_id = TenantId(Id(id_arr)); - let expected_bytes = id_arr.to_vec(); - - let ser_bytes = original_tenant_id.ser().unwrap(); - assert_eq!(ser_bytes, expected_bytes); - - let des_tenant_id = TenantId::des(&ser_bytes).unwrap(); - assert_eq!(des_tenant_id, original_tenant_id); + roundtrip_type!(TenantId, expected_bytes); } #[test] fn test_timeline_id_bincode_serde() { - let id_arr = [ + let expected_bytes = [ 173, 80, 132, 115, 129, 226, 72, 254, 170, 201, 135, 108, 199, 26, 228, 24, ]; - let original_timeline_id = TimelineId(Id(id_arr)); - let expected_bytes = id_arr.to_vec(); - - let ser_bytes = original_timeline_id.ser().unwrap(); - assert_eq!(ser_bytes, expected_bytes); - - let des_timeline_id = TimelineId::des(&expected_bytes).unwrap(); - assert_eq!(des_timeline_id, original_timeline_id); + roundtrip_type!(TimelineId, expected_bytes); } } From b78ce894c1dc054bfe48080c1b1ea4bf0ece9f90 Mon Sep 17 00:00:00 2001 From: duguorong Date: Fri, 13 Oct 2023 10:31:58 +0800 Subject: [PATCH 11/23] chore: clean up comments --- libs/utils/src/id.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/libs/utils/src/id.rs b/libs/utils/src/id.rs index fc549565df90..00aaaa2fdcc1 100644 --- a/libs/utils/src/id.rs +++ b/libs/utils/src/id.rs @@ -18,9 +18,6 @@ pub enum IdError { /// /// NOTE: It (de)serializes as an array of hex bytes, so the string representation would look /// like `[173,80,132,115,129,226,72,254,170,201,135,108,199,26,228,24]`. -/// -/// Use `#[serde_as(as = "DisplayFromStr")]` to (de)serialize it as hex string instead: `ad50847381e248feaac9876cc71ae418`. -/// Check the `serde_with::serde_as` documentation for options for more complex types. #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] struct Id([u8; 16]); From 183f3fe3fafe1928a784fcb452b8f5de7d644189 Mon Sep 17 00:00:00 2001 From: duguorong Date: Fri, 13 Oct 2023 10:34:51 +0800 Subject: [PATCH 12/23] fix: improve the "expecting" messages of ser/de --- libs/utils/src/id.rs | 24 +++++++++++++++++------- libs/utils/src/lsn.rs | 19 ++++++++++++++----- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/libs/utils/src/id.rs b/libs/utils/src/id.rs index 00aaaa2fdcc1..eef6a358e64d 100644 --- a/libs/utils/src/id.rs +++ b/libs/utils/src/id.rs @@ -39,16 +39,19 @@ impl<'de> Deserialize<'de> for Id { where D: serde::Deserializer<'de>, { - struct IdVisitor; + struct IdVisitor { + is_human_readable_deserializer: bool, + } impl<'de> Visitor<'de> for IdVisitor { type Value = Id; - // TODO: improve the "expecting" description fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str( - "value in either sequence form([u8; 16]) or serde_json form(hex string)", - ) + if self.is_human_readable_deserializer { + formatter.write_str("value in form of hex string") + } else { + formatter.write_str("value in form of integer array([u8; 16])") + } } fn visit_seq(self, seq: A) -> Result @@ -69,9 +72,16 @@ impl<'de> Deserialize<'de> for Id { } if deserializer.is_human_readable() { - deserializer.deserialize_str(IdVisitor) + deserializer.deserialize_str(IdVisitor { + is_human_readable_deserializer: true, + }) } else { - deserializer.deserialize_tuple(16, IdVisitor) + deserializer.deserialize_tuple( + 16, + IdVisitor { + is_human_readable_deserializer: false, + }, + ) } } } diff --git a/libs/utils/src/lsn.rs b/libs/utils/src/lsn.rs index 1e017a7732d9..63b76e0c9b1b 100644 --- a/libs/utils/src/lsn.rs +++ b/libs/utils/src/lsn.rs @@ -34,14 +34,23 @@ impl<'de> Deserialize<'de> for Lsn { where D: serde::Deserializer<'de>, { - struct LsnVisitor; + struct LsnVisitor { + is_human_readable_deserializer: bool, + } impl<'de> Visitor<'de> for LsnVisitor { type Value = Lsn; - // TODO: improve the "expecting" description fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("u64 value in either bincode form(u64) or serde_json form({upper_u32_hex}/{lower_u32_hex})") + if self.is_human_readable_deserializer { + formatter.write_str( + "value in form of hex string({upper_u32_hex}/{lower_u32_hex}) representing u64 integer", + ) + } else { + formatter.write_str( + "value in form of integer(u64)", + ) + } } fn visit_u64(self, v: u64) -> Result @@ -60,9 +69,9 @@ impl<'de> Deserialize<'de> for Lsn { } if deserializer.is_human_readable() { - deserializer.deserialize_str(LsnVisitor) + deserializer.deserialize_str(LsnVisitor { is_human_readable_deserializer: true }) } else { - deserializer.deserialize_u64(LsnVisitor) + deserializer.deserialize_u64(LsnVisitor { is_human_readable_deserializer: false }) } } } From 4eca0f9062fd158d34b1d4e19de40436f8f4747f Mon Sep 17 00:00:00 2001 From: duguorong Date: Fri, 13 Oct 2023 11:13:27 +0800 Subject: [PATCH 13/23] refactor: remove the attr "#[serde(with="hex")]" --- safekeeper/src/control_file_upgrade.rs | 6 ------ safekeeper/src/safekeeper.rs | 4 ---- 2 files changed, 10 deletions(-) diff --git a/safekeeper/src/control_file_upgrade.rs b/safekeeper/src/control_file_upgrade.rs index a7f17df797f7..ecd419a6a026 100644 --- a/safekeeper/src/control_file_upgrade.rs +++ b/safekeeper/src/control_file_upgrade.rs @@ -74,9 +74,7 @@ pub struct ServerInfoV3 { /// Postgres server version pub pg_version: u32, pub system_id: SystemId, - #[serde(with = "hex")] pub tenant_id: TenantId, - #[serde(with = "hex")] pub timeline_id: TimelineId, pub wal_seg_size: u32, } @@ -89,7 +87,6 @@ pub struct SafeKeeperStateV3 { pub server: ServerInfoV3, /// Unique id of the last *elected* proposer we dealt with. Not needed /// for correctness, exists for monitoring purposes. - #[serde(with = "hex")] pub proposer_uuid: PgUuid, /// part of WAL acknowledged by quorum and available locally pub commit_lsn: Lsn, @@ -103,9 +100,7 @@ pub struct SafeKeeperStateV3 { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SafeKeeperStateV4 { - #[serde(with = "hex")] pub tenant_id: TenantId, - #[serde(with = "hex")] pub timeline_id: TimelineId, /// persistent acceptor state pub acceptor_state: AcceptorState, @@ -113,7 +108,6 @@ pub struct SafeKeeperStateV4 { pub server: ServerInfo, /// Unique id of the last *elected* proposer we dealt with. Not needed /// for correctness, exists for monitoring purposes. - #[serde(with = "hex")] pub proposer_uuid: PgUuid, /// Part of WAL acknowledged by quorum and available locally. Always points /// to record boundary. diff --git a/safekeeper/src/safekeeper.rs b/safekeeper/src/safekeeper.rs index 1437bdb50ec8..dd7aef3552bf 100644 --- a/safekeeper/src/safekeeper.rs +++ b/safekeeper/src/safekeeper.rs @@ -239,9 +239,7 @@ pub struct PersistedPeers(pub Vec<(NodeId, PersistedPeerInfo)>); /// On disk data is prefixed by magic and format version and followed by checksum. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SafeKeeperState { - #[serde(with = "hex")] pub tenant_id: TenantId, - #[serde(with = "hex")] pub timeline_id: TimelineId, /// persistent acceptor state pub acceptor_state: AcceptorState, @@ -249,7 +247,6 @@ pub struct SafeKeeperState { pub server: ServerInfo, /// Unique id of the last *elected* proposer we dealt with. Not needed /// for correctness, exists for monitoring purposes. - #[serde(with = "hex")] pub proposer_uuid: PgUuid, /// Since which LSN this timeline generally starts. Safekeeper might have /// joined later. @@ -288,7 +285,6 @@ pub struct SafekeeperMemState { pub commit_lsn: Lsn, pub backup_lsn: Lsn, pub peer_horizon_lsn: Lsn, - #[serde(with = "hex")] pub proposer_uuid: PgUuid, } From cbfc4b4c7372635d3524a6a709781921ca33f994 Mon Sep 17 00:00:00 2001 From: duguorong Date: Mon, 23 Oct 2023 15:35:25 +0800 Subject: [PATCH 14/23] fix: resolve the merge conflict --- safekeeper/src/http/routes.rs | 1 - safekeeper/src/timeline.rs | 2 -- 2 files changed, 3 deletions(-) diff --git a/safekeeper/src/http/routes.rs b/safekeeper/src/http/routes.rs index 620ce0fe4406..c5d5839c6f31 100644 --- a/safekeeper/src/http/routes.rs +++ b/safekeeper/src/http/routes.rs @@ -61,7 +61,6 @@ fn get_conf(request: &Request) -> &SafeKeeperConf { /// Same as TermLsn, but serializes LSN using display serializer /// in Postgres format, i.e. 0/FFFFFFFF. Used only for the API response. -#[serde_as] #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct TermSwitchApiEntry { pub term: Term, diff --git a/safekeeper/src/timeline.rs b/safekeeper/src/timeline.rs index 8d7aadbdc472..2ba871207ec8 100644 --- a/safekeeper/src/timeline.rs +++ b/safekeeper/src/timeline.rs @@ -47,9 +47,7 @@ pub struct PeerInfo { /// Term of the last entry. pub last_log_term: Term, /// LSN of the last record. - #[serde_as(as = "DisplayFromStr")] pub flush_lsn: Lsn, - #[serde_as(as = "DisplayFromStr")] pub commit_lsn: Lsn, /// Since which LSN safekeeper has WAL. TODO: remove this once we fill new /// sk since backup_lsn. From c3762f4e4fe221712d0d7885783ec8f760c4501f Mon Sep 17 00:00:00 2001 From: duguorong Date: Sat, 28 Oct 2023 22:01:05 +0800 Subject: [PATCH 15/23] fix: update the "from_nullable_id" util --- s3_scrubber/src/cloud_admin_api.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/s3_scrubber/src/cloud_admin_api.rs b/s3_scrubber/src/cloud_admin_api.rs index 298eeea563fa..151421c84f1e 100644 --- a/s3_scrubber/src/cloud_admin_api.rs +++ b/s3_scrubber/src/cloud_admin_api.rs @@ -5,6 +5,7 @@ use std::time::Duration; use chrono::{DateTime, Utc}; use hex::FromHex; +use pageserver::tenant::Tenant; use reqwest::{header, Client, StatusCode, Url}; use serde::Deserialize; use tokio::sync::Semaphore; @@ -118,13 +119,18 @@ fn from_nullable_id<'de, D>(deserializer: D) -> Result where D: serde::de::Deserializer<'de>, { - let id_str = String::deserialize(deserializer)?; - if id_str.is_empty() { - // This is a bogus value, but for the purposes of the scrubber all that - // matters is that it doesn't collide with any real IDs. - Ok(TenantId::from([0u8; 16])) + if deserializer.is_human_readable() { + let id_str = String::deserialize(deserializer)?; + if id_str.is_empty() { + // This is a bogus value, but for the purposes of the scrubber all that + // matters is that it doesn't collide with any real IDs. + Ok(TenantId::from([0u8; 16])) + } else { + TenantId::from_hex(&id_str).map_err(|e| serde::de::Error::custom(format!("{e}"))) + } } else { - TenantId::from_hex(&id_str).map_err(|e| serde::de::Error::custom(format!("{e}"))) + let id_arr = <[u8; 16]>::deserialize(deserializer)?; + Ok(TenantId::from(id_arr)) } } From a216d7579ee26c068d075e0aa49ea7b675ef6387 Mon Sep 17 00:00:00 2001 From: duguorong Date: Wed, 1 Nov 2023 20:16:35 +0800 Subject: [PATCH 16/23] chore: fmt + clippy --- libs/utils/src/lsn.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libs/utils/src/lsn.rs b/libs/utils/src/lsn.rs index 63b76e0c9b1b..262dcb8a8a91 100644 --- a/libs/utils/src/lsn.rs +++ b/libs/utils/src/lsn.rs @@ -47,9 +47,7 @@ impl<'de> Deserialize<'de> for Lsn { "value in form of hex string({upper_u32_hex}/{lower_u32_hex}) representing u64 integer", ) } else { - formatter.write_str( - "value in form of integer(u64)", - ) + formatter.write_str("value in form of integer(u64)") } } @@ -69,9 +67,13 @@ impl<'de> Deserialize<'de> for Lsn { } if deserializer.is_human_readable() { - deserializer.deserialize_str(LsnVisitor { is_human_readable_deserializer: true }) + deserializer.deserialize_str(LsnVisitor { + is_human_readable_deserializer: true, + }) } else { - deserializer.deserialize_u64(LsnVisitor { is_human_readable_deserializer: false }) + deserializer.deserialize_u64(LsnVisitor { + is_human_readable_deserializer: false, + }) } } } From a60d5634772f24a86321f06b88b55deb703cdd80 Mon Sep 17 00:00:00 2001 From: duguorong Date: Thu, 2 Nov 2023 21:46:48 +0800 Subject: [PATCH 17/23] fix: remove the remaining "DisplayFromStr" attr --- pageserver/src/http/routes.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/pageserver/src/http/routes.rs b/pageserver/src/http/routes.rs index ec243098703c..a6fb26b298b9 100644 --- a/pageserver/src/http/routes.rs +++ b/pageserver/src/http/routes.rs @@ -17,7 +17,6 @@ use pageserver_api::models::{ TenantLoadRequest, TenantLocationConfigRequest, }; use remote_storage::GenericRemoteStorage; -use serde_with::{serde_as, DisplayFromStr}; use tenant_size_model::{SizeResult, StorageModel}; use tokio_util::sync::CancellationToken; use tracing::*; @@ -499,10 +498,8 @@ async fn get_lsn_by_timestamp_handler( let result = timeline.find_lsn_for_timestamp(timestamp_pg, &ctx).await?; if version.unwrap_or(0) > 1 { - #[serde_as] #[derive(serde::Serialize)] struct Result { - #[serde_as(as = "DisplayFromStr")] lsn: Lsn, kind: &'static str, } From 607d351b85691b8d7a42a361596741fbc7fe3918 Mon Sep 17 00:00:00 2001 From: duguorong Date: Fri, 3 Nov 2023 04:08:22 +0800 Subject: [PATCH 18/23] fix: bring back custom attrs to safekeeper structs for compatibility --- safekeeper/src/control_file_upgrade.rs | 8 ++++++++ safekeeper/src/safekeeper.rs | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/safekeeper/src/control_file_upgrade.rs b/safekeeper/src/control_file_upgrade.rs index ecd419a6a026..d23cd07e577a 100644 --- a/safekeeper/src/control_file_upgrade.rs +++ b/safekeeper/src/control_file_upgrade.rs @@ -29,6 +29,7 @@ struct SafeKeeperStateV1 { server: ServerInfoV2, /// Unique id of the last *elected* proposer we dealt with. Not needed /// for correctness, exists for monitoring purposes. + #[serde(with = "hex")] proposer_uuid: PgUuid, /// part of WAL acknowledged by quorum and available locally commit_lsn: Lsn, @@ -45,7 +46,9 @@ pub struct ServerInfoV2 { /// Postgres server version pub pg_version: u32, pub system_id: SystemId, + #[serde(with = "hex")] pub tenant_id: TenantId, + #[serde(with = "hex")] pub timeline_id: TimelineId, pub wal_seg_size: u32, } @@ -58,6 +61,7 @@ pub struct SafeKeeperStateV2 { pub server: ServerInfoV2, /// Unique id of the last *elected* proposer we dealt with. Not needed /// for correctness, exists for monitoring purposes. + #[serde(with = "hex")] pub proposer_uuid: PgUuid, /// part of WAL acknowledged by quorum and available locally pub commit_lsn: Lsn, @@ -74,7 +78,9 @@ pub struct ServerInfoV3 { /// Postgres server version pub pg_version: u32, pub system_id: SystemId, + #[serde(with = "hex")] pub tenant_id: TenantId, + #[serde(with = "hex")] pub timeline_id: TimelineId, pub wal_seg_size: u32, } @@ -87,6 +93,7 @@ pub struct SafeKeeperStateV3 { pub server: ServerInfoV3, /// Unique id of the last *elected* proposer we dealt with. Not needed /// for correctness, exists for monitoring purposes. + #[serde(with = "hex")] pub proposer_uuid: PgUuid, /// part of WAL acknowledged by quorum and available locally pub commit_lsn: Lsn, @@ -108,6 +115,7 @@ pub struct SafeKeeperStateV4 { pub server: ServerInfo, /// Unique id of the last *elected* proposer we dealt with. Not needed /// for correctness, exists for monitoring purposes. + #[serde(with = "hex")] pub proposer_uuid: PgUuid, /// Part of WAL acknowledged by quorum and available locally. Always points /// to record boundary. diff --git a/safekeeper/src/safekeeper.rs b/safekeeper/src/safekeeper.rs index dd7aef3552bf..1437bdb50ec8 100644 --- a/safekeeper/src/safekeeper.rs +++ b/safekeeper/src/safekeeper.rs @@ -239,7 +239,9 @@ pub struct PersistedPeers(pub Vec<(NodeId, PersistedPeerInfo)>); /// On disk data is prefixed by magic and format version and followed by checksum. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SafeKeeperState { + #[serde(with = "hex")] pub tenant_id: TenantId, + #[serde(with = "hex")] pub timeline_id: TimelineId, /// persistent acceptor state pub acceptor_state: AcceptorState, @@ -247,6 +249,7 @@ pub struct SafeKeeperState { pub server: ServerInfo, /// Unique id of the last *elected* proposer we dealt with. Not needed /// for correctness, exists for monitoring purposes. + #[serde(with = "hex")] pub proposer_uuid: PgUuid, /// Since which LSN this timeline generally starts. Safekeeper might have /// joined later. @@ -285,6 +288,7 @@ pub struct SafekeeperMemState { pub commit_lsn: Lsn, pub backup_lsn: Lsn, pub peer_horizon_lsn: Lsn, + #[serde(with = "hex")] pub proposer_uuid: PgUuid, } From 62beb05665006b961a82a414ef8e871aeedde73a Mon Sep 17 00:00:00 2001 From: duguorong Date: Sat, 4 Nov 2023 23:08:26 +0800 Subject: [PATCH 19/23] chore: add testing of "SafeKeeperState" --- safekeeper/src/safekeeper.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/safekeeper/src/safekeeper.rs b/safekeeper/src/safekeeper.rs index 1437bdb50ec8..e31f96332658 100644 --- a/safekeeper/src/safekeeper.rs +++ b/safekeeper/src/safekeeper.rs @@ -1314,4 +1314,18 @@ mod tests { }) ); } + + #[test] + fn test_sk_state_bincode_serde_roundtrip() { + let sk_state = test_sk_state(); + + let mut buf = vec![]; + let _ = sk_state.ser_into(&mut buf); + + let des_sk_state = SafeKeeperState::des(&buf).unwrap(); + + assert!(sk_state.server.wal_seg_size == des_sk_state.server.wal_seg_size); + assert!(sk_state.tenant_id == des_sk_state.tenant_id); + assert!(sk_state.timeline_id == des_sk_state.timeline_id); + } } From f36ef75fb696a0072c0c98137d719f8cd29bbaf4 Mon Sep 17 00:00:00 2001 From: Joonas Koivunen Date: Sat, 4 Nov 2023 17:28:43 +0200 Subject: [PATCH 20/23] test_sk_state_bincode_serde_roundtrip: unwrap, expected bytes --- safekeeper/src/safekeeper.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/safekeeper/src/safekeeper.rs b/safekeeper/src/safekeeper.rs index e31f96332658..ea4ee7093435 100644 --- a/safekeeper/src/safekeeper.rs +++ b/safekeeper/src/safekeeper.rs @@ -1319,8 +1319,27 @@ mod tests { fn test_sk_state_bincode_serde_roundtrip() { let sk_state = test_sk_state(); - let mut buf = vec![]; - let _ = sk_state.ser_into(&mut buf); + let mut buf = Vec::new(); + sk_state.ser_into(&mut buf).unwrap(); + + #[rustfmt::skip] + let expected = [ + 32, 0, 0, 0, 0, 0, 0, 0, 48, 49, 48, 49, 48, 49, 48, 49, + 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, + 48, 49, 48, 49, 48, 49, 48, 49, 32, 0, 0, 0, 0, 0, 0, 0, + 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, + 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 32, 0, 0, 0, 0, 0, 0, 0, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]; + + assert_eq!(&buf[..], &expected); let des_sk_state = SafeKeeperState::des(&buf).unwrap(); From 8dcebd1708e42642f1b42f896f33f6935825cfa2 Mon Sep 17 00:00:00 2001 From: Joonas Koivunen Date: Sat, 4 Nov 2023 22:42:14 +0200 Subject: [PATCH 21/23] fix: control file serde with examples --- safekeeper/src/control_file_upgrade.rs | 279 ++++++++++++++++++++++++- safekeeper/src/safekeeper.rs | 124 ++++++++--- 2 files changed, 362 insertions(+), 41 deletions(-) diff --git a/safekeeper/src/control_file_upgrade.rs b/safekeeper/src/control_file_upgrade.rs index d23cd07e577a..7165626b5f10 100644 --- a/safekeeper/src/control_file_upgrade.rs +++ b/safekeeper/src/control_file_upgrade.rs @@ -13,7 +13,7 @@ use utils::{ }; /// Persistent consensus state of the acceptor. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] struct AcceptorStateV1 { /// acceptor's last term it voted for (advanced in 1 phase) term: Term, @@ -21,7 +21,7 @@ struct AcceptorStateV1 { epoch: Term, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] struct SafeKeeperStateV1 { /// persistent acceptor state acceptor_state: AcceptorStateV1, @@ -29,7 +29,6 @@ struct SafeKeeperStateV1 { server: ServerInfoV2, /// Unique id of the last *elected* proposer we dealt with. Not needed /// for correctness, exists for monitoring purposes. - #[serde(with = "hex")] proposer_uuid: PgUuid, /// part of WAL acknowledged by quorum and available locally commit_lsn: Lsn, @@ -46,14 +45,12 @@ pub struct ServerInfoV2 { /// Postgres server version pub pg_version: u32, pub system_id: SystemId, - #[serde(with = "hex")] pub tenant_id: TenantId, - #[serde(with = "hex")] pub timeline_id: TimelineId, pub wal_seg_size: u32, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct SafeKeeperStateV2 { /// persistent acceptor state pub acceptor_state: AcceptorState, @@ -61,7 +58,6 @@ pub struct SafeKeeperStateV2 { pub server: ServerInfoV2, /// Unique id of the last *elected* proposer we dealt with. Not needed /// for correctness, exists for monitoring purposes. - #[serde(with = "hex")] pub proposer_uuid: PgUuid, /// part of WAL acknowledged by quorum and available locally pub commit_lsn: Lsn, @@ -85,7 +81,7 @@ pub struct ServerInfoV3 { pub wal_seg_size: u32, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct SafeKeeperStateV3 { /// persistent acceptor state pub acceptor_state: AcceptorState, @@ -105,9 +101,11 @@ pub struct SafeKeeperStateV3 { pub wal_start_lsn: Lsn, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct SafeKeeperStateV4 { + #[serde(with = "hex")] pub tenant_id: TenantId, + #[serde(with = "hex")] pub timeline_id: TimelineId, /// persistent acceptor state pub acceptor_state: AcceptorState, @@ -266,3 +264,266 @@ pub fn upgrade_control_file(buf: &[u8], version: u32) -> Result } bail!("unsupported safekeeper control file version {}", version) } + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use utils::id::NodeId; + + use crate::safekeeper::PersistedPeerInfo; + + use super::*; + + #[test] + fn roundtrip_v1() { + let tenant_id = TenantId::from_str("cf0480929707ee75372337efaa5ecf96").unwrap(); + let timeline_id = TimelineId::from_str("112ded66422aa5e953e5440fa5427ac4").unwrap(); + let state = SafeKeeperStateV1 { + acceptor_state: AcceptorStateV1 { + term: 42, + epoch: 43, + }, + server: ServerInfoV2 { + pg_version: 14, + system_id: 0x1234567887654321, + tenant_id, + timeline_id, + wal_seg_size: 0x12345678, + }, + proposer_uuid: { + let mut arr = timeline_id.as_arr(); + arr.reverse(); + arr + }, + commit_lsn: Lsn(1234567800), + truncate_lsn: Lsn(123456780), + wal_start_lsn: Lsn(1234567800 - 8), + }; + + let ser = state.ser().unwrap(); + #[rustfmt::skip] + let expected = [ + // term + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // epoch + 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // pg_version + 0x0e, 0x00, 0x00, 0x00, + // system_id + 0x21, 0x43, 0x65, 0x87, 0x78, 0x56, 0x34, 0x12, + // tenant_id + 0xcf, 0x04, 0x80, 0x92, 0x97, 0x07, 0xee, 0x75, 0x37, 0x23, 0x37, 0xef, 0xaa, 0x5e, 0xcf, 0x96, + // timeline_id + 0x11, 0x2d, 0xed, 0x66, 0x42, 0x2a, 0xa5, 0xe9, 0x53, 0xe5, 0x44, 0x0f, 0xa5, 0x42, 0x7a, 0xc4, + // wal_seg_size + 0x78, 0x56, 0x34, 0x12, + // proposer_uuid + 0xc4, 0x7a, 0x42, 0xa5, 0x0f, 0x44, 0xe5, 0x53, 0xe9, 0xa5, 0x2a, 0x42, 0x66, 0xed, 0x2d, 0x11, + // commit_lsn + 0x78, 0x02, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, + // truncate_lsn + 0x0c, 0xcd, 0x5b, 0x07, 0x00, 0x00, 0x00, 0x00, + // wal_start_lsn + 0x70, 0x02, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, + ]; + + assert_eq!(Hex(&ser), Hex(&expected)); + + let deser = SafeKeeperStateV1::des(&ser).unwrap(); + + assert_eq!(state, deser); + } + + #[test] + fn roundtrip_v2() { + let tenant_id = TenantId::from_str("cf0480929707ee75372337efaa5ecf96").unwrap(); + let timeline_id = TimelineId::from_str("112ded66422aa5e953e5440fa5427ac4").unwrap(); + let state = SafeKeeperStateV2 { + acceptor_state: AcceptorState { + term: 42, + term_history: TermHistory(vec![TermLsn { + lsn: Lsn(0x1), + term: 41, + }]), + }, + server: ServerInfoV2 { + pg_version: 14, + system_id: 0x1234567887654321, + tenant_id, + timeline_id, + wal_seg_size: 0x12345678, + }, + proposer_uuid: { + let mut arr = timeline_id.as_arr(); + arr.reverse(); + arr + }, + commit_lsn: Lsn(1234567800), + truncate_lsn: Lsn(123456780), + wal_start_lsn: Lsn(1234567800 - 8), + }; + + let ser = state.ser().unwrap(); + let expected = [ + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x21, 0x43, 0x65, 0x87, 0x78, 0x56, + 0x34, 0x12, 0xcf, 0x04, 0x80, 0x92, 0x97, 0x07, 0xee, 0x75, 0x37, 0x23, 0x37, 0xef, + 0xaa, 0x5e, 0xcf, 0x96, 0x11, 0x2d, 0xed, 0x66, 0x42, 0x2a, 0xa5, 0xe9, 0x53, 0xe5, + 0x44, 0x0f, 0xa5, 0x42, 0x7a, 0xc4, 0x78, 0x56, 0x34, 0x12, 0xc4, 0x7a, 0x42, 0xa5, + 0x0f, 0x44, 0xe5, 0x53, 0xe9, 0xa5, 0x2a, 0x42, 0x66, 0xed, 0x2d, 0x11, 0x78, 0x02, + 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xcd, 0x5b, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x70, 0x02, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, + ]; + + assert_eq!(Hex(&ser), Hex(&expected)); + + let deser = SafeKeeperStateV2::des(&ser).unwrap(); + + assert_eq!(state, deser); + } + + #[test] + fn roundtrip_v3() { + let tenant_id = TenantId::from_str("cf0480929707ee75372337efaa5ecf96").unwrap(); + let timeline_id = TimelineId::from_str("112ded66422aa5e953e5440fa5427ac4").unwrap(); + let state = SafeKeeperStateV3 { + acceptor_state: AcceptorState { + term: 42, + term_history: TermHistory(vec![TermLsn { + lsn: Lsn(0x1), + term: 41, + }]), + }, + server: ServerInfoV3 { + pg_version: 14, + system_id: 0x1234567887654321, + tenant_id, + timeline_id, + wal_seg_size: 0x12345678, + }, + proposer_uuid: { + let mut arr = timeline_id.as_arr(); + arr.reverse(); + arr + }, + commit_lsn: Lsn(1234567800), + truncate_lsn: Lsn(123456780), + wal_start_lsn: Lsn(1234567800 - 8), + }; + + let ser = state.ser().unwrap(); + let expected = [ + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x21, 0x43, 0x65, 0x87, 0x78, 0x56, + 0x34, 0x12, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x66, 0x30, 0x34, + 0x38, 0x30, 0x39, 0x32, 0x39, 0x37, 0x30, 0x37, 0x65, 0x65, 0x37, 0x35, 0x33, 0x37, + 0x32, 0x33, 0x33, 0x37, 0x65, 0x66, 0x61, 0x61, 0x35, 0x65, 0x63, 0x66, 0x39, 0x36, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x31, 0x32, 0x64, 0x65, 0x64, + 0x36, 0x36, 0x34, 0x32, 0x32, 0x61, 0x61, 0x35, 0x65, 0x39, 0x35, 0x33, 0x65, 0x35, + 0x34, 0x34, 0x30, 0x66, 0x61, 0x35, 0x34, 0x32, 0x37, 0x61, 0x63, 0x34, 0x78, 0x56, + 0x34, 0x12, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x34, 0x37, 0x61, + 0x34, 0x32, 0x61, 0x35, 0x30, 0x66, 0x34, 0x34, 0x65, 0x35, 0x35, 0x33, 0x65, 0x39, + 0x61, 0x35, 0x32, 0x61, 0x34, 0x32, 0x36, 0x36, 0x65, 0x64, 0x32, 0x64, 0x31, 0x31, + 0x78, 0x02, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xcd, 0x5b, 0x07, 0x00, 0x00, + 0x00, 0x00, 0x70, 0x02, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, + ]; + + assert_eq!(Hex(&ser), Hex(&expected)); + + let deser = SafeKeeperStateV3::des(&ser).unwrap(); + + assert_eq!(state, deser); + } + + #[test] + fn roundtrip_v4() { + let tenant_id = TenantId::from_str("cf0480929707ee75372337efaa5ecf96").unwrap(); + let timeline_id = TimelineId::from_str("112ded66422aa5e953e5440fa5427ac4").unwrap(); + let state = SafeKeeperStateV4 { + tenant_id, + timeline_id, + acceptor_state: AcceptorState { + term: 42, + term_history: TermHistory(vec![TermLsn { + lsn: Lsn(0x1), + term: 41, + }]), + }, + server: ServerInfo { + pg_version: 14, + system_id: 0x1234567887654321, + wal_seg_size: 0x12345678, + }, + proposer_uuid: { + let mut arr = timeline_id.as_arr(); + arr.reverse(); + arr + }, + peers: PersistedPeers(vec![( + NodeId(1), + PersistedPeerInfo { + backup_lsn: Lsn(1234567000), + term: 42, + flush_lsn: Lsn(1234567800 - 8), + commit_lsn: Lsn(1234567600), + }, + )]), + commit_lsn: Lsn(1234567800), + s3_wal_lsn: Lsn(1234567300), + peer_horizon_lsn: Lsn(9999999), + remote_consistent_lsn: Lsn(1234560000), + }; + + let ser = state.ser().unwrap(); + let expected = [ + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x66, 0x30, 0x34, 0x38, 0x30, + 0x39, 0x32, 0x39, 0x37, 0x30, 0x37, 0x65, 0x65, 0x37, 0x35, 0x33, 0x37, 0x32, 0x33, + 0x33, 0x37, 0x65, 0x66, 0x61, 0x61, 0x35, 0x65, 0x63, 0x66, 0x39, 0x36, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x31, 0x32, 0x64, 0x65, 0x64, 0x36, 0x36, + 0x34, 0x32, 0x32, 0x61, 0x61, 0x35, 0x65, 0x39, 0x35, 0x33, 0x65, 0x35, 0x34, 0x34, + 0x30, 0x66, 0x61, 0x35, 0x34, 0x32, 0x37, 0x61, 0x63, 0x34, 0x2a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x21, 0x43, 0x65, 0x87, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, + 0x34, 0x12, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x34, 0x37, 0x61, + 0x34, 0x32, 0x61, 0x35, 0x30, 0x66, 0x34, 0x34, 0x65, 0x35, 0x35, 0x33, 0x65, 0x39, + 0x61, 0x35, 0x32, 0x61, 0x34, 0x32, 0x36, 0x36, 0x65, 0x64, 0x32, 0x64, 0x31, 0x31, + 0x78, 0x02, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x96, 0x49, 0x00, 0x00, + 0x00, 0x00, 0x7f, 0x96, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x95, 0x49, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xff, 0x95, 0x49, 0x00, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x02, 0x96, 0x49, 0x00, 0x00, + 0x00, 0x00, 0xb0, 0x01, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, + ]; + + assert_eq!(Hex(&ser), Hex(&expected)); + + let deser = SafeKeeperStateV4::des(&ser).unwrap(); + + assert_eq!(state, deser); + } + + #[derive(PartialEq)] + struct Hex<'a>(&'a [u8]); + + impl std::fmt::Debug for Hex<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[")?; + for (i, c) in self.0.chunks(16).enumerate() { + if i > 0 && !c.is_empty() { + writeln!(f, ", ")?; + } + for (j, b) in c.iter().enumerate() { + if j > 0 { + write!(f, ", ")?; + } + write!(f, "0x{b:02x}")?; + } + } + write!(f, "; {}]", self.0.len()) + } + } +} diff --git a/safekeeper/src/safekeeper.rs b/safekeeper/src/safekeeper.rs index ea4ee7093435..085900312ca7 100644 --- a/safekeeper/src/safekeeper.rs +++ b/safekeeper/src/safekeeper.rs @@ -52,7 +52,7 @@ impl From<(Term, Lsn)> for TermLsn { } } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize, PartialEq)] pub struct TermHistory(pub Vec); impl TermHistory { @@ -178,7 +178,7 @@ impl fmt::Debug for TermHistory { pub type PgUuid = [u8; 16]; /// Persistent consensus state of the acceptor. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct AcceptorState { /// acceptor's last term it voted for (advanced in 1 phase) pub term: Term, @@ -209,16 +209,16 @@ pub struct ServerInfo { pub wal_seg_size: u32, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct PersistedPeerInfo { /// LSN up to which safekeeper offloaded WAL to s3. - backup_lsn: Lsn, + pub backup_lsn: Lsn, /// Term of the last entry. - term: Term, + pub term: Term, /// LSN of the last record. - flush_lsn: Lsn, + pub flush_lsn: Lsn, /// Up to which LSN safekeeper regards its WAL as committed. - commit_lsn: Lsn, + pub commit_lsn: Lsn, } impl PersistedPeerInfo { @@ -232,12 +232,12 @@ impl PersistedPeerInfo { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct PersistedPeers(pub Vec<(NodeId, PersistedPeerInfo)>); /// Persistent information stored on safekeeper node /// On disk data is prefixed by magic and format version and followed by checksum. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct SafeKeeperState { #[serde(with = "hex")] pub tenant_id: TenantId, @@ -1096,7 +1096,7 @@ mod tests { use super::*; use crate::wal_storage::Storage; - use std::{ops::Deref, time::Instant}; + use std::{ops::Deref, str::FromStr, time::Instant}; // fake storage for tests struct InMemoryState { @@ -1317,34 +1317,94 @@ mod tests { #[test] fn test_sk_state_bincode_serde_roundtrip() { - let sk_state = test_sk_state(); + let tenant_id = TenantId::from_str("cf0480929707ee75372337efaa5ecf96").unwrap(); + let timeline_id = TimelineId::from_str("112ded66422aa5e953e5440fa5427ac4").unwrap(); + let state = SafeKeeperState { + tenant_id, + timeline_id, + acceptor_state: AcceptorState { + term: 42, + term_history: TermHistory(vec![TermLsn { + lsn: Lsn(0x1), + term: 41, + }]), + }, + server: ServerInfo { + pg_version: 14, + system_id: 0x1234567887654321, + wal_seg_size: 0x12345678, + }, + proposer_uuid: { + let mut arr = timeline_id.as_arr(); + arr.reverse(); + arr + }, + timeline_start_lsn: Lsn(0x12345600), + local_start_lsn: Lsn(0x12), + commit_lsn: Lsn(1234567800), + backup_lsn: Lsn(1234567300), + peer_horizon_lsn: Lsn(9999999), + remote_consistent_lsn: Lsn(1234560000), + peers: PersistedPeers(vec![( + NodeId(1), + PersistedPeerInfo { + backup_lsn: Lsn(1234567000), + term: 42, + flush_lsn: Lsn(1234567800 - 8), + commit_lsn: Lsn(1234567600), + }, + )]), + }; - let mut buf = Vec::new(); - sk_state.ser_into(&mut buf).unwrap(); + let ser = state.ser().unwrap(); - #[rustfmt::skip] let expected = [ - 32, 0, 0, 0, 0, 0, 0, 0, 48, 49, 48, 49, 48, 49, 48, 49, - 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, - 48, 49, 48, 49, 48, 49, 48, 49, 32, 0, 0, 0, 0, 0, 0, 0, - 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, - 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, 48, 49, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 32, 0, 0, 0, 0, 0, 0, 0, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 48, 48, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x66, 0x30, 0x34, 0x38, 0x30, + 0x39, 0x32, 0x39, 0x37, 0x30, 0x37, 0x65, 0x65, 0x37, 0x35, 0x33, 0x37, 0x32, 0x33, + 0x33, 0x37, 0x65, 0x66, 0x61, 0x61, 0x35, 0x65, 0x63, 0x66, 0x39, 0x36, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x31, 0x32, 0x64, 0x65, 0x64, 0x36, 0x36, + 0x34, 0x32, 0x32, 0x61, 0x61, 0x35, 0x65, 0x39, 0x35, 0x33, 0x65, 0x35, 0x34, 0x34, + 0x30, 0x66, 0x61, 0x35, 0x34, 0x32, 0x37, 0x61, 0x63, 0x34, 0x2a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x21, 0x43, 0x65, 0x87, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, + 0x34, 0x12, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x34, 0x37, 0x61, + 0x34, 0x32, 0x61, 0x35, 0x30, 0x66, 0x34, 0x34, 0x65, 0x35, 0x35, 0x33, 0x65, 0x39, + 0x61, 0x35, 0x32, 0x61, 0x34, 0x32, 0x36, 0x36, 0x65, 0x64, 0x32, 0x64, 0x31, 0x31, + 0x00, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x78, 0x02, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x96, 0x49, + 0x00, 0x00, 0x00, 0x00, 0x7f, 0x96, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, + 0x95, 0x49, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xff, 0x95, 0x49, 0x00, 0x00, + 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x02, 0x96, 0x49, + 0x00, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, ]; - assert_eq!(&buf[..], &expected); + assert_eq!(Hex(&ser), Hex(&expected)); + + let deser = SafeKeeperState::des(&ser).unwrap(); + + assert_eq!(deser, state); + } - let des_sk_state = SafeKeeperState::des(&buf).unwrap(); + #[derive(PartialEq)] + struct Hex<'a>(&'a [u8]); - assert!(sk_state.server.wal_seg_size == des_sk_state.server.wal_seg_size); - assert!(sk_state.tenant_id == des_sk_state.tenant_id); - assert!(sk_state.timeline_id == des_sk_state.timeline_id); + impl std::fmt::Debug for Hex<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[")?; + for (i, c) in self.0.chunks(16).enumerate() { + if i > 0 && !c.is_empty() { + writeln!(f, ", ")?; + } + for (j, b) in c.iter().enumerate() { + if j > 0 { + write!(f, ", ")?; + } + write!(f, "0x{b:02x}")?; + } + } + write!(f, "; {}]", self.0.len()) + } } } From 77dd8b968348c7bd01703123f642838053d7f2fd Mon Sep 17 00:00:00 2001 From: Joonas Koivunen Date: Sat, 4 Nov 2023 23:49:46 +0200 Subject: [PATCH 22/23] refactor: move hex as utils::Hex --- libs/utils/src/hex.rs | 41 ++++++++++++++++++++++++++ libs/utils/src/lib.rs | 4 +++ safekeeper/src/control_file_upgrade.rs | 23 +-------------- safekeeper/src/safekeeper.rs | 22 +------------- 4 files changed, 47 insertions(+), 43 deletions(-) create mode 100644 libs/utils/src/hex.rs diff --git a/libs/utils/src/hex.rs b/libs/utils/src/hex.rs new file mode 100644 index 000000000000..fc0bb7e4a280 --- /dev/null +++ b/libs/utils/src/hex.rs @@ -0,0 +1,41 @@ +/// Useful type for asserting that expected bytes match reporting the bytes more readable +/// array-syntax compatible hex bytes. +/// +/// # Usage +/// +/// ``` +/// use utils::Hex; +/// +/// let actual = serialize_something(); +/// let expected = [0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64]; +/// +/// // the type implements PartialEq and on mismatch, both sides are printed in 16 wide multiline +/// // output suffixed with an array style length for easier comparisons. +/// assert_eq!(Hex(&actual), Hex(&expected)); +/// +/// // with `let expected = [0x68];` the error would had been: +/// // assertion `left == right` failed +/// // left: [0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64; 11] +/// // right: [0x68; 1] +/// # fn serialize_something() -> Vec { "hello world".as_bytes().to_vec() } +/// ``` +#[derive(PartialEq)] +pub struct Hex<'a>(pub &'a [u8]); + +impl std::fmt::Debug for Hex<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[")?; + for (i, c) in self.0.chunks(16).enumerate() { + if i > 0 && !c.is_empty() { + writeln!(f, ", ")?; + } + for (j, b) in c.iter().enumerate() { + if j > 0 { + write!(f, ", ")?; + } + write!(f, "0x{b:02x}")?; + } + } + write!(f, "; {}]", self.0.len()) + } +} diff --git a/libs/utils/src/lib.rs b/libs/utils/src/lib.rs index 88cefd516d75..1e3403402318 100644 --- a/libs/utils/src/lib.rs +++ b/libs/utils/src/lib.rs @@ -24,6 +24,10 @@ pub mod auth; // utility functions and helper traits for unified unique id generation/serialization etc. pub mod id; + +mod hex; +pub use hex::Hex; + // http endpoint utils pub mod http; diff --git a/safekeeper/src/control_file_upgrade.rs b/safekeeper/src/control_file_upgrade.rs index 7165626b5f10..a0be2b205468 100644 --- a/safekeeper/src/control_file_upgrade.rs +++ b/safekeeper/src/control_file_upgrade.rs @@ -269,7 +269,7 @@ pub fn upgrade_control_file(buf: &[u8], version: u32) -> Result mod tests { use std::str::FromStr; - use utils::id::NodeId; + use utils::{id::NodeId, Hex}; use crate::safekeeper::PersistedPeerInfo; @@ -505,25 +505,4 @@ mod tests { assert_eq!(state, deser); } - - #[derive(PartialEq)] - struct Hex<'a>(&'a [u8]); - - impl std::fmt::Debug for Hex<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "[")?; - for (i, c) in self.0.chunks(16).enumerate() { - if i > 0 && !c.is_empty() { - writeln!(f, ", ")?; - } - for (j, b) in c.iter().enumerate() { - if j > 0 { - write!(f, ", ")?; - } - write!(f, "0x{b:02x}")?; - } - } - write!(f, "; {}]", self.0.len()) - } - } } diff --git a/safekeeper/src/safekeeper.rs b/safekeeper/src/safekeeper.rs index 085900312ca7..fd92e20d75cc 100644 --- a/safekeeper/src/safekeeper.rs +++ b/safekeeper/src/safekeeper.rs @@ -1317,6 +1317,7 @@ mod tests { #[test] fn test_sk_state_bincode_serde_roundtrip() { + use utils::Hex; let tenant_id = TenantId::from_str("cf0480929707ee75372337efaa5ecf96").unwrap(); let timeline_id = TimelineId::from_str("112ded66422aa5e953e5440fa5427ac4").unwrap(); let state = SafeKeeperState { @@ -1386,25 +1387,4 @@ mod tests { assert_eq!(deser, state); } - - #[derive(PartialEq)] - struct Hex<'a>(&'a [u8]); - - impl std::fmt::Debug for Hex<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "[")?; - for (i, c) in self.0.chunks(16).enumerate() { - if i > 0 && !c.is_empty() { - writeln!(f, ", ")?; - } - for (j, b) in c.iter().enumerate() { - if j > 0 { - write!(f, ", ")?; - } - write!(f, "0x{b:02x}")?; - } - } - write!(f, "; {}]", self.0.len()) - } - } } From cdbf341c1873ecc2f11de843968a0bf0343dd09a Mon Sep 17 00:00:00 2001 From: Joonas Koivunen Date: Sat, 4 Nov 2023 23:51:21 +0200 Subject: [PATCH 23/23] test: annotate latest state image --- safekeeper/src/safekeeper.rs | 59 ++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/safekeeper/src/safekeeper.rs b/safekeeper/src/safekeeper.rs index fd92e20d75cc..47a624281d43 100644 --- a/safekeeper/src/safekeeper.rs +++ b/safekeeper/src/safekeeper.rs @@ -1359,26 +1359,47 @@ mod tests { let ser = state.ser().unwrap(); + #[rustfmt::skip] let expected = [ - 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x66, 0x30, 0x34, 0x38, 0x30, - 0x39, 0x32, 0x39, 0x37, 0x30, 0x37, 0x65, 0x65, 0x37, 0x35, 0x33, 0x37, 0x32, 0x33, - 0x33, 0x37, 0x65, 0x66, 0x61, 0x61, 0x35, 0x65, 0x63, 0x66, 0x39, 0x36, 0x20, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x31, 0x32, 0x64, 0x65, 0x64, 0x36, 0x36, - 0x34, 0x32, 0x32, 0x61, 0x61, 0x35, 0x65, 0x39, 0x35, 0x33, 0x65, 0x35, 0x34, 0x34, - 0x30, 0x66, 0x61, 0x35, 0x34, 0x32, 0x37, 0x61, 0x63, 0x34, 0x2a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x21, 0x43, 0x65, 0x87, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, - 0x34, 0x12, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x34, 0x37, 0x61, - 0x34, 0x32, 0x61, 0x35, 0x30, 0x66, 0x34, 0x34, 0x65, 0x35, 0x35, 0x33, 0x65, 0x39, - 0x61, 0x35, 0x32, 0x61, 0x34, 0x32, 0x36, 0x36, 0x65, 0x64, 0x32, 0x64, 0x31, 0x31, - 0x00, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x78, 0x02, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x96, 0x49, - 0x00, 0x00, 0x00, 0x00, 0x7f, 0x96, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, - 0x95, 0x49, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xff, 0x95, 0x49, 0x00, 0x00, - 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x02, 0x96, 0x49, - 0x00, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, + // tenant_id as length prefixed hex + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x66, 0x30, 0x34, 0x38, 0x30, 0x39, 0x32, 0x39, 0x37, 0x30, 0x37, 0x65, 0x65, 0x37, 0x35, 0x33, 0x37, 0x32, 0x33, 0x33, 0x37, 0x65, 0x66, 0x61, 0x61, 0x35, 0x65, 0x63, 0x66, 0x39, 0x36, + // timeline_id as length prefixed hex + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x31, 0x31, 0x32, 0x64, 0x65, 0x64, 0x36, 0x36, 0x34, 0x32, 0x32, 0x61, 0x61, 0x35, 0x65, 0x39, 0x35, 0x33, 0x65, 0x35, 0x34, 0x34, 0x30, 0x66, 0x61, 0x35, 0x34, 0x32, 0x37, 0x61, 0x63, 0x34, + // term + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // length prefix + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // unsure why this order is swapped + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // pg_version + 0x0e, 0x00, 0x00, 0x00, + // systemid + 0x21, 0x43, 0x65, 0x87, 0x78, 0x56, 0x34, 0x12, + // wal_seg_size + 0x78, 0x56, 0x34, 0x12, + // pguuid as length prefixed hex + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x34, 0x37, 0x61, 0x34, 0x32, 0x61, 0x35, 0x30, 0x66, 0x34, 0x34, 0x65, 0x35, 0x35, 0x33, 0x65, 0x39, 0x61, 0x35, 0x32, 0x61, 0x34, 0x32, 0x36, 0x36, 0x65, 0x64, 0x32, 0x64, 0x31, 0x31, + + // timeline_start_lsn + 0x00, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x78, 0x02, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, + 0x84, 0x00, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, + 0x7f, 0x96, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xe4, 0x95, 0x49, 0x00, 0x00, 0x00, 0x00, + // length prefix for persistentpeers + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // nodeid + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // backuplsn + 0x58, 0xff, 0x95, 0x49, 0x00, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x70, 0x02, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, + 0xb0, 0x01, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, ]; assert_eq!(Hex(&ser), Hex(&expected));