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 : }
|