Skip to main content

mito2/
config.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Configurations.
16
17use std::cmp;
18use std::path::Path;
19use std::time::Duration;
20
21use common_base::memory_limit::MemoryLimit;
22use common_base::readable_size::ReadableSize;
23use common_memory_manager::OnExhaustedPolicy;
24use common_stat::{get_total_cpu_cores, get_total_memory_readable};
25use common_telemetry::warn;
26use serde::{Deserialize, Serialize};
27use serde_with::serde_as;
28
29use crate::cache::file_cache::DEFAULT_INDEX_CACHE_PERCENT;
30use crate::error::Result;
31use crate::gc::GcConfig;
32use crate::sst::DEFAULT_WRITE_BUFFER_SIZE;
33
34const MULTIPART_UPLOAD_MINIMUM_SIZE: ReadableSize = ReadableSize::mb(5);
35const DEFAULT_SERIES_INDEX_MAINTENANCE_INTERVAL: Duration = Duration::from_secs(5 * 60);
36/// Default maximum number of SST files to scan concurrently.
37pub(crate) const DEFAULT_MAX_CONCURRENT_SCAN_FILES: usize = 384;
38
39// Use `1/GLOBAL_WRITE_BUFFER_SIZE_FACTOR` of OS memory as global write buffer size in default mode
40const GLOBAL_WRITE_BUFFER_SIZE_FACTOR: u64 = 8;
41/// Use `1/SST_META_CACHE_SIZE_FACTOR` of OS memory size as SST meta cache size in default mode
42const SST_META_CACHE_SIZE_FACTOR: u64 = 8;
43/// Use `1/PREFILTER_RESULT_CACHE_SIZE_FACTOR` of OS memory size as prefilter result cache size in default mode
44const PREFILTER_RESULT_CACHE_SIZE_FACTOR: u64 = 32;
45/// Use `1/INDEX_METADATA_CACHE_SIZE_FACTOR` of OS memory size as index metadata cache size in default mode
46const INDEX_METADATA_CACHE_SIZE_FACTOR: u64 = 32;
47/// Use `1/MEM_CACHE_SIZE_FACTOR` of OS memory size as mem cache size in default mode
48const MEM_CACHE_SIZE_FACTOR: u64 = 16;
49/// Use `1/PAGE_CACHE_SIZE_FACTOR` of OS memory size as page cache size in default mode
50const PAGE_CACHE_SIZE_FACTOR: u64 = 8;
51/// Use `1/INDEX_CREATE_MEM_THRESHOLD_FACTOR` of OS memory size as mem threshold for creating index
52const INDEX_CREATE_MEM_THRESHOLD_FACTOR: u64 = 16;
53
54/// Fetch option timeout
55pub(crate) const FETCH_OPTION_TIMEOUT: Duration = Duration::from_secs(3);
56
57/// Configuration for [MitoEngine](crate::engine::MitoEngine).
58/// Before using the config, make sure to call `MitoConfig::validate()` to check if the config is valid.
59#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
60#[serde(default)]
61pub struct MitoConfig {
62    // Worker configs:
63    /// Number of region workers (default: 1/2 of cpu cores).
64    /// Sets to 0 to use the default value.
65    pub num_workers: usize,
66    /// Request channel size of each worker (default 128).
67    pub worker_channel_size: usize,
68    /// Max batch size for a worker to handle requests (default 64).
69    pub worker_request_batch_size: usize,
70
71    // Manifest configs:
72    /// Number of meta action updated to trigger a new checkpoint
73    /// for the manifest (default 10).
74    pub manifest_checkpoint_distance: u64,
75    /// Number of removed files to keep in manifest's `removed_files` field before also
76    /// remove them from `removed_files`. Mostly for debugging purpose.
77    /// If set to 0, it will only use `keep_removed_file_ttl` to decide when to remove files
78    /// from `removed_files` field.
79    pub experimental_manifest_keep_removed_file_count: usize,
80    /// How long to keep removed files in the `removed_files` field of manifest
81    /// after they are removed from manifest.
82    /// files will only be removed from `removed_files` field
83    /// if both `keep_removed_file_count` and `keep_removed_file_ttl` is reached.
84    #[serde(with = "humantime_serde")]
85    pub experimental_manifest_keep_removed_file_ttl: Duration,
86    /// Whether to compress manifest and checkpoint file by gzip (default false).
87    pub compress_manifest: bool,
88
89    // Background job configs:
90    /// Max number of running background index build jobs (default: 1/8 of cpu cores).
91    pub max_background_index_builds: usize,
92    /// Under development; do not enable. Whether to enable series indexes (default false).
93    /// Indexes are stored on the local filesystem under `{data_home}/series_index`.
94    pub experimental_enable_series_index: bool,
95    /// Approximate series and range index size limit in open regions (default: 5 GiB).
96    /// Workers share a periodically refreshed estimate and skip maintenance when full.
97    /// In-flight reconciliation can exceed the limit; closed-region files are not counted.
98    pub experimental_series_index_max_size: ReadableSize,
99    /// Whether to build and query range indexes when series indexes are enabled (default false).
100    /// Obsolete range-index metadata and files are still cleaned up when disabled.
101    pub experimental_enable_range_index: bool,
102    /// Interval between series-index maintenance runs (default 5 min). Zero uses the default.
103    #[serde(with = "humantime_serde")]
104    pub experimental_series_index_maintenance_interval: Duration,
105    /// Under development; do not enable. Requested minimum bucket width for series indexes.
106    /// It is rounded up to an exact multiple of each region's compaction time window.
107    #[serde(with = "humantime_serde")]
108    pub experimental_series_index_bucket_width: Duration,
109    /// Max number of running background flush jobs (default: 1/2 of cpu cores).
110    pub max_background_flushes: usize,
111    /// Max number of running background compaction jobs (default: 1/4 of cpu cores).
112    pub max_background_compactions: usize,
113    /// Max number of running background purge jobs (default: number of cpu cores).
114    pub max_background_purges: usize,
115    /// Memory budget for compaction tasks.
116    /// Supports absolute size (e.g., "2GiB", "512MB") or percentage of system memory (e.g., "50%").
117    /// Setting it to 0 or "unlimited" disables the limit.
118    pub experimental_compaction_memory_limit: MemoryLimit,
119    /// Behavior when compaction cannot acquire memory from the budget.
120    pub experimental_compaction_on_exhausted: OnExhaustedPolicy,
121
122    // Flush configs:
123    /// Interval to auto flush a region if it has not flushed yet (default 10 min).
124    #[serde(with = "humantime_serde")]
125    pub auto_flush_interval: Duration,
126    /// Global write buffer size threshold to trigger flush.
127    pub global_write_buffer_size: ReadableSize,
128    /// Global write buffer size threshold to reject write requests.
129    pub global_write_buffer_reject_size: ReadableSize,
130    /// Default write buffer size for each region. Regions stall at this size and
131    /// reject writes at twice this size. Setting it to 0 disables both limits
132    /// unless the table specifies `write_buffer_size`.
133    pub default_region_write_buffer_size: ReadableSize,
134
135    // Cache configs:
136    /// Cache size for SST metadata. Setting it to 0 to disable the cache.
137    pub sst_meta_cache_size: ReadableSize,
138    /// Cache size for vectors and arrow arrays. Setting it to 0 to disable the cache.
139    pub vector_cache_size: ReadableSize,
140    /// Cache size for pages of SST row groups. Setting it to 0 to disable the cache.
141    pub page_cache_size: ReadableSize,
142    /// Cache size for time series selector (e.g. `last_value()`). Setting it to 0 to disable the cache.
143    pub selector_result_cache_size: ReadableSize,
144    /// Cache size for flat range scan results. Setting it to 0 to disable the cache.
145    pub range_result_cache_size: ReadableSize,
146    /// Cache size for prefilter results. Setting it to 0 to disable the cache.
147    pub prefilter_result_cache_size: ReadableSize,
148    /// Whether to enable the write cache.
149    pub enable_write_cache: bool,
150    /// File system path for write cache dir's root, defaults to `{data_home}`.
151    pub write_cache_path: String,
152    /// Capacity for write cache.
153    pub write_cache_size: ReadableSize,
154    /// TTL for write cache.
155    #[serde(with = "humantime_serde")]
156    pub write_cache_ttl: Option<Duration>,
157    /// Preload index (puffin) files into cache on region open (default: true).
158    pub preload_index_cache: bool,
159    /// Percentage of write cache capacity allocated for index (puffin) files (default: 20).
160    /// The remaining capacity is used for data (parquet) files.
161    /// Must be between 0 and 100 (exclusive).
162    pub index_cache_percent: u8,
163    /// Enable background downloading of files to the local cache when accessed during queries (default: true).
164    /// When enabled, files will be asynchronously downloaded to improve performance for subsequent reads.
165    pub enable_refill_cache_on_read: bool,
166    /// Capacity for manifest cache (default: 256MB).
167    pub manifest_cache_size: ReadableSize,
168
169    // Other configs:
170    /// Buffer size for SST writing.
171    // TODO(#9240): Support per-object-store write buffer sizes for backend-specific tuning.
172    pub sst_write_buffer_size: ReadableSize,
173    /// Maximum number of SST files to scan concurrently (default 384).
174    pub max_concurrent_scan_files: usize,
175    /// Whether to allow stale entries read during replay.
176    pub allow_stale_entries: bool,
177    /// Memory limit for table scans across all queries.
178    /// Setting it to 0 or "unlimited" disables the limit.
179    /// Supports absolute size (e.g., "2GB") or percentage of system memory (e.g., "50%").
180    pub scan_memory_limit: MemoryLimit,
181    /// Behavior when scan memory tracking cannot acquire memory from the budget.
182    /// `wait` means `wait(10s)`, not unlimited waiting.
183    /// Defaults to [`OnExhaustedPolicy::Fail`], which intentionally differs from
184    /// [`OnExhaustedPolicy::default()`].
185    pub scan_memory_on_exhausted: OnExhaustedPolicy,
186
187    /// Index configs.
188    pub index: IndexConfig,
189    /// Inverted index configs.
190    pub inverted_index: InvertedIndexConfig,
191    /// Full-text index configs.
192    pub fulltext_index: FulltextIndexConfig,
193    /// Bloom filter index configs.
194    pub bloom_filter_index: BloomFilterConfig,
195
196    /// Minimum time interval between two compactions.
197    /// To align with the old behavior, the default value is 0 (no restrictions).
198    #[serde(with = "humantime_serde")]
199    pub min_compaction_interval: Duration,
200    /// Whether to schedule compaction after applying a region edit.
201    pub schedule_compaction_after_edit: bool,
202
203    /// Whether to enable flat format as the default SST format.
204    /// When enabled, forces using BulkMemtable and BulkMemtableBuilder.
205    pub default_flat_format: bool,
206
207    /// Whether to enable the experimental two-phase mode for eligible metric series scans.
208    pub experimental_series_scan_v2: bool,
209
210    pub gc: GcConfig,
211}
212
213impl Default for MitoConfig {
214    fn default() -> Self {
215        let mut mito_config = MitoConfig {
216            num_workers: divide_num_cpus(2),
217            worker_channel_size: 128,
218            worker_request_batch_size: 64,
219            manifest_checkpoint_distance: 10,
220            experimental_manifest_keep_removed_file_count: 256,
221            experimental_manifest_keep_removed_file_ttl: Duration::from_secs(60 * 60),
222            compress_manifest: false,
223            max_background_index_builds: divide_num_cpus(8),
224            experimental_enable_series_index: false,
225            experimental_series_index_max_size: ReadableSize::gb(5),
226            experimental_enable_range_index: false,
227            experimental_series_index_maintenance_interval:
228                DEFAULT_SERIES_INDEX_MAINTENANCE_INTERVAL,
229            experimental_series_index_bucket_width: Duration::from_secs(5 * 24 * 60 * 60),
230            max_background_flushes: divide_num_cpus(2),
231            max_background_compactions: divide_num_cpus(4),
232            max_background_purges: get_total_cpu_cores(),
233            experimental_compaction_memory_limit: MemoryLimit::Unlimited,
234            experimental_compaction_on_exhausted: OnExhaustedPolicy::default(),
235            auto_flush_interval: Duration::from_secs(10 * 60),
236            global_write_buffer_size: ReadableSize::gb(1),
237            global_write_buffer_reject_size: ReadableSize::gb(2),
238            default_region_write_buffer_size: ReadableSize::mb(0),
239            sst_meta_cache_size: ReadableSize::mb(128),
240            vector_cache_size: ReadableSize::mb(512),
241            page_cache_size: ReadableSize::mb(512),
242            selector_result_cache_size: ReadableSize::mb(512),
243            range_result_cache_size: ReadableSize::mb(512),
244            prefilter_result_cache_size: ReadableSize::mb(128),
245            enable_write_cache: false,
246            write_cache_path: String::new(),
247            write_cache_size: ReadableSize::gb(5),
248            write_cache_ttl: None,
249            preload_index_cache: true,
250            index_cache_percent: DEFAULT_INDEX_CACHE_PERCENT,
251            enable_refill_cache_on_read: true,
252            manifest_cache_size: ReadableSize::mb(256),
253            sst_write_buffer_size: DEFAULT_WRITE_BUFFER_SIZE,
254            max_concurrent_scan_files: DEFAULT_MAX_CONCURRENT_SCAN_FILES,
255            allow_stale_entries: false,
256            scan_memory_limit: MemoryLimit::default(),
257            scan_memory_on_exhausted: OnExhaustedPolicy::Fail,
258            index: IndexConfig::default(),
259            inverted_index: InvertedIndexConfig::default(),
260            fulltext_index: FulltextIndexConfig::default(),
261            bloom_filter_index: BloomFilterConfig::default(),
262            min_compaction_interval: Duration::from_secs(0),
263            schedule_compaction_after_edit: true,
264            default_flat_format: true,
265            experimental_series_scan_v2: true,
266            gc: GcConfig::default(),
267        };
268
269        // Adjust buffer and cache size according to system memory if we can.
270        if let Some(sys_memory) = get_total_memory_readable() {
271            mito_config.adjust_buffer_and_cache_size(sys_memory);
272        }
273
274        mito_config
275    }
276}
277
278impl MitoConfig {
279    /// Sanitize incorrect configurations.
280    ///
281    /// Returns an error if there is a configuration that unable to sanitize.
282    pub fn sanitize(&mut self, data_home: &str) -> Result<()> {
283        if self.experimental_enable_series_index {
284            snafu::ensure!(
285                self.experimental_series_index_max_size.as_bytes() >= 1024,
286                crate::error::InvalidConfigSnafu {
287                    reason: "experimental_series_index_max_size must be at least 1KiB"
288                }
289            );
290        }
291        // Use default value if `num_workers` is 0.
292        if self.num_workers == 0 {
293            self.num_workers = divide_num_cpus(2);
294        }
295
296        // Sanitize channel size.
297        if self.worker_channel_size == 0 {
298            warn!("Sanitize channel size 0 to 1");
299            self.worker_channel_size = 1;
300        }
301
302        if self.max_background_flushes == 0 {
303            warn!(
304                "Sanitize max background flushes 0 to {}",
305                divide_num_cpus(2)
306            );
307            self.max_background_flushes = divide_num_cpus(2);
308        }
309        if self.max_background_compactions == 0 {
310            warn!(
311                "Sanitize max background compactions 0 to {}",
312                divide_num_cpus(4)
313            );
314            self.max_background_compactions = divide_num_cpus(4);
315        }
316        if self.max_background_purges == 0 {
317            let cpu_cores = get_total_cpu_cores();
318            warn!("Sanitize max background purges 0 to {}", cpu_cores);
319            self.max_background_purges = cpu_cores;
320        }
321
322        if self
323            .experimental_series_index_maintenance_interval
324            .is_zero()
325        {
326            warn!("Sanitize series-index maintenance interval 0 to 5 minutes");
327            self.experimental_series_index_maintenance_interval =
328                DEFAULT_SERIES_INDEX_MAINTENANCE_INTERVAL;
329        }
330
331        if self.global_write_buffer_reject_size <= self.global_write_buffer_size {
332            self.global_write_buffer_reject_size = self.global_write_buffer_size * 2;
333            warn!(
334                "Sanitize global write buffer reject size to {}",
335                self.global_write_buffer_reject_size
336            );
337        }
338
339        if self.sst_write_buffer_size < MULTIPART_UPLOAD_MINIMUM_SIZE {
340            self.sst_write_buffer_size = MULTIPART_UPLOAD_MINIMUM_SIZE;
341            warn!(
342                "Sanitize sst write buffer size to {}",
343                self.sst_write_buffer_size
344            );
345        }
346
347        // Sets write cache path if it is empty.
348        if self.write_cache_path.trim().is_empty() {
349            self.write_cache_path = data_home.to_string();
350        }
351
352        // Validate index_cache_percent is within valid range (0, 100)
353        if self.index_cache_percent == 0 || self.index_cache_percent >= 100 {
354            warn!(
355                "Invalid index_cache_percent {}, resetting to default {}",
356                self.index_cache_percent, DEFAULT_INDEX_CACHE_PERCENT
357            );
358            self.index_cache_percent = DEFAULT_INDEX_CACHE_PERCENT;
359        }
360
361        self.index.sanitize(data_home, &self.inverted_index)?;
362
363        Ok(())
364    }
365
366    fn adjust_buffer_and_cache_size(&mut self, sys_memory: ReadableSize) {
367        // shouldn't be greater than 1G in default mode.
368        let global_write_buffer_size = cmp::min(
369            sys_memory / GLOBAL_WRITE_BUFFER_SIZE_FACTOR,
370            ReadableSize::gb(1),
371        );
372        // Use 2x of global write buffer size as global write buffer reject size.
373        let global_write_buffer_reject_size = global_write_buffer_size * 2;
374        // Page-index-bearing SST metadata can be much larger than footers alone.
375        // Keep the auto-sized default bounded, but allow a larger warm working set.
376        let sst_meta_cache_size = cmp::min(
377            sys_memory / SST_META_CACHE_SIZE_FACTOR,
378            ReadableSize::mb(512),
379        );
380        let prefilter_result_cache_size = cmp::min(
381            sys_memory / PREFILTER_RESULT_CACHE_SIZE_FACTOR,
382            ReadableSize::mb(128),
383        );
384        // shouldn't be greater than 512MB in default mode.
385        let mem_cache_size = cmp::min(sys_memory / MEM_CACHE_SIZE_FACTOR, ReadableSize::mb(512));
386        let page_cache_size = sys_memory / PAGE_CACHE_SIZE_FACTOR;
387
388        self.global_write_buffer_size = global_write_buffer_size;
389        self.global_write_buffer_reject_size = global_write_buffer_reject_size;
390        self.sst_meta_cache_size = sst_meta_cache_size;
391        self.vector_cache_size = mem_cache_size;
392        self.page_cache_size = page_cache_size;
393        self.selector_result_cache_size = mem_cache_size;
394        self.range_result_cache_size = mem_cache_size;
395        // Use a smaller cache size because prefilter result usually should be small.
396        self.prefilter_result_cache_size = prefilter_result_cache_size;
397
398        self.index.adjust_buffer_and_cache_size(sys_memory);
399    }
400
401    /// Enable write cache.
402    #[cfg(test)]
403    pub fn enable_write_cache(
404        mut self,
405        path: String,
406        size: ReadableSize,
407        ttl: Option<Duration>,
408    ) -> Self {
409        self.enable_write_cache = true;
410        self.write_cache_path = path;
411        self.write_cache_size = size;
412        self.write_cache_ttl = ttl;
413        self
414    }
415}
416
417#[cfg(test)]
418mod tests {
419    use super::*;
420
421    #[test]
422    fn test_default_auto_flush_interval() {
423        assert_eq!(
424            Duration::from_secs(10 * 60),
425            MitoConfig::default().auto_flush_interval
426        );
427    }
428
429    #[test]
430    fn test_adjust_sst_metadata_and_prefilter_cache_caps_independently() {
431        let mut config = MitoConfig::default();
432
433        config.adjust_buffer_and_cache_size(ReadableSize::gb(1));
434        assert_eq!(ReadableSize::mb(128), config.sst_meta_cache_size);
435        assert_eq!(ReadableSize::mb(32), config.prefilter_result_cache_size);
436
437        config.adjust_buffer_and_cache_size(ReadableSize::gb(64));
438        assert_eq!(ReadableSize::mb(512), config.sst_meta_cache_size);
439        assert_eq!(ReadableSize::mb(128), config.prefilter_result_cache_size);
440    }
441
442    #[test]
443    fn test_series_index_config() {
444        assert!(!MitoConfig::default().experimental_enable_series_index);
445        assert!(!MitoConfig::default().experimental_enable_range_index);
446        assert_eq!(
447            MitoConfig::default().experimental_series_index_bucket_width,
448            Duration::from_secs(5 * 24 * 60 * 60)
449        );
450        let mut config: MitoConfig = toml::from_str(
451            "experimental_enable_series_index = true
452             experimental_enable_range_index = false
453             experimental_series_index_max_size = '64MiB'
454             experimental_series_index_maintenance_interval = '30s'
455             experimental_series_index_bucket_width = '2days'",
456        )
457        .unwrap();
458        config.sanitize("/data").unwrap();
459        assert!(config.experimental_enable_series_index);
460        assert_eq!(
461            ReadableSize::mb(64),
462            config.experimental_series_index_max_size
463        );
464        assert!(!config.experimental_enable_range_index);
465        assert_eq!(
466            config.experimental_series_index_maintenance_interval,
467            Duration::from_secs(30)
468        );
469        assert_eq!(
470            config.experimental_series_index_bucket_width,
471            Duration::from_secs(2 * 24 * 60 * 60)
472        );
473        let restored: MitoConfig = toml::from_str(&toml::to_string(&config).unwrap()).unwrap();
474        assert_eq!(config, restored);
475        config.experimental_series_index_max_size = ReadableSize(1023);
476        assert!(config.sanitize("/data").is_err());
477        config.experimental_series_index_max_size = ReadableSize(1024);
478        config.sanitize("/data").unwrap();
479
480        let mut config: MitoConfig =
481            toml::from_str("experimental_series_index_maintenance_interval = '0s'").unwrap();
482        config.sanitize("/data").unwrap();
483        assert_eq!(
484            config.experimental_series_index_maintenance_interval,
485            MitoConfig::default().experimental_series_index_maintenance_interval
486        );
487    }
488
489    #[test]
490    fn test_range_index_config() {
491        let config: MitoConfig = toml::from_str("experimental_enable_series_index = true").unwrap();
492        assert!(!config.experimental_enable_range_index);
493        for enabled in [false, true] {
494            let config: MitoConfig =
495                toml::from_str(&format!("experimental_enable_range_index = {enabled}")).unwrap();
496            assert_eq!(enabled, config.experimental_enable_range_index);
497            let restored: MitoConfig = toml::from_str(&toml::to_string(&config).unwrap()).unwrap();
498            assert_eq!(config, restored);
499        }
500    }
501
502    #[test]
503    fn test_experimental_series_scan_v2_config() {
504        assert!(MitoConfig::default().experimental_series_scan_v2);
505
506        let config: MitoConfig = toml::from_str("experimental_series_scan_v2 = false").unwrap();
507        assert!(!config.experimental_series_scan_v2);
508    }
509}
510
511/// Index build mode.
512#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
513#[serde(rename_all = "snake_case")]
514pub enum IndexBuildMode {
515    /// Build index synchronously.
516    #[default]
517    Sync,
518    /// Build index asynchronously.
519    Async,
520}
521
522#[serde_as]
523#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
524#[serde(default)]
525pub struct IndexConfig {
526    /// Auxiliary directory path for the index in filesystem, used to
527    /// store intermediate files for creating the index and staging files
528    /// for searching the index, defaults to `{data_home}/index_intermediate`.
529    ///
530    /// This path contains two subdirectories:
531    /// - `__intm`: for storing intermediate files used during creating index.
532    /// - `staging`: for storing staging files used during searching index.
533    ///
534    /// The default name for this directory is `index_intermediate` for backward compatibility.
535    pub aux_path: String,
536
537    /// The max capacity of the staging directory.
538    pub staging_size: ReadableSize,
539    /// The TTL of the staging directory.
540    /// Defaults to 7 days.
541    /// Setting it to "0s" to disable TTL.
542    #[serde(with = "humantime_serde")]
543    pub staging_ttl: Option<Duration>,
544
545    /// Index Build Mode
546    pub build_mode: IndexBuildMode,
547
548    /// Write buffer size for creating the index.
549    pub write_buffer_size: ReadableSize,
550
551    /// Cache size for metadata of puffin files. Setting it to 0 to disable the cache.
552    pub metadata_cache_size: ReadableSize,
553    /// Cache size for inverted index content. Setting it to 0 to disable the cache.
554    pub content_cache_size: ReadableSize,
555    /// Page size for inverted index content.
556    pub content_cache_page_size: ReadableSize,
557    /// Cache size for index result. Setting it to 0 to disable the cache.
558    pub result_cache_size: ReadableSize,
559}
560
561impl Default for IndexConfig {
562    fn default() -> Self {
563        Self {
564            aux_path: String::new(),
565            staging_size: ReadableSize::gb(2),
566            staging_ttl: Some(Duration::from_secs(7 * 24 * 60 * 60)),
567            build_mode: IndexBuildMode::default(),
568            write_buffer_size: ReadableSize::mb(8),
569            metadata_cache_size: ReadableSize::mb(64),
570            content_cache_size: ReadableSize::mb(128),
571            content_cache_page_size: ReadableSize::kb(64),
572            result_cache_size: ReadableSize::mb(128),
573        }
574    }
575}
576
577impl IndexConfig {
578    pub fn sanitize(
579        &mut self,
580        data_home: &str,
581        inverted_index: &InvertedIndexConfig,
582    ) -> Result<()> {
583        #[allow(deprecated)]
584        if self.aux_path.is_empty() && !inverted_index.intermediate_path.is_empty() {
585            self.aux_path.clone_from(&inverted_index.intermediate_path);
586            warn!(
587                "`inverted_index.intermediate_path` is deprecated, use
588                 `index.aux_path` instead. Set `index.aux_path` to {}",
589                &inverted_index.intermediate_path
590            )
591        }
592        if self.aux_path.is_empty() {
593            let path = Path::new(data_home).join("index_intermediate");
594            self.aux_path = path.as_os_str().to_string_lossy().to_string();
595        }
596
597        if self.write_buffer_size < MULTIPART_UPLOAD_MINIMUM_SIZE {
598            self.write_buffer_size = MULTIPART_UPLOAD_MINIMUM_SIZE;
599            warn!(
600                "Sanitize index write buffer size to {}",
601                self.write_buffer_size
602            );
603        }
604
605        if self.staging_ttl.map(|ttl| ttl.is_zero()).unwrap_or(false) {
606            self.staging_ttl = None;
607        }
608
609        Ok(())
610    }
611
612    pub fn adjust_buffer_and_cache_size(&mut self, sys_memory: ReadableSize) {
613        let cache_size = cmp::min(sys_memory / MEM_CACHE_SIZE_FACTOR, ReadableSize::mb(128));
614        self.result_cache_size = cmp::min(self.result_cache_size, cache_size);
615        self.content_cache_size = cmp::min(self.content_cache_size, cache_size);
616
617        let metadata_cache_size = cmp::min(
618            sys_memory / INDEX_METADATA_CACHE_SIZE_FACTOR,
619            ReadableSize::mb(64),
620        );
621        self.metadata_cache_size = cmp::min(self.metadata_cache_size, metadata_cache_size);
622    }
623}
624
625/// Operational mode for certain actions.
626#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
627#[serde(rename_all = "snake_case")]
628pub enum Mode {
629    /// The action is performed automatically based on internal criteria.
630    #[default]
631    Auto,
632    /// The action is explicitly disabled.
633    Disable,
634}
635
636impl Mode {
637    /// Whether the action is disabled.
638    pub fn disabled(&self) -> bool {
639        matches!(self, Mode::Disable)
640    }
641
642    /// Whether the action is automatic.
643    pub fn auto(&self) -> bool {
644        matches!(self, Mode::Auto)
645    }
646}
647
648/// Memory threshold for performing certain actions.
649#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
650#[serde(rename_all = "snake_case")]
651pub enum MemoryThreshold {
652    /// Automatically determine the threshold based on internal criteria.
653    #[default]
654    Auto,
655    /// Unlimited memory.
656    Unlimited,
657    /// Fixed memory threshold.
658    #[serde(untagged)]
659    Size(ReadableSize),
660}
661
662/// Configuration options for the inverted index.
663#[serde_as]
664#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
665#[serde(default)]
666pub struct InvertedIndexConfig {
667    /// Whether to create the index on flush: automatically or never.
668    pub create_on_flush: Mode,
669    /// Whether to create the index on compaction: automatically or never.
670    pub create_on_compaction: Mode,
671    /// Whether to apply the index on query: automatically or never.
672    pub apply_on_query: Mode,
673
674    /// Memory threshold for performing an external sort during index creation.
675    pub mem_threshold_on_create: MemoryThreshold,
676
677    #[deprecated = "use [IndexConfig::aux_path] instead"]
678    #[serde(skip_serializing)]
679    pub intermediate_path: String,
680
681    #[deprecated = "use [IndexConfig::write_buffer_size] instead"]
682    #[serde(skip_serializing)]
683    pub write_buffer_size: ReadableSize,
684}
685
686impl Default for InvertedIndexConfig {
687    #[allow(deprecated)]
688    fn default() -> Self {
689        Self {
690            create_on_flush: Mode::Auto,
691            create_on_compaction: Mode::Auto,
692            apply_on_query: Mode::Auto,
693            mem_threshold_on_create: MemoryThreshold::Auto,
694            write_buffer_size: ReadableSize::mb(8),
695            intermediate_path: String::new(),
696        }
697    }
698}
699
700impl InvertedIndexConfig {
701    pub fn mem_threshold_on_create(&self) -> Option<usize> {
702        match self.mem_threshold_on_create {
703            MemoryThreshold::Auto => {
704                if let Some(sys_memory) = get_total_memory_readable() {
705                    Some((sys_memory / INDEX_CREATE_MEM_THRESHOLD_FACTOR).as_bytes() as usize)
706                } else {
707                    Some(ReadableSize::mb(64).as_bytes() as usize)
708                }
709            }
710            MemoryThreshold::Unlimited => None,
711            MemoryThreshold::Size(size) => Some(size.as_bytes() as usize),
712        }
713    }
714}
715
716/// Configuration options for the full-text index.
717#[serde_as]
718#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
719#[serde(default)]
720pub struct FulltextIndexConfig {
721    /// Whether to create the index on flush: automatically or never.
722    pub create_on_flush: Mode,
723    /// Whether to create the index on compaction: automatically or never.
724    pub create_on_compaction: Mode,
725    /// Whether to apply the index on query: automatically or never.
726    pub apply_on_query: Mode,
727    /// Memory threshold for creating the index.
728    pub mem_threshold_on_create: MemoryThreshold,
729    /// Whether to compress the index data.
730    pub compress: bool,
731}
732
733impl Default for FulltextIndexConfig {
734    fn default() -> Self {
735        Self {
736            create_on_flush: Mode::Auto,
737            create_on_compaction: Mode::Auto,
738            apply_on_query: Mode::Auto,
739            mem_threshold_on_create: MemoryThreshold::Auto,
740            compress: true,
741        }
742    }
743}
744
745impl FulltextIndexConfig {
746    pub fn mem_threshold_on_create(&self) -> usize {
747        match self.mem_threshold_on_create {
748            MemoryThreshold::Auto => {
749                if let Some(sys_memory) = get_total_memory_readable() {
750                    (sys_memory / INDEX_CREATE_MEM_THRESHOLD_FACTOR).as_bytes() as _
751                } else {
752                    ReadableSize::mb(64).as_bytes() as _
753                }
754            }
755            MemoryThreshold::Unlimited => usize::MAX,
756            MemoryThreshold::Size(size) => size.as_bytes() as _,
757        }
758    }
759}
760
761/// Configuration options for the bloom filter.
762#[serde_as]
763#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
764#[serde(default)]
765pub struct BloomFilterConfig {
766    /// Whether to create the index on flush: automatically or never.
767    pub create_on_flush: Mode,
768    /// Whether to create the index on compaction: automatically or never.
769    pub create_on_compaction: Mode,
770    /// Whether to apply the index on query: automatically or never.
771    pub apply_on_query: Mode,
772    /// Memory threshold for creating the index.
773    pub mem_threshold_on_create: MemoryThreshold,
774}
775
776impl Default for BloomFilterConfig {
777    fn default() -> Self {
778        Self {
779            create_on_flush: Mode::Auto,
780            create_on_compaction: Mode::Auto,
781            apply_on_query: Mode::Auto,
782            mem_threshold_on_create: MemoryThreshold::Auto,
783        }
784    }
785}
786
787impl BloomFilterConfig {
788    pub fn mem_threshold_on_create(&self) -> Option<usize> {
789        match self.mem_threshold_on_create {
790            MemoryThreshold::Auto => {
791                if let Some(sys_memory) = get_total_memory_readable() {
792                    Some((sys_memory / INDEX_CREATE_MEM_THRESHOLD_FACTOR).as_bytes() as usize)
793                } else {
794                    Some(ReadableSize::mb(64).as_bytes() as usize)
795                }
796            }
797            MemoryThreshold::Unlimited => None,
798            MemoryThreshold::Size(size) => Some(size.as_bytes() as usize),
799        }
800    }
801}
802
803/// Divide cpu num by a non-zero `divisor` and returns at least 1.
804fn divide_num_cpus(divisor: usize) -> usize {
805    debug_assert!(divisor > 0);
806    let cores = get_total_cpu_cores();
807    debug_assert!(cores > 0);
808
809    cores.div_ceil(divisor)
810}