mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-13 16:32:56 +00:00
Updates storage components to edition 2024. We like to stay on the latest edition if possible. There is no functional changes, however some code changes had to be done to accommodate the edition's breaking changes. The PR has two commits: * the first commit updates storage crates to edition 2024 and appeases `cargo clippy` by changing code. i have accidentially ran the formatter on some files that had other edits. * the second commit performs a `cargo fmt` I would recommend a closer review of the first commit and a less close review of the second one (as it just runs `cargo fmt`). part of https://github.com/neondatabase/neon/issues/10918
787 lines
33 KiB
Rust
787 lines
33 KiB
Rust
//! Code to deal with safekeeper control file upgrades
|
|
use std::vec;
|
|
|
|
use anyhow::{Result, bail};
|
|
use pq_proto::SystemId;
|
|
use safekeeper_api::membership::{Configuration, INVALID_GENERATION};
|
|
use safekeeper_api::{ServerInfo, Term};
|
|
use serde::{Deserialize, Serialize};
|
|
use tracing::*;
|
|
use utils::bin_ser::LeSer;
|
|
use utils::id::{NodeId, TenantId, TimelineId};
|
|
use utils::lsn::Lsn;
|
|
|
|
use crate::safekeeper::{AcceptorState, PgUuid, TermHistory, TermLsn};
|
|
use crate::state::{EvictionState, TimelinePersistentState};
|
|
use crate::wal_backup_partial;
|
|
|
|
/// Persistent consensus state of the acceptor.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
struct AcceptorStateV1 {
|
|
/// acceptor's last term it voted for (advanced in 1 phase)
|
|
term: Term,
|
|
/// acceptor's epoch (advanced, i.e. bumped to 'term' when VCL is reached).
|
|
epoch: Term,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
struct SafeKeeperStateV1 {
|
|
/// persistent acceptor state
|
|
acceptor_state: AcceptorStateV1,
|
|
/// information about server
|
|
server: ServerInfoV2,
|
|
/// Unique id of the last *elected* proposer we dealt with. Not needed
|
|
/// for correctness, exists for monitoring purposes.
|
|
proposer_uuid: PgUuid,
|
|
/// part of WAL acknowledged by quorum and available locally
|
|
commit_lsn: Lsn,
|
|
/// minimal LSN which may be needed for recovery of some safekeeper (end_lsn
|
|
/// of last record streamed to everyone)
|
|
truncate_lsn: Lsn,
|
|
// Safekeeper starts receiving WAL from this LSN, zeros before it ought to
|
|
// be skipped during decoding.
|
|
wal_start_lsn: Lsn,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct ServerInfoV2 {
|
|
/// Postgres server version
|
|
pub pg_version: u32,
|
|
pub system_id: SystemId,
|
|
pub tenant_id: TenantId,
|
|
pub timeline_id: TimelineId,
|
|
pub wal_seg_size: u32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct SafeKeeperStateV2 {
|
|
/// persistent acceptor state
|
|
pub acceptor_state: AcceptorState,
|
|
/// information about server
|
|
pub server: ServerInfoV2,
|
|
/// Unique id of the last *elected* proposer we dealt with. Not needed
|
|
/// for correctness, exists for monitoring purposes.
|
|
pub proposer_uuid: PgUuid,
|
|
/// part of WAL acknowledged by quorum and available locally
|
|
pub commit_lsn: Lsn,
|
|
/// minimal LSN which may be needed for recovery of some safekeeper (end_lsn
|
|
/// of last record streamed to everyone)
|
|
pub truncate_lsn: Lsn,
|
|
// Safekeeper starts receiving WAL from this LSN, zeros before it ought to
|
|
// be skipped during decoding.
|
|
pub wal_start_lsn: Lsn,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
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,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct SafeKeeperStateV3 {
|
|
/// persistent acceptor state
|
|
pub acceptor_state: AcceptorState,
|
|
/// information about server
|
|
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,
|
|
/// minimal LSN which may be needed for recovery of some safekeeper (end_lsn
|
|
/// of last record streamed to everyone)
|
|
pub truncate_lsn: Lsn,
|
|
// Safekeeper starts receiving WAL from this LSN, zeros before it ought to
|
|
// be skipped during decoding.
|
|
pub wal_start_lsn: Lsn,
|
|
}
|
|
|
|
#[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,
|
|
/// information about server
|
|
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.
|
|
pub commit_lsn: Lsn,
|
|
/// First LSN not yet offloaded to s3. Useful to persist to avoid finding
|
|
/// out offloading progress on boot.
|
|
pub s3_wal_lsn: Lsn,
|
|
/// Minimal LSN which may be needed for recovery of some safekeeper (end_lsn
|
|
/// of last record streamed to everyone). Persisting it helps skipping
|
|
/// recovery in walproposer, generally we compute it from peers. In
|
|
/// walproposer proto called 'truncate_lsn'.
|
|
pub peer_horizon_lsn: Lsn,
|
|
/// LSN of the oldest known checkpoint made by pageserver and successfully
|
|
/// pushed to s3. We don't remove WAL beyond it. Persisted only for
|
|
/// informational purposes, we receive it from pageserver (or broker).
|
|
pub remote_consistent_lsn: Lsn,
|
|
// Peers and their state as we remember it. Knowing peers themselves is
|
|
// fundamental; but state is saved here only for informational purposes and
|
|
// obviously can be stale. (Currently not saved at all, but let's provision
|
|
// place to have less file version upgrades).
|
|
pub peers: PersistedPeers,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct SafeKeeperStateV7 {
|
|
#[serde(with = "hex")]
|
|
pub tenant_id: TenantId,
|
|
#[serde(with = "hex")]
|
|
pub timeline_id: TimelineId,
|
|
/// persistent acceptor state
|
|
pub acceptor_state: AcceptorState,
|
|
/// information about server
|
|
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.
|
|
pub timeline_start_lsn: Lsn,
|
|
/// Since which LSN safekeeper has (had) WAL for this timeline.
|
|
/// All WAL segments next to one containing local_start_lsn are
|
|
/// filled with data from the beginning.
|
|
pub local_start_lsn: Lsn,
|
|
/// Part of WAL acknowledged by quorum *and available locally*. Always points
|
|
/// to record boundary.
|
|
pub commit_lsn: Lsn,
|
|
/// LSN that points to the end of the last backed up segment. Useful to
|
|
/// persist to avoid finding out offloading progress on boot.
|
|
pub backup_lsn: Lsn,
|
|
/// Minimal LSN which may be needed for recovery of some safekeeper (end_lsn
|
|
/// of last record streamed to everyone). Persisting it helps skipping
|
|
/// recovery in walproposer, generally we compute it from peers. In
|
|
/// walproposer proto called 'truncate_lsn'. Updates are currently drived
|
|
/// only by walproposer.
|
|
pub peer_horizon_lsn: Lsn,
|
|
/// LSN of the oldest known checkpoint made by pageserver and successfully
|
|
/// pushed to s3. We don't remove WAL beyond it. Persisted only for
|
|
/// informational purposes, we receive it from pageserver (or broker).
|
|
pub remote_consistent_lsn: Lsn,
|
|
// Peers and their state as we remember it. Knowing peers themselves is
|
|
// fundamental; but state is saved here only for informational purposes and
|
|
// obviously can be stale. (Currently not saved at all, but let's provision
|
|
// place to have less file version upgrades).
|
|
pub peers: PersistedPeers,
|
|
}
|
|
|
|
/// Persistent information stored on safekeeper node about timeline.
|
|
/// On disk data is prefixed by magic and format version and followed by checksum.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct SafeKeeperStateV8 {
|
|
#[serde(with = "hex")]
|
|
pub tenant_id: TenantId,
|
|
#[serde(with = "hex")]
|
|
pub timeline_id: TimelineId,
|
|
/// persistent acceptor state
|
|
pub acceptor_state: AcceptorState,
|
|
/// information about server
|
|
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.
|
|
pub timeline_start_lsn: Lsn,
|
|
/// Since which LSN safekeeper has (had) WAL for this timeline.
|
|
/// All WAL segments next to one containing local_start_lsn are
|
|
/// filled with data from the beginning.
|
|
pub local_start_lsn: Lsn,
|
|
/// Part of WAL acknowledged by quorum *and available locally*. Always points
|
|
/// to record boundary.
|
|
pub commit_lsn: Lsn,
|
|
/// LSN that points to the end of the last backed up segment. Useful to
|
|
/// persist to avoid finding out offloading progress on boot.
|
|
pub backup_lsn: Lsn,
|
|
/// Minimal LSN which may be needed for recovery of some safekeeper (end_lsn
|
|
/// of last record streamed to everyone). Persisting it helps skipping
|
|
/// recovery in walproposer, generally we compute it from peers. In
|
|
/// walproposer proto called 'truncate_lsn'. Updates are currently drived
|
|
/// only by walproposer.
|
|
pub peer_horizon_lsn: Lsn,
|
|
/// LSN of the oldest known checkpoint made by pageserver and successfully
|
|
/// pushed to s3. We don't remove WAL beyond it. Persisted only for
|
|
/// informational purposes, we receive it from pageserver (or broker).
|
|
pub remote_consistent_lsn: Lsn,
|
|
/// Peers and their state as we remember it. Knowing peers themselves is
|
|
/// fundamental; but state is saved here only for informational purposes and
|
|
/// obviously can be stale. (Currently not saved at all, but let's provision
|
|
/// place to have less file version upgrades).
|
|
pub peers: PersistedPeers,
|
|
/// Holds names of partial segments uploaded to remote storage. Used to
|
|
/// clean up old objects without leaving garbage in remote storage.
|
|
pub partial_backup: wal_backup_partial::State,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct PersistedPeers(pub Vec<(NodeId, PersistedPeerInfo)>);
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct PersistedPeerInfo {
|
|
/// LSN up to which safekeeper offloaded WAL to s3.
|
|
pub backup_lsn: Lsn,
|
|
/// Term of the last entry.
|
|
pub term: Term,
|
|
/// LSN of the last record.
|
|
pub flush_lsn: Lsn,
|
|
/// Up to which LSN safekeeper regards its WAL as committed.
|
|
pub commit_lsn: Lsn,
|
|
}
|
|
|
|
impl PersistedPeerInfo {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
backup_lsn: Lsn::INVALID,
|
|
term: safekeeper_api::INITIAL_TERM,
|
|
flush_lsn: Lsn(0),
|
|
commit_lsn: Lsn(0),
|
|
}
|
|
}
|
|
}
|
|
|
|
// make clippy happy
|
|
impl Default for PersistedPeerInfo {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// Note: SafekeeperStateVn is old name for TimelinePersistentStateVn.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct TimelinePersistentStateV9 {
|
|
#[serde(with = "hex")]
|
|
pub tenant_id: TenantId,
|
|
#[serde(with = "hex")]
|
|
pub timeline_id: TimelineId,
|
|
/// persistent acceptor state
|
|
pub acceptor_state: AcceptorState,
|
|
/// information about server
|
|
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.
|
|
pub timeline_start_lsn: Lsn,
|
|
/// Since which LSN safekeeper has (had) WAL for this timeline.
|
|
/// All WAL segments next to one containing local_start_lsn are
|
|
/// filled with data from the beginning.
|
|
pub local_start_lsn: Lsn,
|
|
/// Part of WAL acknowledged by quorum *and available locally*. Always points
|
|
/// to record boundary.
|
|
pub commit_lsn: Lsn,
|
|
/// LSN that points to the end of the last backed up segment. Useful to
|
|
/// persist to avoid finding out offloading progress on boot.
|
|
pub backup_lsn: Lsn,
|
|
/// Minimal LSN which may be needed for recovery of some safekeeper (end_lsn
|
|
/// of last record streamed to everyone). Persisting it helps skipping
|
|
/// recovery in walproposer, generally we compute it from peers. In
|
|
/// walproposer proto called 'truncate_lsn'. Updates are currently drived
|
|
/// only by walproposer.
|
|
pub peer_horizon_lsn: Lsn,
|
|
/// LSN of the oldest known checkpoint made by pageserver and successfully
|
|
/// pushed to s3. We don't remove WAL beyond it. Persisted only for
|
|
/// informational purposes, we receive it from pageserver (or broker).
|
|
pub remote_consistent_lsn: Lsn,
|
|
/// Peers and their state as we remember it. Knowing peers themselves is
|
|
/// fundamental; but state is saved here only for informational purposes and
|
|
/// obviously can be stale. (Currently not saved at all, but let's provision
|
|
/// place to have less file version upgrades).
|
|
pub peers: PersistedPeers,
|
|
/// Holds names of partial segments uploaded to remote storage. Used to
|
|
/// clean up old objects without leaving garbage in remote storage.
|
|
pub partial_backup: wal_backup_partial::State,
|
|
/// Eviction state of the timeline. If it's Offloaded, we should download
|
|
/// WAL files from remote storage to serve the timeline.
|
|
pub eviction_state: EvictionState,
|
|
}
|
|
|
|
pub fn upgrade_control_file(buf: &[u8], version: u32) -> Result<TimelinePersistentState> {
|
|
// migrate to storing full term history
|
|
if version == 1 {
|
|
info!("reading safekeeper control file version {}", version);
|
|
let oldstate = SafeKeeperStateV1::des(&buf[..buf.len()])?;
|
|
let ac = AcceptorState {
|
|
term: oldstate.acceptor_state.term,
|
|
term_history: TermHistory(vec![TermLsn {
|
|
term: oldstate.acceptor_state.epoch,
|
|
lsn: Lsn(0),
|
|
}]),
|
|
};
|
|
return Ok(TimelinePersistentState {
|
|
tenant_id: oldstate.server.tenant_id,
|
|
timeline_id: oldstate.server.timeline_id,
|
|
mconf: Configuration::empty(),
|
|
acceptor_state: ac,
|
|
server: ServerInfo {
|
|
pg_version: oldstate.server.pg_version,
|
|
system_id: oldstate.server.system_id,
|
|
wal_seg_size: oldstate.server.wal_seg_size,
|
|
},
|
|
proposer_uuid: oldstate.proposer_uuid,
|
|
timeline_start_lsn: Lsn(0),
|
|
local_start_lsn: Lsn(0),
|
|
commit_lsn: oldstate.commit_lsn,
|
|
backup_lsn: Lsn(0),
|
|
peer_horizon_lsn: oldstate.truncate_lsn,
|
|
remote_consistent_lsn: Lsn(0),
|
|
partial_backup: wal_backup_partial::State::default(),
|
|
eviction_state: EvictionState::Present,
|
|
creation_ts: std::time::SystemTime::UNIX_EPOCH,
|
|
});
|
|
// migrate to hexing some ids
|
|
} else if version == 2 {
|
|
info!("reading safekeeper control file version {}", version);
|
|
let oldstate = SafeKeeperStateV2::des(&buf[..buf.len()])?;
|
|
let server = ServerInfo {
|
|
pg_version: oldstate.server.pg_version,
|
|
system_id: oldstate.server.system_id,
|
|
wal_seg_size: oldstate.server.wal_seg_size,
|
|
};
|
|
return Ok(TimelinePersistentState {
|
|
tenant_id: oldstate.server.tenant_id,
|
|
timeline_id: oldstate.server.timeline_id,
|
|
mconf: Configuration::empty(),
|
|
acceptor_state: oldstate.acceptor_state,
|
|
server,
|
|
proposer_uuid: oldstate.proposer_uuid,
|
|
timeline_start_lsn: Lsn(0),
|
|
local_start_lsn: Lsn(0),
|
|
commit_lsn: oldstate.commit_lsn,
|
|
backup_lsn: Lsn(0),
|
|
peer_horizon_lsn: oldstate.truncate_lsn,
|
|
remote_consistent_lsn: Lsn(0),
|
|
partial_backup: wal_backup_partial::State::default(),
|
|
eviction_state: EvictionState::Present,
|
|
creation_ts: std::time::SystemTime::UNIX_EPOCH,
|
|
});
|
|
// migrate to moving tenant_id/timeline_id to the top and adding some lsns
|
|
} else if version == 3 {
|
|
info!("reading safekeeper control file version {version}");
|
|
let oldstate = SafeKeeperStateV3::des(&buf[..buf.len()])?;
|
|
let server = ServerInfo {
|
|
pg_version: oldstate.server.pg_version,
|
|
system_id: oldstate.server.system_id,
|
|
wal_seg_size: oldstate.server.wal_seg_size,
|
|
};
|
|
return Ok(TimelinePersistentState {
|
|
tenant_id: oldstate.server.tenant_id,
|
|
timeline_id: oldstate.server.timeline_id,
|
|
mconf: Configuration::empty(),
|
|
acceptor_state: oldstate.acceptor_state,
|
|
server,
|
|
proposer_uuid: oldstate.proposer_uuid,
|
|
timeline_start_lsn: Lsn(0),
|
|
local_start_lsn: Lsn(0),
|
|
commit_lsn: oldstate.commit_lsn,
|
|
backup_lsn: Lsn(0),
|
|
peer_horizon_lsn: oldstate.truncate_lsn,
|
|
remote_consistent_lsn: Lsn(0),
|
|
partial_backup: wal_backup_partial::State::default(),
|
|
eviction_state: EvictionState::Present,
|
|
creation_ts: std::time::SystemTime::UNIX_EPOCH,
|
|
});
|
|
// migrate to having timeline_start_lsn
|
|
} else if version == 4 {
|
|
info!("reading safekeeper control file version {}", version);
|
|
let oldstate = SafeKeeperStateV4::des(&buf[..buf.len()])?;
|
|
let server = ServerInfo {
|
|
pg_version: oldstate.server.pg_version,
|
|
system_id: oldstate.server.system_id,
|
|
wal_seg_size: oldstate.server.wal_seg_size,
|
|
};
|
|
return Ok(TimelinePersistentState {
|
|
tenant_id: oldstate.tenant_id,
|
|
timeline_id: oldstate.timeline_id,
|
|
mconf: Configuration::empty(),
|
|
acceptor_state: oldstate.acceptor_state,
|
|
server,
|
|
proposer_uuid: oldstate.proposer_uuid,
|
|
timeline_start_lsn: Lsn(0),
|
|
local_start_lsn: Lsn(0),
|
|
commit_lsn: oldstate.commit_lsn,
|
|
backup_lsn: Lsn::INVALID,
|
|
peer_horizon_lsn: oldstate.peer_horizon_lsn,
|
|
remote_consistent_lsn: Lsn(0),
|
|
partial_backup: wal_backup_partial::State::default(),
|
|
eviction_state: EvictionState::Present,
|
|
creation_ts: std::time::SystemTime::UNIX_EPOCH,
|
|
});
|
|
} else if version == 5 {
|
|
info!("reading safekeeper control file version {}", version);
|
|
let mut oldstate = TimelinePersistentState::des(&buf[..buf.len()])?;
|
|
if oldstate.timeline_start_lsn != Lsn(0) {
|
|
return Ok(oldstate);
|
|
}
|
|
|
|
// set special timeline_start_lsn because we don't know the real one
|
|
info!("setting timeline_start_lsn and local_start_lsn to Lsn(1)");
|
|
oldstate.timeline_start_lsn = Lsn(1);
|
|
oldstate.local_start_lsn = Lsn(1);
|
|
|
|
return Ok(oldstate);
|
|
} else if version == 6 {
|
|
info!("reading safekeeper control file version {}", version);
|
|
let mut oldstate = TimelinePersistentState::des(&buf[..buf.len()])?;
|
|
if oldstate.server.pg_version != 0 {
|
|
return Ok(oldstate);
|
|
}
|
|
|
|
// set pg_version to the default v14
|
|
info!("setting pg_version to 140005");
|
|
oldstate.server.pg_version = 140005;
|
|
|
|
return Ok(oldstate);
|
|
} else if version == 7 {
|
|
info!("reading safekeeper control file version {}", version);
|
|
let oldstate = SafeKeeperStateV7::des(&buf[..buf.len()])?;
|
|
|
|
return Ok(TimelinePersistentState {
|
|
tenant_id: oldstate.tenant_id,
|
|
timeline_id: oldstate.timeline_id,
|
|
mconf: Configuration::empty(),
|
|
acceptor_state: oldstate.acceptor_state,
|
|
server: oldstate.server,
|
|
proposer_uuid: oldstate.proposer_uuid,
|
|
timeline_start_lsn: oldstate.timeline_start_lsn,
|
|
local_start_lsn: oldstate.local_start_lsn,
|
|
commit_lsn: oldstate.commit_lsn,
|
|
backup_lsn: oldstate.backup_lsn,
|
|
peer_horizon_lsn: oldstate.peer_horizon_lsn,
|
|
remote_consistent_lsn: oldstate.remote_consistent_lsn,
|
|
partial_backup: wal_backup_partial::State::default(),
|
|
eviction_state: EvictionState::Present,
|
|
creation_ts: std::time::SystemTime::UNIX_EPOCH,
|
|
});
|
|
} else if version == 8 {
|
|
let oldstate = SafeKeeperStateV8::des(&buf[..buf.len()])?;
|
|
|
|
return Ok(TimelinePersistentState {
|
|
tenant_id: oldstate.tenant_id,
|
|
timeline_id: oldstate.timeline_id,
|
|
mconf: Configuration::empty(),
|
|
acceptor_state: oldstate.acceptor_state,
|
|
server: oldstate.server,
|
|
proposer_uuid: oldstate.proposer_uuid,
|
|
timeline_start_lsn: oldstate.timeline_start_lsn,
|
|
local_start_lsn: oldstate.local_start_lsn,
|
|
commit_lsn: oldstate.commit_lsn,
|
|
backup_lsn: oldstate.backup_lsn,
|
|
peer_horizon_lsn: oldstate.peer_horizon_lsn,
|
|
remote_consistent_lsn: oldstate.remote_consistent_lsn,
|
|
partial_backup: oldstate.partial_backup,
|
|
eviction_state: EvictionState::Present,
|
|
creation_ts: std::time::SystemTime::UNIX_EPOCH,
|
|
});
|
|
} else if version == 9 {
|
|
let oldstate = TimelinePersistentStateV9::des(&buf[..buf.len()])?;
|
|
return Ok(TimelinePersistentState {
|
|
tenant_id: oldstate.tenant_id,
|
|
timeline_id: oldstate.timeline_id,
|
|
mconf: Configuration::empty(),
|
|
acceptor_state: oldstate.acceptor_state,
|
|
server: oldstate.server,
|
|
proposer_uuid: oldstate.proposer_uuid,
|
|
timeline_start_lsn: oldstate.timeline_start_lsn,
|
|
local_start_lsn: oldstate.local_start_lsn,
|
|
commit_lsn: oldstate.commit_lsn,
|
|
backup_lsn: oldstate.backup_lsn,
|
|
peer_horizon_lsn: oldstate.peer_horizon_lsn,
|
|
remote_consistent_lsn: oldstate.remote_consistent_lsn,
|
|
partial_backup: oldstate.partial_backup,
|
|
eviction_state: oldstate.eviction_state,
|
|
creation_ts: std::time::SystemTime::UNIX_EPOCH,
|
|
});
|
|
}
|
|
|
|
// TODO: persist the file back to the disk after upgrade
|
|
// TODO: think about backward compatibility and rollbacks
|
|
|
|
bail!("unsupported safekeeper control file version {}", version)
|
|
}
|
|
|
|
// Used as a temp hack to make forward compatibility test work. Should be
|
|
// removed after PR adding v10 is merged.
|
|
pub fn downgrade_v10_to_v9(state: &TimelinePersistentState) -> TimelinePersistentStateV9 {
|
|
assert!(state.mconf.generation == INVALID_GENERATION);
|
|
TimelinePersistentStateV9 {
|
|
tenant_id: state.tenant_id,
|
|
timeline_id: state.timeline_id,
|
|
acceptor_state: state.acceptor_state.clone(),
|
|
server: state.server.clone(),
|
|
proposer_uuid: state.proposer_uuid,
|
|
timeline_start_lsn: state.timeline_start_lsn,
|
|
local_start_lsn: state.local_start_lsn,
|
|
commit_lsn: state.commit_lsn,
|
|
backup_lsn: state.backup_lsn,
|
|
peer_horizon_lsn: state.peer_horizon_lsn,
|
|
remote_consistent_lsn: state.remote_consistent_lsn,
|
|
peers: PersistedPeers(vec![]),
|
|
partial_backup: state.partial_backup.clone(),
|
|
eviction_state: state.eviction_state,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::str::FromStr;
|
|
|
|
use utils::Hex;
|
|
use utils::id::NodeId;
|
|
|
|
use super::*;
|
|
use crate::control_file_upgrade::PersistedPeerInfo;
|
|
|
|
#[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);
|
|
}
|
|
}
|