mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-13 00:12:54 +00:00
## Problem In https://github.com/neondatabase/neon/pull/5299, the new config-v1 tenant config file was added to hold the LocationConf type. We left the old config file in place for forward compat, and because running without generations (therefore without LocationConf) as still useful before the storage controller was ready for prime-time. Closes: https://github.com/neondatabase/neon/issues/5388 ## Summary of changes - Remove code for reading and writing the legacy config file - Remove Generation::Broken: it was unused. - Treat missing config file on disk as an error loading a tenant, rather than defaulting it. We can now remove LocationConf::default, and thereby guarantee that we never construct a tenant with a None generation. - Update some comments + add some assertions to clarify that Generation::None is only used in layer metadata, not in the state of a running tenant. - Update docker compose test to create tenants with a generation
179 lines
5.2 KiB
Rust
179 lines
5.2 KiB
Rust
use std::fmt::Debug;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Tenant generations are used to provide split-brain safety and allow
|
|
/// multiple pageservers to attach the same tenant concurrently.
|
|
///
|
|
/// See docs/rfcs/025-generation-numbers.md for detail on how generation
|
|
/// numbers are used.
|
|
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
|
pub enum Generation {
|
|
// The None Generation is used in the metadata of layers written before generations were
|
|
// introduced. A running Tenant always has a valid generation, but the layer metadata may
|
|
// include None generations.
|
|
None,
|
|
|
|
Valid(u32),
|
|
}
|
|
|
|
/// The Generation type represents a number associated with a Tenant, which
|
|
/// increments every time the tenant is attached to a new pageserver, or
|
|
/// an attached pageserver restarts.
|
|
///
|
|
/// It is included as a suffix in S3 keys, as a protection against split-brain
|
|
/// scenarios where pageservers might otherwise issue conflicting writes to
|
|
/// remote storage
|
|
impl Generation {
|
|
pub const MAX: Self = Self::Valid(u32::MAX);
|
|
|
|
/// Create a new Generation that represents a legacy key format with
|
|
/// no generation suffix
|
|
pub fn none() -> Self {
|
|
Self::None
|
|
}
|
|
|
|
pub const fn new(v: u32) -> Self {
|
|
Self::Valid(v)
|
|
}
|
|
|
|
pub fn is_none(&self) -> bool {
|
|
matches!(self, Self::None)
|
|
}
|
|
|
|
#[track_caller]
|
|
pub fn get_suffix(&self) -> impl std::fmt::Display {
|
|
match self {
|
|
Self::Valid(v) => GenerationFileSuffix(Some(*v)),
|
|
Self::None => GenerationFileSuffix(None),
|
|
}
|
|
}
|
|
|
|
/// `suffix` is the part after "-" in a key
|
|
///
|
|
/// Returns None if parsing was unsuccessful
|
|
pub fn parse_suffix(suffix: &str) -> Option<Generation> {
|
|
u32::from_str_radix(suffix, 16).map(Generation::new).ok()
|
|
}
|
|
|
|
#[track_caller]
|
|
pub fn previous(&self) -> Generation {
|
|
match self {
|
|
Self::Valid(n) => {
|
|
if *n == 0 {
|
|
// Since a tenant may be upgraded from a pre-generations state, interpret the "previous" generation
|
|
// to 0 as being "no generation".
|
|
Self::None
|
|
} else {
|
|
Self::Valid(n - 1)
|
|
}
|
|
}
|
|
Self::None => Self::None,
|
|
}
|
|
}
|
|
|
|
#[track_caller]
|
|
pub fn next(&self) -> Generation {
|
|
match self {
|
|
Self::Valid(n) => Self::Valid(*n + 1),
|
|
Self::None => Self::Valid(1),
|
|
}
|
|
}
|
|
|
|
pub fn into(self) -> Option<u32> {
|
|
if let Self::Valid(v) = self {
|
|
Some(v)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
struct GenerationFileSuffix(Option<u32>);
|
|
|
|
impl std::fmt::Display for GenerationFileSuffix {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
if let Some(g) = self.0 {
|
|
write!(f, "-{g:08x}")
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Serialize for Generation {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
if let Self::Valid(v) = self {
|
|
v.serialize(serializer)
|
|
} else {
|
|
// We should never be asked to serialize a None. Structures
|
|
// that include an optional generation should convert None to an
|
|
// Option<Generation>::None
|
|
Err(serde::ser::Error::custom(
|
|
"Tried to serialize invalid generation ({self})",
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for Generation {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
Ok(Self::Valid(u32::deserialize(deserializer)?))
|
|
}
|
|
}
|
|
|
|
// We intentionally do not implement Display for Generation, to reduce the
|
|
// risk of a bug where the generation is used in a format!() string directly
|
|
// instead of using get_suffix().
|
|
impl Debug for Generation {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::Valid(v) => {
|
|
write!(f, "{:08x}", v)
|
|
}
|
|
Self::None => {
|
|
write!(f, "<none>")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn generation_gt() {
|
|
// Important that a None generation compares less than a valid one, during upgrades from
|
|
// pre-generation systems.
|
|
assert!(Generation::none() < Generation::new(0));
|
|
assert!(Generation::none() < Generation::new(1));
|
|
}
|
|
|
|
#[test]
|
|
fn suffix_is_stable() {
|
|
use std::fmt::Write as _;
|
|
|
|
// the suffix must remain stable through-out the pageserver remote storage evolution and
|
|
// not be changed accidentially without thinking about migration
|
|
let examples = [
|
|
(line!(), Generation::None, ""),
|
|
(line!(), Generation::Valid(0), "-00000000"),
|
|
(line!(), Generation::Valid(u32::MAX), "-ffffffff"),
|
|
];
|
|
|
|
let mut s = String::new();
|
|
for (line, gen, expected) in examples {
|
|
s.clear();
|
|
write!(s, "{}", &gen.get_suffix()).expect("string grows");
|
|
assert_eq!(s, expected, "example on {line}");
|
|
}
|
|
}
|
|
}
|