LCOV - differential code coverage report
Current view: top level - pageserver/src/tenant - config.rs (source / functions) Coverage Total Hit UBC CBC
Current: f6946e90941b557c917ac98cd5a7e9506d180f3e.info Lines: 82.4 % 239 197 42 197
Current Date: 2023-10-19 02:04:12 Functions: 32.6 % 236 77 159 77
Baseline: c8637f37369098875162f194f92736355783b050.info
Baseline Date: 2023-10-18 20:25:20

           TLA  Line data    Source code
       1                 : //! Functions for handling per-tenant configuration options
       2                 : //!
       3                 : //! If tenant is created with --config option,
       4                 : //! the tenant-specific config will be stored in tenant's directory.
       5                 : //! Otherwise, global pageserver's config is used.
       6                 : //!
       7                 : //! If the tenant config file is corrupted, the tenant will be disabled.
       8                 : //! We cannot use global or default config instead, because wrong settings
       9                 : //! may lead to a data loss.
      10                 : //!
      11                 : use anyhow::Context;
      12                 : use pageserver_api::models;
      13                 : use serde::{Deserialize, Serialize};
      14                 : use std::num::NonZeroU64;
      15                 : use std::time::Duration;
      16                 : use utils::generation::Generation;
      17                 : 
      18                 : pub mod defaults {
      19                 :     // FIXME: This current value is very low. I would imagine something like 1 GB or 10 GB
      20                 :     // would be more appropriate. But a low value forces the code to be exercised more,
      21                 :     // which is good for now to trigger bugs.
      22                 :     // This parameter actually determines L0 layer file size.
      23                 :     pub const DEFAULT_CHECKPOINT_DISTANCE: u64 = 256 * 1024 * 1024;
      24                 :     pub const DEFAULT_CHECKPOINT_TIMEOUT: &str = "10 m";
      25                 : 
      26                 :     // Target file size, when creating image and delta layers.
      27                 :     // This parameter determines L1 layer file size.
      28                 :     pub const DEFAULT_COMPACTION_TARGET_SIZE: u64 = 128 * 1024 * 1024;
      29                 : 
      30                 :     pub const DEFAULT_COMPACTION_PERIOD: &str = "20 s";
      31                 :     pub const DEFAULT_COMPACTION_THRESHOLD: usize = 10;
      32                 : 
      33                 :     pub const DEFAULT_GC_HORIZON: u64 = 64 * 1024 * 1024;
      34                 : 
      35                 :     // Large DEFAULT_GC_PERIOD is fine as long as PITR_INTERVAL is larger.
      36                 :     // If there's a need to decrease this value, first make sure that GC
      37                 :     // doesn't hold a layer map write lock for non-trivial operations.
      38                 :     // Relevant: https://github.com/neondatabase/neon/issues/3394
      39                 :     pub const DEFAULT_GC_PERIOD: &str = "1 hr";
      40                 :     pub const DEFAULT_IMAGE_CREATION_THRESHOLD: usize = 3;
      41                 :     pub const DEFAULT_PITR_INTERVAL: &str = "7 days";
      42                 :     pub const DEFAULT_WALRECEIVER_CONNECT_TIMEOUT: &str = "10 seconds";
      43                 :     pub const DEFAULT_WALRECEIVER_LAGGING_WAL_TIMEOUT: &str = "10 seconds";
      44                 :     pub const DEFAULT_MAX_WALRECEIVER_LSN_WAL_LAG: u64 = 10 * 1024 * 1024;
      45                 :     pub const DEFAULT_EVICTIONS_LOW_RESIDENCE_DURATION_METRIC_THRESHOLD: &str = "24 hour";
      46                 : }
      47                 : 
      48 CBC         748 : #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
      49                 : pub(crate) enum AttachmentMode {
      50                 :     /// Our generation is current as far as we know, and as far as we know we are the only attached
      51                 :     /// pageserver.  This is the "normal" attachment mode.
      52                 :     Single,
      53                 :     /// Our generation number is current as far as we know, but we are advised that another
      54                 :     /// pageserver is still attached, and therefore to avoid executing deletions.   This is
      55                 :     /// the attachment mode of a pagesever that is the destination of a migration.
      56                 :     Multi,
      57                 :     /// Our generation number is superseded, or about to be superseded.  We are advised
      58                 :     /// to avoid remote storage writes if possible, and to avoid sending billing data.  This
      59                 :     /// is the attachment mode of a pageserver that is the origin of a migration.
      60                 :     Stale,
      61                 : }
      62                 : 
      63             748 : #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
      64                 : pub(crate) struct AttachedLocationConfig {
      65                 :     pub(crate) generation: Generation,
      66                 :     pub(crate) attach_mode: AttachmentMode,
      67                 :     // TODO: add a flag to override AttachmentMode's policies under
      68                 :     // disk pressure (i.e. unblock uploads under disk pressure in Stale
      69                 :     // state, unblock deletions after timeout in Multi state)
      70                 : }
      71                 : 
      72 UBC           0 : #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
      73                 : pub(crate) struct SecondaryLocationConfig {
      74                 :     /// If true, keep the local cache warm by polling remote storage
      75                 :     pub(crate) warm: bool,
      76                 : }
      77                 : 
      78 CBC          28 : #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
      79                 : pub(crate) enum LocationMode {
      80                 :     Attached(AttachedLocationConfig),
      81                 :     Secondary(SecondaryLocationConfig),
      82                 : }
      83                 : 
      84                 : /// Per-tenant, per-pageserver configuration.  All pageservers use the same TenantConf,
      85                 : /// but have distinct LocationConf.
      86              70 : #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
      87                 : pub(crate) struct LocationConf {
      88                 :     /// The location-specific part of the configuration, describes the operating
      89                 :     /// mode of this pageserver for this tenant.
      90                 :     pub(crate) mode: LocationMode,
      91                 :     /// The pan-cluster tenant configuration, the same on all locations
      92                 :     pub(crate) tenant_conf: TenantConfOpt,
      93                 : }
      94                 : 
      95                 : impl std::fmt::Debug for LocationConf {
      96 UBC           0 :     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
      97               0 :         match &self.mode {
      98               0 :             LocationMode::Attached(conf) => {
      99               0 :                 write!(
     100               0 :                     f,
     101               0 :                     "Attached {:?}, gen={:?}",
     102               0 :                     conf.attach_mode, conf.generation
     103               0 :                 )
     104                 :             }
     105               0 :             LocationMode::Secondary(conf) => {
     106               0 :                 write!(f, "Secondary, warm={}", conf.warm)
     107                 :             }
     108                 :         }
     109               0 :     }
     110                 : }
     111                 : 
     112                 : impl AttachedLocationConfig {
     113                 :     /// Consult attachment mode to determine whether we are currently permitted
     114                 :     /// to delete layers.  This is only advisory, not required for data safety.
     115                 :     /// See [`AttachmentMode`] for more context.
     116 CBC         647 :     pub(crate) fn may_delete_layers_hint(&self) -> bool {
     117             647 :         // TODO: add an override for disk pressure in AttachedLocationConfig,
     118             647 :         // and respect it here.
     119             647 :         match &self.attach_mode {
     120             647 :             AttachmentMode::Single => true,
     121                 :             AttachmentMode::Multi | AttachmentMode::Stale => {
     122                 :                 // In Multi mode we avoid doing deletions because some other
     123                 :                 // attached pageserver might get 404 while trying to read
     124                 :                 // a layer we delete which is still referenced in their metadata.
     125                 :                 //
     126                 :                 // In Stale mode, we avoid doing deletions because we expect
     127                 :                 // that they would ultimately fail validation in the deletion
     128                 :                 // queue due to our stale generation.
     129 UBC           0 :                 false
     130                 :             }
     131                 :         }
     132 CBC         647 :     }
     133                 : 
     134                 :     /// Whether we are currently hinted that it is worthwhile to upload layers.
     135                 :     /// This is only advisory, not required for data safety.
     136                 :     /// See [`AttachmentMode`] for more context.
     137             241 :     pub(crate) fn may_upload_layers_hint(&self) -> bool {
     138             241 :         // TODO: add an override for disk pressure in AttachedLocationConfig,
     139             241 :         // and respect it here.
     140             241 :         match &self.attach_mode {
     141             241 :             AttachmentMode::Single | AttachmentMode::Multi => true,
     142                 :             AttachmentMode::Stale => {
     143                 :                 // In Stale mode, we avoid doing uploads because we expect that
     144                 :                 // our replacement pageserver will already have started its own
     145                 :                 // IndexPart that will never reference layers we upload: it is
     146                 :                 // wasteful.
     147 UBC           0 :                 false
     148                 :             }
     149                 :         }
     150 CBC         241 :     }
     151                 : }
     152                 : 
     153                 : impl LocationConf {
     154                 :     /// For use when loading from a legacy configuration: presence of a tenant
     155                 :     /// implies it is in AttachmentMode::Single, which used to be the only
     156                 :     /// possible state.  This function should eventually be removed.
     157             762 :     pub(crate) fn attached_single(tenant_conf: TenantConfOpt, generation: Generation) -> Self {
     158             762 :         Self {
     159             762 :             mode: LocationMode::Attached(AttachedLocationConfig {
     160             762 :                 generation,
     161             762 :                 attach_mode: AttachmentMode::Single,
     162             762 :             }),
     163             762 :             tenant_conf,
     164             762 :         }
     165             762 :     }
     166                 : 
     167                 :     /// For use when attaching/re-attaching: update the generation stored in this
     168                 :     /// structure.  If we were in a secondary state, promote to attached (posession
     169                 :     /// of a fresh generation implies this).
     170             215 :     pub(crate) fn attach_in_generation(&mut self, generation: Generation) {
     171             215 :         match &mut self.mode {
     172             215 :             LocationMode::Attached(attach_conf) => {
     173             215 :                 attach_conf.generation = generation;
     174             215 :             }
     175                 :             LocationMode::Secondary(_) => {
     176                 :                 // We are promoted to attached by the control plane's re-attach response
     177 UBC           0 :                 self.mode = LocationMode::Attached(AttachedLocationConfig {
     178               0 :                     generation,
     179               0 :                     attach_mode: AttachmentMode::Single,
     180               0 :                 })
     181                 :             }
     182                 :         }
     183 CBC         215 :     }
     184                 : 
     185 UBC           0 :     pub(crate) fn try_from(conf: &'_ models::LocationConfig) -> anyhow::Result<Self> {
     186               0 :         let tenant_conf = TenantConfOpt::try_from(&conf.tenant_conf)?;
     187                 : 
     188               0 :         fn get_generation(conf: &'_ models::LocationConfig) -> Result<Generation, anyhow::Error> {
     189               0 :             conf.generation
     190               0 :                 .ok_or_else(|| anyhow::anyhow!("Generation must be set when attaching"))
     191               0 :         }
     192                 : 
     193               0 :         let mode = match &conf.mode {
     194                 :             models::LocationConfigMode::AttachedMulti => {
     195                 :                 LocationMode::Attached(AttachedLocationConfig {
     196               0 :                     generation: get_generation(conf)?,
     197               0 :                     attach_mode: AttachmentMode::Multi,
     198                 :                 })
     199                 :             }
     200                 :             models::LocationConfigMode::AttachedSingle => {
     201                 :                 LocationMode::Attached(AttachedLocationConfig {
     202               0 :                     generation: get_generation(conf)?,
     203               0 :                     attach_mode: AttachmentMode::Single,
     204                 :                 })
     205                 :             }
     206                 :             models::LocationConfigMode::AttachedStale => {
     207                 :                 LocationMode::Attached(AttachedLocationConfig {
     208               0 :                     generation: get_generation(conf)?,
     209               0 :                     attach_mode: AttachmentMode::Stale,
     210                 :                 })
     211                 :             }
     212                 :             models::LocationConfigMode::Secondary => {
     213               0 :                 anyhow::ensure!(conf.generation.is_none());
     214                 : 
     215               0 :                 let warm = conf
     216               0 :                     .secondary_conf
     217               0 :                     .as_ref()
     218               0 :                     .map(|c| c.warm)
     219               0 :                     .unwrap_or(false);
     220               0 :                 LocationMode::Secondary(SecondaryLocationConfig { warm })
     221                 :             }
     222                 :             models::LocationConfigMode::Detached => {
     223                 :                 // Should not have been called: API code should translate this mode
     224                 :                 // into a detach rather than trying to decode it as a LocationConf
     225               0 :                 return Err(anyhow::anyhow!("Cannot decode a Detached configuration"));
     226                 :             }
     227                 :         };
     228                 : 
     229               0 :         Ok(Self { mode, tenant_conf })
     230               0 :     }
     231                 : }
     232                 : 
     233                 : impl Default for LocationConf {
     234                 :     // TODO: this should be removed once tenant loading can guarantee that we are never
     235                 :     // loading from a directory without a configuration.
     236                 :     // => tech debt since https://github.com/neondatabase/neon/issues/1555
     237 CBC           4 :     fn default() -> Self {
     238               4 :         Self {
     239               4 :             mode: LocationMode::Attached(AttachedLocationConfig {
     240               4 :                 generation: Generation::none(),
     241               4 :                 attach_mode: AttachmentMode::Single,
     242               4 :             }),
     243               4 :             tenant_conf: TenantConfOpt::default(),
     244               4 :         }
     245               4 :     }
     246                 : }
     247                 : 
     248                 : /// A tenant's calcuated configuration, which is the result of merging a
     249                 : /// tenant's TenantConfOpt with the global TenantConf from PageServerConf.
     250                 : ///
     251                 : /// For storing and transmitting individual tenant's configuration, see
     252                 : /// TenantConfOpt.
     253              40 : #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
     254                 : pub struct TenantConf {
     255                 :     // Flush out an inmemory layer, if it's holding WAL older than this
     256                 :     // This puts a backstop on how much WAL needs to be re-digested if the
     257                 :     // page server crashes.
     258                 :     // This parameter actually determines L0 layer file size.
     259                 :     pub checkpoint_distance: u64,
     260                 :     // Inmemory layer is also flushed at least once in checkpoint_timeout to
     261                 :     // eventually upload WAL after activity is stopped.
     262                 :     #[serde(with = "humantime_serde")]
     263                 :     pub checkpoint_timeout: Duration,
     264                 :     // Target file size, when creating image and delta layers.
     265                 :     // This parameter determines L1 layer file size.
     266                 :     pub compaction_target_size: u64,
     267                 :     // How often to check if there's compaction work to be done.
     268                 :     // Duration::ZERO means automatic compaction is disabled.
     269                 :     #[serde(with = "humantime_serde")]
     270                 :     pub compaction_period: Duration,
     271                 :     // Level0 delta layer threshold for compaction.
     272                 :     pub compaction_threshold: usize,
     273                 :     // Determines how much history is retained, to allow
     274                 :     // branching and read replicas at an older point in time.
     275                 :     // The unit is #of bytes of WAL.
     276                 :     // Page versions older than this are garbage collected away.
     277                 :     pub gc_horizon: u64,
     278                 :     // Interval at which garbage collection is triggered.
     279                 :     // Duration::ZERO means automatic GC is disabled
     280                 :     #[serde(with = "humantime_serde")]
     281                 :     pub gc_period: Duration,
     282                 :     // Delta layer churn threshold to create L1 image layers.
     283                 :     pub image_creation_threshold: usize,
     284                 :     // Determines how much history is retained, to allow
     285                 :     // branching and read replicas at an older point in time.
     286                 :     // The unit is time.
     287                 :     // Page versions older than this are garbage collected away.
     288                 :     #[serde(with = "humantime_serde")]
     289                 :     pub pitr_interval: Duration,
     290                 :     /// Maximum amount of time to wait while opening a connection to receive wal, before erroring.
     291                 :     #[serde(with = "humantime_serde")]
     292                 :     pub walreceiver_connect_timeout: Duration,
     293                 :     /// Considers safekeepers stalled after no WAL updates were received longer than this threshold.
     294                 :     /// A stalled safekeeper will be changed to a newer one when it appears.
     295                 :     #[serde(with = "humantime_serde")]
     296                 :     pub lagging_wal_timeout: Duration,
     297                 :     /// Considers safekeepers lagging when their WAL is behind another safekeeper for more than this threshold.
     298                 :     /// A lagging safekeeper will be changed after `lagging_wal_timeout` time elapses since the last WAL update,
     299                 :     /// to avoid eager reconnects.
     300                 :     pub max_lsn_wal_lag: NonZeroU64,
     301                 :     pub trace_read_requests: bool,
     302                 :     pub eviction_policy: EvictionPolicy,
     303                 :     pub min_resident_size_override: Option<u64>,
     304                 :     // See the corresponding metric's help string.
     305                 :     #[serde(with = "humantime_serde")]
     306                 :     pub evictions_low_residence_duration_metric_threshold: Duration,
     307                 :     pub gc_feedback: bool,
     308                 : }
     309                 : 
     310                 : /// Same as TenantConf, but this struct preserves the information about
     311                 : /// which parameters are set and which are not.
     312            2769 : #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
     313                 : pub struct TenantConfOpt {
     314                 :     #[serde(skip_serializing_if = "Option::is_none")]
     315                 :     #[serde(default)]
     316                 :     pub checkpoint_distance: Option<u64>,
     317                 : 
     318                 :     #[serde(skip_serializing_if = "Option::is_none")]
     319                 :     #[serde(with = "humantime_serde")]
     320                 :     #[serde(default)]
     321                 :     pub checkpoint_timeout: Option<Duration>,
     322                 : 
     323                 :     #[serde(skip_serializing_if = "Option::is_none")]
     324                 :     #[serde(default)]
     325                 :     pub compaction_target_size: Option<u64>,
     326                 : 
     327                 :     #[serde(skip_serializing_if = "Option::is_none")]
     328                 :     #[serde(with = "humantime_serde")]
     329                 :     #[serde(default)]
     330                 :     pub compaction_period: Option<Duration>,
     331                 : 
     332                 :     #[serde(skip_serializing_if = "Option::is_none")]
     333                 :     #[serde(default)]
     334                 :     pub compaction_threshold: Option<usize>,
     335                 : 
     336                 :     #[serde(skip_serializing_if = "Option::is_none")]
     337                 :     #[serde(default)]
     338                 :     pub gc_horizon: Option<u64>,
     339                 : 
     340                 :     #[serde(skip_serializing_if = "Option::is_none")]
     341                 :     #[serde(with = "humantime_serde")]
     342                 :     #[serde(default)]
     343                 :     pub gc_period: Option<Duration>,
     344                 : 
     345                 :     #[serde(skip_serializing_if = "Option::is_none")]
     346                 :     #[serde(default)]
     347                 :     pub image_creation_threshold: Option<usize>,
     348                 : 
     349                 :     #[serde(skip_serializing_if = "Option::is_none")]
     350                 :     #[serde(with = "humantime_serde")]
     351                 :     #[serde(default)]
     352                 :     pub pitr_interval: Option<Duration>,
     353                 : 
     354                 :     #[serde(skip_serializing_if = "Option::is_none")]
     355                 :     #[serde(with = "humantime_serde")]
     356                 :     #[serde(default)]
     357                 :     pub walreceiver_connect_timeout: Option<Duration>,
     358                 : 
     359                 :     #[serde(skip_serializing_if = "Option::is_none")]
     360                 :     #[serde(with = "humantime_serde")]
     361                 :     #[serde(default)]
     362                 :     pub lagging_wal_timeout: Option<Duration>,
     363                 : 
     364                 :     #[serde(skip_serializing_if = "Option::is_none")]
     365                 :     #[serde(default)]
     366                 :     pub max_lsn_wal_lag: Option<NonZeroU64>,
     367                 : 
     368                 :     #[serde(skip_serializing_if = "Option::is_none")]
     369                 :     #[serde(default)]
     370                 :     pub trace_read_requests: Option<bool>,
     371                 : 
     372                 :     #[serde(skip_serializing_if = "Option::is_none")]
     373                 :     #[serde(default)]
     374                 :     pub eviction_policy: Option<EvictionPolicy>,
     375                 : 
     376                 :     #[serde(skip_serializing_if = "Option::is_none")]
     377                 :     #[serde(default)]
     378                 :     pub min_resident_size_override: Option<u64>,
     379                 : 
     380                 :     #[serde(skip_serializing_if = "Option::is_none")]
     381                 :     #[serde(with = "humantime_serde")]
     382                 :     #[serde(default)]
     383                 :     pub evictions_low_residence_duration_metric_threshold: Option<Duration>,
     384                 : 
     385                 :     #[serde(skip_serializing_if = "Option::is_none")]
     386                 :     #[serde(default)]
     387                 :     pub gc_feedback: Option<bool>,
     388                 : }
     389                 : 
     390              57 : #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
     391                 : #[serde(tag = "kind")]
     392                 : pub enum EvictionPolicy {
     393                 :     NoEviction,
     394                 :     LayerAccessThreshold(EvictionPolicyLayerAccessThreshold),
     395                 : }
     396                 : 
     397                 : impl EvictionPolicy {
     398             931 :     pub fn discriminant_str(&self) -> &'static str {
     399             931 :         match self {
     400             905 :             EvictionPolicy::NoEviction => "NoEviction",
     401              26 :             EvictionPolicy::LayerAccessThreshold(_) => "LayerAccessThreshold",
     402                 :         }
     403             931 :     }
     404                 : }
     405                 : 
     406              84 : #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
     407                 : pub struct EvictionPolicyLayerAccessThreshold {
     408                 :     #[serde(with = "humantime_serde")]
     409                 :     pub period: Duration,
     410                 :     #[serde(with = "humantime_serde")]
     411                 :     pub threshold: Duration,
     412                 : }
     413                 : 
     414                 : impl TenantConfOpt {
     415             958 :     pub fn merge(&self, global_conf: TenantConf) -> TenantConf {
     416             958 :         TenantConf {
     417             958 :             checkpoint_distance: self
     418             958 :                 .checkpoint_distance
     419             958 :                 .unwrap_or(global_conf.checkpoint_distance),
     420             958 :             checkpoint_timeout: self
     421             958 :                 .checkpoint_timeout
     422             958 :                 .unwrap_or(global_conf.checkpoint_timeout),
     423             958 :             compaction_target_size: self
     424             958 :                 .compaction_target_size
     425             958 :                 .unwrap_or(global_conf.compaction_target_size),
     426             958 :             compaction_period: self
     427             958 :                 .compaction_period
     428             958 :                 .unwrap_or(global_conf.compaction_period),
     429             958 :             compaction_threshold: self
     430             958 :                 .compaction_threshold
     431             958 :                 .unwrap_or(global_conf.compaction_threshold),
     432             958 :             gc_horizon: self.gc_horizon.unwrap_or(global_conf.gc_horizon),
     433             958 :             gc_period: self.gc_period.unwrap_or(global_conf.gc_period),
     434             958 :             image_creation_threshold: self
     435             958 :                 .image_creation_threshold
     436             958 :                 .unwrap_or(global_conf.image_creation_threshold),
     437             958 :             pitr_interval: self.pitr_interval.unwrap_or(global_conf.pitr_interval),
     438             958 :             walreceiver_connect_timeout: self
     439             958 :                 .walreceiver_connect_timeout
     440             958 :                 .unwrap_or(global_conf.walreceiver_connect_timeout),
     441             958 :             lagging_wal_timeout: self
     442             958 :                 .lagging_wal_timeout
     443             958 :                 .unwrap_or(global_conf.lagging_wal_timeout),
     444             958 :             max_lsn_wal_lag: self.max_lsn_wal_lag.unwrap_or(global_conf.max_lsn_wal_lag),
     445             958 :             trace_read_requests: self
     446             958 :                 .trace_read_requests
     447             958 :                 .unwrap_or(global_conf.trace_read_requests),
     448             958 :             eviction_policy: self.eviction_policy.unwrap_or(global_conf.eviction_policy),
     449             958 :             min_resident_size_override: self
     450             958 :                 .min_resident_size_override
     451             958 :                 .or(global_conf.min_resident_size_override),
     452             958 :             evictions_low_residence_duration_metric_threshold: self
     453             958 :                 .evictions_low_residence_duration_metric_threshold
     454             958 :                 .unwrap_or(global_conf.evictions_low_residence_duration_metric_threshold),
     455             958 :             gc_feedback: self.gc_feedback.unwrap_or(global_conf.gc_feedback),
     456             958 :         }
     457             958 :     }
     458                 : }
     459                 : 
     460                 : impl Default for TenantConf {
     461            1923 :     fn default() -> Self {
     462            1923 :         use defaults::*;
     463            1923 :         Self {
     464            1923 :             checkpoint_distance: DEFAULT_CHECKPOINT_DISTANCE,
     465            1923 :             checkpoint_timeout: humantime::parse_duration(DEFAULT_CHECKPOINT_TIMEOUT)
     466            1923 :                 .expect("cannot parse default checkpoint timeout"),
     467            1923 :             compaction_target_size: DEFAULT_COMPACTION_TARGET_SIZE,
     468            1923 :             compaction_period: humantime::parse_duration(DEFAULT_COMPACTION_PERIOD)
     469            1923 :                 .expect("cannot parse default compaction period"),
     470            1923 :             compaction_threshold: DEFAULT_COMPACTION_THRESHOLD,
     471            1923 :             gc_horizon: DEFAULT_GC_HORIZON,
     472            1923 :             gc_period: humantime::parse_duration(DEFAULT_GC_PERIOD)
     473            1923 :                 .expect("cannot parse default gc period"),
     474            1923 :             image_creation_threshold: DEFAULT_IMAGE_CREATION_THRESHOLD,
     475            1923 :             pitr_interval: humantime::parse_duration(DEFAULT_PITR_INTERVAL)
     476            1923 :                 .expect("cannot parse default PITR interval"),
     477            1923 :             walreceiver_connect_timeout: humantime::parse_duration(
     478            1923 :                 DEFAULT_WALRECEIVER_CONNECT_TIMEOUT,
     479            1923 :             )
     480            1923 :             .expect("cannot parse default walreceiver connect timeout"),
     481            1923 :             lagging_wal_timeout: humantime::parse_duration(DEFAULT_WALRECEIVER_LAGGING_WAL_TIMEOUT)
     482            1923 :                 .expect("cannot parse default walreceiver lagging wal timeout"),
     483            1923 :             max_lsn_wal_lag: NonZeroU64::new(DEFAULT_MAX_WALRECEIVER_LSN_WAL_LAG)
     484            1923 :                 .expect("cannot parse default max walreceiver Lsn wal lag"),
     485            1923 :             trace_read_requests: false,
     486            1923 :             eviction_policy: EvictionPolicy::NoEviction,
     487            1923 :             min_resident_size_override: None,
     488            1923 :             evictions_low_residence_duration_metric_threshold: humantime::parse_duration(
     489            1923 :                 DEFAULT_EVICTIONS_LOW_RESIDENCE_DURATION_METRIC_THRESHOLD,
     490            1923 :             )
     491            1923 :             .expect("cannot parse default evictions_low_residence_duration_metric_threshold"),
     492            1923 :             gc_feedback: false,
     493            1923 :         }
     494            1923 :     }
     495                 : }
     496                 : 
     497                 : // Helper function to standardize the error messages we produce on bad durations
     498                 : //
     499                 : // Intended to be used with anyhow's `with_context`, e.g.:
     500                 : //
     501                 : //   let value = result.with_context(bad_duration("name", &value))?;
     502                 : //
     503             352 : fn bad_duration<'a>(field_name: &'static str, value: &'a str) -> impl 'a + Fn() -> String {
     504 UBC           0 :     move || format!("Cannot parse `{field_name}` duration {value:?}")
     505 CBC         352 : }
     506                 : 
     507                 : impl TryFrom<&'_ models::TenantConfig> for TenantConfOpt {
     508                 :     type Error = anyhow::Error;
     509                 : 
     510             532 :     fn try_from(request_data: &'_ models::TenantConfig) -> Result<Self, Self::Error> {
     511             532 :         let mut tenant_conf = TenantConfOpt::default();
     512                 : 
     513             532 :         if let Some(gc_period) = &request_data.gc_period {
     514                 :             tenant_conf.gc_period = Some(
     515             155 :                 humantime::parse_duration(gc_period)
     516             155 :                     .with_context(bad_duration("gc_period", gc_period))?,
     517                 :             );
     518             377 :         }
     519             532 :         tenant_conf.gc_horizon = request_data.gc_horizon;
     520             532 :         tenant_conf.image_creation_threshold = request_data.image_creation_threshold;
     521                 : 
     522             532 :         if let Some(pitr_interval) = &request_data.pitr_interval {
     523                 :             tenant_conf.pitr_interval = Some(
     524              27 :                 humantime::parse_duration(pitr_interval)
     525              27 :                     .with_context(bad_duration("pitr_interval", pitr_interval))?,
     526                 :             );
     527             505 :         }
     528                 : 
     529             532 :         if let Some(walreceiver_connect_timeout) = &request_data.walreceiver_connect_timeout {
     530                 :             tenant_conf.walreceiver_connect_timeout = Some(
     531               2 :                 humantime::parse_duration(walreceiver_connect_timeout).with_context(
     532               2 :                     bad_duration("walreceiver_connect_timeout", walreceiver_connect_timeout),
     533               2 :                 )?,
     534                 :             );
     535             530 :         }
     536             532 :         if let Some(lagging_wal_timeout) = &request_data.lagging_wal_timeout {
     537                 :             tenant_conf.lagging_wal_timeout = Some(
     538               2 :                 humantime::parse_duration(lagging_wal_timeout)
     539               2 :                     .with_context(bad_duration("lagging_wal_timeout", lagging_wal_timeout))?,
     540                 :             );
     541             530 :         }
     542             532 :         if let Some(max_lsn_wal_lag) = request_data.max_lsn_wal_lag {
     543               3 :             tenant_conf.max_lsn_wal_lag = Some(max_lsn_wal_lag);
     544             529 :         }
     545             532 :         if let Some(trace_read_requests) = request_data.trace_read_requests {
     546               3 :             tenant_conf.trace_read_requests = Some(trace_read_requests);
     547             529 :         }
     548                 : 
     549             532 :         tenant_conf.checkpoint_distance = request_data.checkpoint_distance;
     550             532 :         if let Some(checkpoint_timeout) = &request_data.checkpoint_timeout {
     551                 :             tenant_conf.checkpoint_timeout = Some(
     552               7 :                 humantime::parse_duration(checkpoint_timeout)
     553               7 :                     .with_context(bad_duration("checkpoint_timeout", checkpoint_timeout))?,
     554                 :             );
     555             525 :         }
     556                 : 
     557             532 :         tenant_conf.compaction_target_size = request_data.compaction_target_size;
     558             532 :         tenant_conf.compaction_threshold = request_data.compaction_threshold;
     559                 : 
     560             532 :         if let Some(compaction_period) = &request_data.compaction_period {
     561                 :             tenant_conf.compaction_period = Some(
     562             152 :                 humantime::parse_duration(compaction_period)
     563             152 :                     .with_context(bad_duration("compaction_period", compaction_period))?,
     564                 :             );
     565             380 :         }
     566                 : 
     567             532 :         if let Some(eviction_policy) = &request_data.eviction_policy {
     568                 :             tenant_conf.eviction_policy = Some(
     569               6 :                 serde::Deserialize::deserialize(eviction_policy)
     570               6 :                     .context("parse field `eviction_policy`")?,
     571                 :             );
     572             526 :         }
     573                 : 
     574             532 :         tenant_conf.min_resident_size_override = request_data.min_resident_size_override;
     575                 : 
     576               7 :         if let Some(evictions_low_residence_duration_metric_threshold) =
     577             532 :             &request_data.evictions_low_residence_duration_metric_threshold
     578                 :         {
     579                 :             tenant_conf.evictions_low_residence_duration_metric_threshold = Some(
     580               7 :                 humantime::parse_duration(evictions_low_residence_duration_metric_threshold)
     581               7 :                     .with_context(bad_duration(
     582               7 :                         "evictions_low_residence_duration_metric_threshold",
     583               7 :                         evictions_low_residence_duration_metric_threshold,
     584               7 :                     ))?,
     585                 :             );
     586             525 :         }
     587             532 :         tenant_conf.gc_feedback = request_data.gc_feedback;
     588             532 : 
     589             532 :         Ok(tenant_conf)
     590             532 :     }
     591                 : }
     592                 : 
     593                 : #[cfg(test)]
     594                 : mod tests {
     595                 :     use super::*;
     596                 : 
     597               1 :     #[test]
     598               1 :     fn de_serializing_pageserver_config_omits_empty_values() {
     599               1 :         let small_conf = TenantConfOpt {
     600               1 :             gc_horizon: Some(42),
     601               1 :             ..TenantConfOpt::default()
     602               1 :         };
     603               1 : 
     604               1 :         let toml_form = toml_edit::ser::to_string(&small_conf).unwrap();
     605               1 :         assert_eq!(toml_form, "gc_horizon = 42\n");
     606               1 :         assert_eq!(small_conf, toml_edit::de::from_str(&toml_form).unwrap());
     607                 : 
     608               1 :         let json_form = serde_json::to_string(&small_conf).unwrap();
     609               1 :         assert_eq!(json_form, "{\"gc_horizon\":42}");
     610               1 :         assert_eq!(small_conf, serde_json::from_str(&json_form).unwrap());
     611               1 :     }
     612                 : }
        

Generated by: LCOV version 2.1-beta