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 std::num::NonZeroU64;
12 : use std::time::Duration;
13 :
14 : pub(crate) use pageserver_api::config::TenantConfigToml as TenantConf;
15 : use pageserver_api::models::{
16 : self, CompactionAlgorithmSettings, EvictionPolicy, TenantConfigPatch,
17 : };
18 : use pageserver_api::shard::{ShardCount, ShardIdentity, ShardNumber, ShardStripeSize};
19 : use serde::de::IntoDeserializer;
20 : use serde::{Deserialize, Serialize};
21 : use serde_json::Value;
22 : use utils::generation::Generation;
23 : use utils::postgres_client::PostgresClientProtocol;
24 :
25 0 : #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
26 : pub(crate) enum AttachmentMode {
27 : /// Our generation is current as far as we know, and as far as we know we are the only attached
28 : /// pageserver. This is the "normal" attachment mode.
29 : Single,
30 : /// Our generation number is current as far as we know, but we are advised that another
31 : /// pageserver is still attached, and therefore to avoid executing deletions. This is
32 : /// the attachment mode of a pagesever that is the destination of a migration.
33 : Multi,
34 : /// Our generation number is superseded, or about to be superseded. We are advised
35 : /// to avoid remote storage writes if possible, and to avoid sending billing data. This
36 : /// is the attachment mode of a pageserver that is the origin of a migration.
37 : Stale,
38 : }
39 :
40 0 : #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
41 : pub(crate) struct AttachedLocationConfig {
42 : pub(crate) generation: Generation,
43 : pub(crate) attach_mode: AttachmentMode,
44 : // TODO: add a flag to override AttachmentMode's policies under
45 : // disk pressure (i.e. unblock uploads under disk pressure in Stale
46 : // state, unblock deletions after timeout in Multi state)
47 : }
48 :
49 0 : #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
50 : pub(crate) struct SecondaryLocationConfig {
51 : /// If true, keep the local cache warm by polling remote storage
52 : pub(crate) warm: bool,
53 : }
54 :
55 0 : #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
56 : pub(crate) enum LocationMode {
57 : Attached(AttachedLocationConfig),
58 : Secondary(SecondaryLocationConfig),
59 : }
60 :
61 : /// Per-tenant, per-pageserver configuration. All pageservers use the same TenantConf,
62 : /// but have distinct LocationConf.
63 0 : #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
64 : pub(crate) struct LocationConf {
65 : /// The location-specific part of the configuration, describes the operating
66 : /// mode of this pageserver for this tenant.
67 : pub(crate) mode: LocationMode,
68 :
69 : /// The detailed shard identity. This structure is already scoped within
70 : /// a TenantShardId, but we need the full ShardIdentity to enable calculating
71 : /// key->shard mappings.
72 : #[serde(default = "ShardIdentity::unsharded")]
73 : #[serde(skip_serializing_if = "ShardIdentity::is_unsharded")]
74 : pub(crate) shard: ShardIdentity,
75 :
76 : /// The pan-cluster tenant configuration, the same on all locations
77 : pub(crate) tenant_conf: TenantConfOpt,
78 : }
79 :
80 : impl std::fmt::Debug for LocationConf {
81 0 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 0 : match &self.mode {
83 0 : LocationMode::Attached(conf) => {
84 0 : write!(
85 0 : f,
86 0 : "Attached {:?}, gen={:?}",
87 0 : conf.attach_mode, conf.generation
88 0 : )
89 : }
90 0 : LocationMode::Secondary(conf) => {
91 0 : write!(f, "Secondary, warm={}", conf.warm)
92 : }
93 : }
94 0 : }
95 : }
96 :
97 : impl AttachedLocationConfig {
98 : /// Consult attachment mode to determine whether we are currently permitted
99 : /// to delete layers. This is only advisory, not required for data safety.
100 : /// See [`AttachmentMode`] for more context.
101 2432 : pub(crate) fn may_delete_layers_hint(&self) -> bool {
102 2432 : // TODO: add an override for disk pressure in AttachedLocationConfig,
103 2432 : // and respect it here.
104 2432 : match &self.attach_mode {
105 2432 : AttachmentMode::Single => true,
106 : AttachmentMode::Multi | AttachmentMode::Stale => {
107 : // In Multi mode we avoid doing deletions because some other
108 : // attached pageserver might get 404 while trying to read
109 : // a layer we delete which is still referenced in their metadata.
110 : //
111 : // In Stale mode, we avoid doing deletions because we expect
112 : // that they would ultimately fail validation in the deletion
113 : // queue due to our stale generation.
114 0 : false
115 : }
116 : }
117 2432 : }
118 :
119 : /// Whether we are currently hinted that it is worthwhile to upload layers.
120 : /// This is only advisory, not required for data safety.
121 : /// See [`AttachmentMode`] for more context.
122 924 : pub(crate) fn may_upload_layers_hint(&self) -> bool {
123 924 : // TODO: add an override for disk pressure in AttachedLocationConfig,
124 924 : // and respect it here.
125 924 : match &self.attach_mode {
126 924 : AttachmentMode::Single | AttachmentMode::Multi => true,
127 : AttachmentMode::Stale => {
128 : // In Stale mode, we avoid doing uploads because we expect that
129 : // our replacement pageserver will already have started its own
130 : // IndexPart that will never reference layers we upload: it is
131 : // wasteful.
132 0 : false
133 : }
134 : }
135 924 : }
136 : }
137 :
138 : impl LocationConf {
139 : /// For use when loading from a legacy configuration: presence of a tenant
140 : /// implies it is in AttachmentMode::Single, which used to be the only
141 : /// possible state. This function should eventually be removed.
142 452 : pub(crate) fn attached_single(
143 452 : tenant_conf: TenantConfOpt,
144 452 : generation: Generation,
145 452 : shard_params: &models::ShardParameters,
146 452 : ) -> Self {
147 452 : Self {
148 452 : mode: LocationMode::Attached(AttachedLocationConfig {
149 452 : generation,
150 452 : attach_mode: AttachmentMode::Single,
151 452 : }),
152 452 : shard: ShardIdentity::from_params(ShardNumber(0), shard_params),
153 452 : tenant_conf,
154 452 : }
155 452 : }
156 :
157 : /// For use when attaching/re-attaching: update the generation stored in this
158 : /// structure. If we were in a secondary state, promote to attached (posession
159 : /// of a fresh generation implies this).
160 0 : pub(crate) fn attach_in_generation(&mut self, mode: AttachmentMode, generation: Generation) {
161 0 : match &mut self.mode {
162 0 : LocationMode::Attached(attach_conf) => {
163 0 : attach_conf.generation = generation;
164 0 : attach_conf.attach_mode = mode;
165 0 : }
166 : LocationMode::Secondary(_) => {
167 : // We are promoted to attached by the control plane's re-attach response
168 0 : self.mode = LocationMode::Attached(AttachedLocationConfig {
169 0 : generation,
170 0 : attach_mode: mode,
171 0 : })
172 : }
173 : }
174 0 : }
175 :
176 0 : pub(crate) fn try_from(conf: &'_ models::LocationConfig) -> anyhow::Result<Self> {
177 0 : let tenant_conf = TenantConfOpt::try_from(&conf.tenant_conf)?;
178 :
179 0 : fn get_generation(conf: &'_ models::LocationConfig) -> Result<Generation, anyhow::Error> {
180 0 : conf.generation
181 0 : .map(Generation::new)
182 0 : .ok_or_else(|| anyhow::anyhow!("Generation must be set when attaching"))
183 0 : }
184 :
185 0 : let mode = match &conf.mode {
186 : models::LocationConfigMode::AttachedMulti => {
187 : LocationMode::Attached(AttachedLocationConfig {
188 0 : generation: get_generation(conf)?,
189 0 : attach_mode: AttachmentMode::Multi,
190 : })
191 : }
192 : models::LocationConfigMode::AttachedSingle => {
193 : LocationMode::Attached(AttachedLocationConfig {
194 0 : generation: get_generation(conf)?,
195 0 : attach_mode: AttachmentMode::Single,
196 : })
197 : }
198 : models::LocationConfigMode::AttachedStale => {
199 : LocationMode::Attached(AttachedLocationConfig {
200 0 : generation: get_generation(conf)?,
201 0 : attach_mode: AttachmentMode::Stale,
202 : })
203 : }
204 : models::LocationConfigMode::Secondary => {
205 0 : anyhow::ensure!(conf.generation.is_none());
206 :
207 0 : let warm = conf
208 0 : .secondary_conf
209 0 : .as_ref()
210 0 : .map(|c| c.warm)
211 0 : .unwrap_or(false);
212 0 : LocationMode::Secondary(SecondaryLocationConfig { warm })
213 : }
214 : models::LocationConfigMode::Detached => {
215 : // Should not have been called: API code should translate this mode
216 : // into a detach rather than trying to decode it as a LocationConf
217 0 : return Err(anyhow::anyhow!("Cannot decode a Detached configuration"));
218 : }
219 : };
220 :
221 0 : let shard = if conf.shard_count == 0 {
222 0 : ShardIdentity::unsharded()
223 : } else {
224 0 : ShardIdentity::new(
225 0 : ShardNumber(conf.shard_number),
226 0 : ShardCount::new(conf.shard_count),
227 0 : ShardStripeSize(conf.shard_stripe_size),
228 0 : )?
229 : };
230 :
231 0 : Ok(Self {
232 0 : shard,
233 0 : mode,
234 0 : tenant_conf,
235 0 : })
236 0 : }
237 : }
238 :
239 : impl Default for LocationConf {
240 : // TODO: this should be removed once tenant loading can guarantee that we are never
241 : // loading from a directory without a configuration.
242 : // => tech debt since https://github.com/neondatabase/neon/issues/1555
243 0 : fn default() -> Self {
244 0 : Self {
245 0 : mode: LocationMode::Attached(AttachedLocationConfig {
246 0 : generation: Generation::none(),
247 0 : attach_mode: AttachmentMode::Single,
248 0 : }),
249 0 : tenant_conf: TenantConfOpt::default(),
250 0 : shard: ShardIdentity::unsharded(),
251 0 : }
252 0 : }
253 : }
254 :
255 : /// Same as TenantConf, but this struct preserves the information about
256 : /// which parameters are set and which are not.
257 148 : #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
258 : pub struct TenantConfOpt {
259 : #[serde(skip_serializing_if = "Option::is_none")]
260 : #[serde(default)]
261 : pub checkpoint_distance: Option<u64>,
262 :
263 : #[serde(skip_serializing_if = "Option::is_none")]
264 : #[serde(with = "humantime_serde")]
265 : #[serde(default)]
266 : pub checkpoint_timeout: Option<Duration>,
267 :
268 : #[serde(skip_serializing_if = "Option::is_none")]
269 : #[serde(default)]
270 : pub compaction_target_size: Option<u64>,
271 :
272 : #[serde(skip_serializing_if = "Option::is_none")]
273 : #[serde(with = "humantime_serde")]
274 : #[serde(default)]
275 : pub compaction_period: Option<Duration>,
276 :
277 : #[serde(skip_serializing_if = "Option::is_none")]
278 : #[serde(default)]
279 : pub compaction_threshold: Option<usize>,
280 :
281 : #[serde(skip_serializing_if = "Option::is_none")]
282 : #[serde(default)]
283 : pub compaction_upper_limit: Option<usize>,
284 :
285 : #[serde(skip_serializing_if = "Option::is_none")]
286 : #[serde(default)]
287 : pub compaction_algorithm: Option<CompactionAlgorithmSettings>,
288 :
289 : #[serde(skip_serializing_if = "Option::is_none")]
290 : #[serde(default)]
291 : pub compaction_l0_first: Option<bool>,
292 :
293 : #[serde(skip_serializing_if = "Option::is_none")]
294 : #[serde(default)]
295 : pub compaction_l0_semaphore: Option<bool>,
296 :
297 : #[serde(skip_serializing_if = "Option::is_none")]
298 : #[serde(default)]
299 : pub l0_flush_delay_threshold: Option<usize>,
300 :
301 : #[serde(skip_serializing_if = "Option::is_none")]
302 : #[serde(default)]
303 : pub l0_flush_stall_threshold: Option<usize>,
304 :
305 : #[serde(skip_serializing_if = "Option::is_none")]
306 : #[serde(default)]
307 : pub l0_flush_wait_upload: Option<bool>,
308 :
309 : #[serde(skip_serializing_if = "Option::is_none")]
310 : #[serde(default)]
311 : pub gc_horizon: Option<u64>,
312 :
313 : #[serde(skip_serializing_if = "Option::is_none")]
314 : #[serde(with = "humantime_serde")]
315 : #[serde(default)]
316 : pub gc_period: Option<Duration>,
317 :
318 : #[serde(skip_serializing_if = "Option::is_none")]
319 : #[serde(default)]
320 : pub image_creation_threshold: Option<usize>,
321 :
322 : #[serde(skip_serializing_if = "Option::is_none")]
323 : #[serde(with = "humantime_serde")]
324 : #[serde(default)]
325 : pub pitr_interval: Option<Duration>,
326 :
327 : #[serde(skip_serializing_if = "Option::is_none")]
328 : #[serde(with = "humantime_serde")]
329 : #[serde(default)]
330 : pub walreceiver_connect_timeout: Option<Duration>,
331 :
332 : #[serde(skip_serializing_if = "Option::is_none")]
333 : #[serde(with = "humantime_serde")]
334 : #[serde(default)]
335 : pub lagging_wal_timeout: Option<Duration>,
336 :
337 : #[serde(skip_serializing_if = "Option::is_none")]
338 : #[serde(default)]
339 : pub max_lsn_wal_lag: Option<NonZeroU64>,
340 :
341 : #[serde(skip_serializing_if = "Option::is_none")]
342 : #[serde(default)]
343 : pub eviction_policy: Option<EvictionPolicy>,
344 :
345 : #[serde(skip_serializing_if = "Option::is_none")]
346 : #[serde(default)]
347 : pub min_resident_size_override: Option<u64>,
348 :
349 : #[serde(skip_serializing_if = "Option::is_none")]
350 : #[serde(with = "humantime_serde")]
351 : #[serde(default)]
352 : pub evictions_low_residence_duration_metric_threshold: Option<Duration>,
353 :
354 : #[serde(skip_serializing_if = "Option::is_none")]
355 : #[serde(with = "humantime_serde")]
356 : #[serde(default)]
357 : pub heatmap_period: Option<Duration>,
358 :
359 : #[serde(skip_serializing_if = "Option::is_none")]
360 : #[serde(default)]
361 : pub lazy_slru_download: Option<bool>,
362 :
363 : #[serde(skip_serializing_if = "Option::is_none")]
364 : pub timeline_get_throttle: Option<pageserver_api::models::ThrottleConfig>,
365 :
366 : #[serde(skip_serializing_if = "Option::is_none")]
367 : pub image_layer_creation_check_threshold: Option<u8>,
368 :
369 : #[serde(skip_serializing_if = "Option::is_none")]
370 : pub image_creation_preempt_threshold: Option<usize>,
371 :
372 : #[serde(skip_serializing_if = "Option::is_none")]
373 : #[serde(with = "humantime_serde")]
374 : #[serde(default)]
375 : pub lsn_lease_length: Option<Duration>,
376 :
377 : #[serde(skip_serializing_if = "Option::is_none")]
378 : #[serde(with = "humantime_serde")]
379 : #[serde(default)]
380 : pub lsn_lease_length_for_ts: Option<Duration>,
381 :
382 : #[serde(skip_serializing_if = "Option::is_none")]
383 : #[serde(default)]
384 : pub timeline_offloading: Option<bool>,
385 :
386 : #[serde(skip_serializing_if = "Option::is_none")]
387 : pub wal_receiver_protocol_override: Option<PostgresClientProtocol>,
388 :
389 : #[serde(skip_serializing_if = "Option::is_none")]
390 : pub rel_size_v2_enabled: Option<bool>,
391 :
392 : #[serde(skip_serializing_if = "Option::is_none")]
393 : pub gc_compaction_enabled: Option<bool>,
394 :
395 : #[serde(skip_serializing_if = "Option::is_none")]
396 : pub gc_compaction_initial_threshold_kb: Option<u64>,
397 :
398 : #[serde(skip_serializing_if = "Option::is_none")]
399 : pub gc_compaction_ratio_percent: Option<u64>,
400 : }
401 :
402 : impl TenantConfOpt {
403 0 : pub fn merge(&self, global_conf: TenantConf) -> TenantConf {
404 0 : TenantConf {
405 0 : checkpoint_distance: self
406 0 : .checkpoint_distance
407 0 : .unwrap_or(global_conf.checkpoint_distance),
408 0 : checkpoint_timeout: self
409 0 : .checkpoint_timeout
410 0 : .unwrap_or(global_conf.checkpoint_timeout),
411 0 : compaction_target_size: self
412 0 : .compaction_target_size
413 0 : .unwrap_or(global_conf.compaction_target_size),
414 0 : compaction_period: self
415 0 : .compaction_period
416 0 : .unwrap_or(global_conf.compaction_period),
417 0 : compaction_threshold: self
418 0 : .compaction_threshold
419 0 : .unwrap_or(global_conf.compaction_threshold),
420 0 : compaction_upper_limit: self
421 0 : .compaction_upper_limit
422 0 : .unwrap_or(global_conf.compaction_upper_limit),
423 0 : compaction_algorithm: self
424 0 : .compaction_algorithm
425 0 : .as_ref()
426 0 : .unwrap_or(&global_conf.compaction_algorithm)
427 0 : .clone(),
428 0 : compaction_l0_first: self
429 0 : .compaction_l0_first
430 0 : .unwrap_or(global_conf.compaction_l0_first),
431 0 : compaction_l0_semaphore: self
432 0 : .compaction_l0_semaphore
433 0 : .unwrap_or(global_conf.compaction_l0_semaphore),
434 0 : l0_flush_delay_threshold: self
435 0 : .l0_flush_delay_threshold
436 0 : .or(global_conf.l0_flush_delay_threshold),
437 0 : l0_flush_stall_threshold: self
438 0 : .l0_flush_stall_threshold
439 0 : .or(global_conf.l0_flush_stall_threshold),
440 0 : l0_flush_wait_upload: self
441 0 : .l0_flush_wait_upload
442 0 : .unwrap_or(global_conf.l0_flush_wait_upload),
443 0 : gc_horizon: self.gc_horizon.unwrap_or(global_conf.gc_horizon),
444 0 : gc_period: self.gc_period.unwrap_or(global_conf.gc_period),
445 0 : image_creation_threshold: self
446 0 : .image_creation_threshold
447 0 : .unwrap_or(global_conf.image_creation_threshold),
448 0 : pitr_interval: self.pitr_interval.unwrap_or(global_conf.pitr_interval),
449 0 : walreceiver_connect_timeout: self
450 0 : .walreceiver_connect_timeout
451 0 : .unwrap_or(global_conf.walreceiver_connect_timeout),
452 0 : lagging_wal_timeout: self
453 0 : .lagging_wal_timeout
454 0 : .unwrap_or(global_conf.lagging_wal_timeout),
455 0 : max_lsn_wal_lag: self.max_lsn_wal_lag.unwrap_or(global_conf.max_lsn_wal_lag),
456 0 : eviction_policy: self.eviction_policy.unwrap_or(global_conf.eviction_policy),
457 0 : min_resident_size_override: self
458 0 : .min_resident_size_override
459 0 : .or(global_conf.min_resident_size_override),
460 0 : evictions_low_residence_duration_metric_threshold: self
461 0 : .evictions_low_residence_duration_metric_threshold
462 0 : .unwrap_or(global_conf.evictions_low_residence_duration_metric_threshold),
463 0 : heatmap_period: self.heatmap_period.unwrap_or(global_conf.heatmap_period),
464 0 : lazy_slru_download: self
465 0 : .lazy_slru_download
466 0 : .unwrap_or(global_conf.lazy_slru_download),
467 0 : timeline_get_throttle: self
468 0 : .timeline_get_throttle
469 0 : .clone()
470 0 : .unwrap_or(global_conf.timeline_get_throttle),
471 0 : image_layer_creation_check_threshold: self
472 0 : .image_layer_creation_check_threshold
473 0 : .unwrap_or(global_conf.image_layer_creation_check_threshold),
474 0 : image_creation_preempt_threshold: self
475 0 : .image_creation_preempt_threshold
476 0 : .unwrap_or(global_conf.image_creation_preempt_threshold),
477 0 : lsn_lease_length: self
478 0 : .lsn_lease_length
479 0 : .unwrap_or(global_conf.lsn_lease_length),
480 0 : lsn_lease_length_for_ts: self
481 0 : .lsn_lease_length_for_ts
482 0 : .unwrap_or(global_conf.lsn_lease_length_for_ts),
483 0 : timeline_offloading: self
484 0 : .timeline_offloading
485 0 : .unwrap_or(global_conf.timeline_offloading),
486 0 : wal_receiver_protocol_override: self
487 0 : .wal_receiver_protocol_override
488 0 : .or(global_conf.wal_receiver_protocol_override),
489 0 : rel_size_v2_enabled: self
490 0 : .rel_size_v2_enabled
491 0 : .unwrap_or(global_conf.rel_size_v2_enabled),
492 0 : gc_compaction_enabled: self
493 0 : .gc_compaction_enabled
494 0 : .unwrap_or(global_conf.gc_compaction_enabled),
495 0 : gc_compaction_initial_threshold_kb: self
496 0 : .gc_compaction_initial_threshold_kb
497 0 : .unwrap_or(global_conf.gc_compaction_initial_threshold_kb),
498 0 : gc_compaction_ratio_percent: self
499 0 : .gc_compaction_ratio_percent
500 0 : .unwrap_or(global_conf.gc_compaction_ratio_percent),
501 0 : }
502 0 : }
503 :
504 0 : pub fn apply_patch(self, patch: TenantConfigPatch) -> anyhow::Result<TenantConfOpt> {
505 0 : let Self {
506 0 : mut checkpoint_distance,
507 0 : mut checkpoint_timeout,
508 0 : mut compaction_target_size,
509 0 : mut compaction_period,
510 0 : mut compaction_threshold,
511 0 : mut compaction_upper_limit,
512 0 : mut compaction_algorithm,
513 0 : mut compaction_l0_first,
514 0 : mut compaction_l0_semaphore,
515 0 : mut l0_flush_delay_threshold,
516 0 : mut l0_flush_stall_threshold,
517 0 : mut l0_flush_wait_upload,
518 0 : mut gc_horizon,
519 0 : mut gc_period,
520 0 : mut image_creation_threshold,
521 0 : mut pitr_interval,
522 0 : mut walreceiver_connect_timeout,
523 0 : mut lagging_wal_timeout,
524 0 : mut max_lsn_wal_lag,
525 0 : mut eviction_policy,
526 0 : mut min_resident_size_override,
527 0 : mut evictions_low_residence_duration_metric_threshold,
528 0 : mut heatmap_period,
529 0 : mut lazy_slru_download,
530 0 : mut timeline_get_throttle,
531 0 : mut image_layer_creation_check_threshold,
532 0 : mut image_creation_preempt_threshold,
533 0 : mut lsn_lease_length,
534 0 : mut lsn_lease_length_for_ts,
535 0 : mut timeline_offloading,
536 0 : mut wal_receiver_protocol_override,
537 0 : mut rel_size_v2_enabled,
538 0 : mut gc_compaction_enabled,
539 0 : mut gc_compaction_initial_threshold_kb,
540 0 : mut gc_compaction_ratio_percent,
541 0 : } = self;
542 0 :
543 0 : patch.checkpoint_distance.apply(&mut checkpoint_distance);
544 0 : patch
545 0 : .checkpoint_timeout
546 0 : .map(|v| humantime::parse_duration(&v))?
547 0 : .apply(&mut checkpoint_timeout);
548 0 : patch
549 0 : .compaction_target_size
550 0 : .apply(&mut compaction_target_size);
551 0 : patch
552 0 : .compaction_period
553 0 : .map(|v| humantime::parse_duration(&v))?
554 0 : .apply(&mut compaction_period);
555 0 : patch.compaction_threshold.apply(&mut compaction_threshold);
556 0 : patch
557 0 : .compaction_upper_limit
558 0 : .apply(&mut compaction_upper_limit);
559 0 : patch.compaction_algorithm.apply(&mut compaction_algorithm);
560 0 : patch.compaction_l0_first.apply(&mut compaction_l0_first);
561 0 : patch
562 0 : .compaction_l0_semaphore
563 0 : .apply(&mut compaction_l0_semaphore);
564 0 : patch
565 0 : .l0_flush_delay_threshold
566 0 : .apply(&mut l0_flush_delay_threshold);
567 0 : patch
568 0 : .l0_flush_stall_threshold
569 0 : .apply(&mut l0_flush_stall_threshold);
570 0 : patch.l0_flush_wait_upload.apply(&mut l0_flush_wait_upload);
571 0 : patch.gc_horizon.apply(&mut gc_horizon);
572 0 : patch
573 0 : .gc_period
574 0 : .map(|v| humantime::parse_duration(&v))?
575 0 : .apply(&mut gc_period);
576 0 : patch
577 0 : .image_creation_threshold
578 0 : .apply(&mut image_creation_threshold);
579 0 : patch
580 0 : .pitr_interval
581 0 : .map(|v| humantime::parse_duration(&v))?
582 0 : .apply(&mut pitr_interval);
583 0 : patch
584 0 : .walreceiver_connect_timeout
585 0 : .map(|v| humantime::parse_duration(&v))?
586 0 : .apply(&mut walreceiver_connect_timeout);
587 0 : patch
588 0 : .lagging_wal_timeout
589 0 : .map(|v| humantime::parse_duration(&v))?
590 0 : .apply(&mut lagging_wal_timeout);
591 0 : patch.max_lsn_wal_lag.apply(&mut max_lsn_wal_lag);
592 0 : patch.eviction_policy.apply(&mut eviction_policy);
593 0 : patch
594 0 : .min_resident_size_override
595 0 : .apply(&mut min_resident_size_override);
596 0 : patch
597 0 : .evictions_low_residence_duration_metric_threshold
598 0 : .map(|v| humantime::parse_duration(&v))?
599 0 : .apply(&mut evictions_low_residence_duration_metric_threshold);
600 0 : patch
601 0 : .heatmap_period
602 0 : .map(|v| humantime::parse_duration(&v))?
603 0 : .apply(&mut heatmap_period);
604 0 : patch.lazy_slru_download.apply(&mut lazy_slru_download);
605 0 : patch
606 0 : .timeline_get_throttle
607 0 : .apply(&mut timeline_get_throttle);
608 0 : patch
609 0 : .image_layer_creation_check_threshold
610 0 : .apply(&mut image_layer_creation_check_threshold);
611 0 : patch
612 0 : .image_creation_preempt_threshold
613 0 : .apply(&mut image_creation_preempt_threshold);
614 0 : patch
615 0 : .lsn_lease_length
616 0 : .map(|v| humantime::parse_duration(&v))?
617 0 : .apply(&mut lsn_lease_length);
618 0 : patch
619 0 : .lsn_lease_length_for_ts
620 0 : .map(|v| humantime::parse_duration(&v))?
621 0 : .apply(&mut lsn_lease_length_for_ts);
622 0 : patch.timeline_offloading.apply(&mut timeline_offloading);
623 0 : patch
624 0 : .wal_receiver_protocol_override
625 0 : .apply(&mut wal_receiver_protocol_override);
626 0 : patch.rel_size_v2_enabled.apply(&mut rel_size_v2_enabled);
627 0 : patch
628 0 : .gc_compaction_enabled
629 0 : .apply(&mut gc_compaction_enabled);
630 0 : patch
631 0 : .gc_compaction_initial_threshold_kb
632 0 : .apply(&mut gc_compaction_initial_threshold_kb);
633 0 : patch
634 0 : .gc_compaction_ratio_percent
635 0 : .apply(&mut gc_compaction_ratio_percent);
636 0 :
637 0 : Ok(Self {
638 0 : checkpoint_distance,
639 0 : checkpoint_timeout,
640 0 : compaction_target_size,
641 0 : compaction_period,
642 0 : compaction_threshold,
643 0 : compaction_upper_limit,
644 0 : compaction_algorithm,
645 0 : compaction_l0_first,
646 0 : compaction_l0_semaphore,
647 0 : l0_flush_delay_threshold,
648 0 : l0_flush_stall_threshold,
649 0 : l0_flush_wait_upload,
650 0 : gc_horizon,
651 0 : gc_period,
652 0 : image_creation_threshold,
653 0 : pitr_interval,
654 0 : walreceiver_connect_timeout,
655 0 : lagging_wal_timeout,
656 0 : max_lsn_wal_lag,
657 0 : eviction_policy,
658 0 : min_resident_size_override,
659 0 : evictions_low_residence_duration_metric_threshold,
660 0 : heatmap_period,
661 0 : lazy_slru_download,
662 0 : timeline_get_throttle,
663 0 : image_layer_creation_check_threshold,
664 0 : image_creation_preempt_threshold,
665 0 : lsn_lease_length,
666 0 : lsn_lease_length_for_ts,
667 0 : timeline_offloading,
668 0 : wal_receiver_protocol_override,
669 0 : rel_size_v2_enabled,
670 0 : gc_compaction_enabled,
671 0 : gc_compaction_initial_threshold_kb,
672 0 : gc_compaction_ratio_percent,
673 0 : })
674 0 : }
675 : }
676 :
677 : impl TryFrom<&'_ models::TenantConfig> for TenantConfOpt {
678 : type Error = anyhow::Error;
679 :
680 4 : fn try_from(request_data: &'_ models::TenantConfig) -> Result<Self, Self::Error> {
681 : // Convert the request_data to a JSON Value
682 4 : let json_value: Value = serde_json::to_value(request_data)?;
683 :
684 : // Create a Deserializer from the JSON Value
685 4 : let deserializer = json_value.into_deserializer();
686 :
687 : // Use serde_path_to_error to deserialize the JSON Value into TenantConfOpt
688 4 : let tenant_conf: TenantConfOpt = serde_path_to_error::deserialize(deserializer)?;
689 :
690 4 : Ok(tenant_conf)
691 4 : }
692 : }
693 :
694 : /// This is a conversion from our internal tenant config object to the one used
695 : /// in external APIs.
696 : impl From<TenantConfOpt> for models::TenantConfig {
697 : // TODO(vlad): These are now the same, but they have different serialization logic.
698 : // Can we merge them?
699 0 : fn from(value: TenantConfOpt) -> Self {
700 0 : Self {
701 0 : checkpoint_distance: value.checkpoint_distance,
702 0 : checkpoint_timeout: value.checkpoint_timeout,
703 0 : compaction_algorithm: value.compaction_algorithm,
704 0 : compaction_target_size: value.compaction_target_size,
705 0 : compaction_period: value.compaction_period,
706 0 : compaction_threshold: value.compaction_threshold,
707 0 : compaction_upper_limit: value.compaction_upper_limit,
708 0 : compaction_l0_first: value.compaction_l0_first,
709 0 : compaction_l0_semaphore: value.compaction_l0_semaphore,
710 0 : l0_flush_delay_threshold: value.l0_flush_delay_threshold,
711 0 : l0_flush_stall_threshold: value.l0_flush_stall_threshold,
712 0 : l0_flush_wait_upload: value.l0_flush_wait_upload,
713 0 : gc_horizon: value.gc_horizon,
714 0 : gc_period: value.gc_period,
715 0 : image_creation_threshold: value.image_creation_threshold,
716 0 : pitr_interval: value.pitr_interval,
717 0 : walreceiver_connect_timeout: value.walreceiver_connect_timeout,
718 0 : lagging_wal_timeout: value.lagging_wal_timeout,
719 0 : max_lsn_wal_lag: value.max_lsn_wal_lag,
720 0 : eviction_policy: value.eviction_policy,
721 0 : min_resident_size_override: value.min_resident_size_override,
722 0 : evictions_low_residence_duration_metric_threshold: value
723 0 : .evictions_low_residence_duration_metric_threshold,
724 0 : heatmap_period: value.heatmap_period,
725 0 : lazy_slru_download: value.lazy_slru_download,
726 0 : timeline_get_throttle: value.timeline_get_throttle,
727 0 : image_layer_creation_check_threshold: value.image_layer_creation_check_threshold,
728 0 : image_creation_preempt_threshold: value.image_creation_preempt_threshold,
729 0 : lsn_lease_length: value.lsn_lease_length,
730 0 : lsn_lease_length_for_ts: value.lsn_lease_length_for_ts,
731 0 : timeline_offloading: value.timeline_offloading,
732 0 : wal_receiver_protocol_override: value.wal_receiver_protocol_override,
733 0 : rel_size_v2_enabled: value.rel_size_v2_enabled,
734 0 : gc_compaction_enabled: value.gc_compaction_enabled,
735 0 : gc_compaction_initial_threshold_kb: value.gc_compaction_initial_threshold_kb,
736 0 : gc_compaction_ratio_percent: value.gc_compaction_ratio_percent,
737 0 : }
738 0 : }
739 : }
740 :
741 : #[cfg(test)]
742 : mod tests {
743 : use models::TenantConfig;
744 :
745 : use super::*;
746 :
747 : #[test]
748 4 : fn de_serializing_pageserver_config_omits_empty_values() {
749 4 : let small_conf = TenantConfOpt {
750 4 : gc_horizon: Some(42),
751 4 : ..TenantConfOpt::default()
752 4 : };
753 4 :
754 4 : let toml_form = toml_edit::ser::to_string(&small_conf).unwrap();
755 4 : assert_eq!(toml_form, "gc_horizon = 42\n");
756 4 : assert_eq!(small_conf, toml_edit::de::from_str(&toml_form).unwrap());
757 :
758 4 : let json_form = serde_json::to_string(&small_conf).unwrap();
759 4 : assert_eq!(json_form, "{\"gc_horizon\":42}");
760 4 : assert_eq!(small_conf, serde_json::from_str(&json_form).unwrap());
761 4 : }
762 :
763 : #[test]
764 4 : fn test_try_from_models_tenant_config_success() {
765 4 : let tenant_config = models::TenantConfig {
766 4 : lagging_wal_timeout: Some(Duration::from_secs(5)),
767 4 : ..TenantConfig::default()
768 4 : };
769 4 :
770 4 : let tenant_conf_opt = TenantConfOpt::try_from(&tenant_config).unwrap();
771 4 :
772 4 : assert_eq!(
773 4 : tenant_conf_opt.lagging_wal_timeout,
774 4 : Some(Duration::from_secs(5))
775 4 : );
776 4 : }
777 : }
|