Skip to main content

sort_governor/
config.rs

1//! Static caps the planner works within. These are process-lifetime
2//! constants (derived once from the file-descriptor rlimit); the live,
3//! moment-to-moment pressure lives in [`crate::SorterSnapshot`].
4
5/// Default in-memory ceiling: sorts estimated at or below this never spill
6/// when the budget has headroom.
7const DEFAULT_IN_MEMORY_CEILING_BYTES: u64 = 8 * 1024 * 1024;
8/// Default hard cap on concurrently-open merge readers per sort.
9const DEFAULT_MAX_FAN_IN: u32 = 64;
10/// Default cap on concurrent external sorts in the process.
11const DEFAULT_MAX_CONCURRENT_EXTERNAL: u32 = 8;
12/// Default file descriptors reserved for everything that is not sorting
13/// (sockets, the block store, journals, …).
14const DEFAULT_FD_SAFETY_MARGIN: u32 = 96;
15/// Default floor for a spill run buffer.
16const DEFAULT_MIN_RUN_BUFFER_BYTES: u64 = 1024 * 1024;
17/// Default ceiling for a spill run buffer. Deliberately generous: under fd
18/// pressure the planner raises the buffer to keep the run count — and thus
19/// the merge-pass depth — bounded.
20const DEFAULT_MAX_RUN_BUFFER_BYTES: u64 = 256 * 1024 * 1024;
21
22/// Process-lifetime caps for the Sorter's planner.
23#[derive(Debug, Clone)]
24pub struct SorterConfig {
25    in_memory_ceiling_bytes: u64,
26    max_fan_in: u32,
27    max_concurrent_external: u32,
28    fd_safety_margin: u32,
29    min_run_buffer_bytes: u64,
30    max_run_buffer_bytes: u64,
31}
32
33impl Default for SorterConfig {
34    fn default() -> Self {
35        Self {
36            in_memory_ceiling_bytes: DEFAULT_IN_MEMORY_CEILING_BYTES,
37            max_fan_in: DEFAULT_MAX_FAN_IN,
38            max_concurrent_external: DEFAULT_MAX_CONCURRENT_EXTERNAL,
39            fd_safety_margin: DEFAULT_FD_SAFETY_MARGIN,
40            min_run_buffer_bytes: DEFAULT_MIN_RUN_BUFFER_BYTES,
41            max_run_buffer_bytes: DEFAULT_MAX_RUN_BUFFER_BYTES,
42        }
43    }
44}
45
46impl SorterConfig {
47    /// Derive caps from the process's soft file-descriptor limit, clamping
48    /// the per-sort fan-in ceiling so it can never approach the limit even
49    /// before the live snapshot rations it further.
50    #[must_use]
51    pub fn from_fd_limit(soft_fd_limit: u32) -> Self {
52        let usable = soft_fd_limit.saturating_sub(DEFAULT_FD_SAFETY_MARGIN);
53        let max_fan_in = (usable / DEFAULT_MAX_CONCURRENT_EXTERNAL).clamp(2, DEFAULT_MAX_FAN_IN);
54        Self {
55            max_fan_in,
56            ..Self::default()
57        }
58    }
59
60    /// In-memory ceiling in bytes.
61    #[must_use]
62    pub fn in_memory_ceiling_bytes(&self) -> u64 {
63        self.in_memory_ceiling_bytes
64    }
65
66    /// Hard cap on concurrently-open merge readers per sort.
67    #[must_use]
68    pub fn max_fan_in(&self) -> u32 {
69        self.max_fan_in
70    }
71
72    /// Cap on concurrent external sorts in the process.
73    #[must_use]
74    pub fn max_concurrent_external(&self) -> u32 {
75        self.max_concurrent_external
76    }
77
78    /// File descriptors reserved for non-sort work.
79    #[must_use]
80    pub fn fd_safety_margin(&self) -> u32 {
81        self.fd_safety_margin
82    }
83
84    /// Floor for a spill run buffer.
85    #[must_use]
86    pub fn min_run_buffer_bytes(&self) -> u64 {
87        self.min_run_buffer_bytes
88    }
89
90    /// Ceiling for a spill run buffer.
91    #[must_use]
92    pub fn max_run_buffer_bytes(&self) -> u64 {
93        self.max_run_buffer_bytes
94    }
95}