Skip to main content

raft_log/
config.rs

1use std::format;
2
3use crate::ChunkId;
4use crate::errors::InvalidChunkFileName;
5use crate::num;
6
7/// Configuration for Raft-log.
8///
9/// This struct holds various configuration parameters for the Raft-log,
10/// including directory location, cache settings, and chunk management options.
11///
12/// Optional parameters are `Option<T>` in this struct, and default values is
13/// evaluated when a getter method is called.
14#[derive(Clone, Debug, Default)]
15pub struct Config {
16    /// Base directory for storing Raft-log files
17    pub dir: String,
18
19    /// Maximum number of items to keep in the log cache
20    pub log_cache_max_items: Option<usize>,
21
22    /// Maximum capacity of the log cache in bytes
23    pub log_cache_capacity: Option<usize>,
24
25    /// Size of the read buffer in bytes
26    pub read_buffer_size: Option<usize>,
27
28    /// Maximum number of records in a chunk
29    pub chunk_max_records: Option<usize>,
30
31    /// Maximum size of a chunk in bytes
32    pub chunk_max_size: Option<usize>,
33
34    /// Whether to truncate the last half sync-ed record.
35    ///
36    /// If truncate, the chunk is considered successfully opened.
37    /// Otherwise, an io::Error will be returned.
38    pub truncate_incomplete_record: Option<bool>,
39}
40
41impl Config {
42    /// Creates a new Config with the specified directory and default values for
43    /// other fields
44    pub fn new(dir: impl ToString) -> Self {
45        Self {
46            dir: dir.to_string(),
47            ..Default::default()
48        }
49    }
50
51    /// Creates a new Config with all configurable parameters
52    pub fn new_full(
53        dir: impl ToString,
54        log_cache_max_items: Option<usize>,
55        log_cache_capacity: Option<usize>,
56        read_buffer_size: Option<usize>,
57        chunk_max_records: Option<usize>,
58        chunk_max_size: Option<usize>,
59    ) -> Self {
60        Self {
61            dir: dir.to_string(),
62            log_cache_max_items,
63            log_cache_capacity,
64            read_buffer_size,
65            chunk_max_records,
66            chunk_max_size,
67            truncate_incomplete_record: None,
68        }
69    }
70
71    /// Returns the maximum number of items in log cache (defaults to 100,000)
72    pub fn log_cache_max_items(&self) -> usize {
73        self.log_cache_max_items.unwrap_or(100_000)
74    }
75
76    /// Returns the maximum capacity of log cache in bytes (defaults to 1GB)
77    pub fn log_cache_capacity(&self) -> usize {
78        self.log_cache_capacity.unwrap_or(1024 * 1024 * 1024)
79    }
80
81    /// Returns the size of read buffer in bytes (defaults to 64MB)
82    pub fn read_buffer_size(&self) -> usize {
83        self.read_buffer_size.unwrap_or(64 * 1024 * 1024)
84    }
85
86    /// Returns the maximum number of records per chunk (defaults to 1M records)
87    pub fn chunk_max_records(&self) -> usize {
88        self.chunk_max_records.unwrap_or(1024 * 1024)
89    }
90
91    /// Returns the maximum size of a chunk in bytes (defaults to 1GB)
92    pub fn chunk_max_size(&self) -> usize {
93        self.chunk_max_size.unwrap_or(1024 * 1024 * 1024)
94    }
95
96    /// Returns whether to truncate incomplete records (defaults to true)
97    pub fn truncate_incomplete_record(&self) -> bool {
98        self.truncate_incomplete_record.unwrap_or(true)
99    }
100
101    /// Returns the full path for a given chunk ID
102    pub fn chunk_path(&self, chunk_id: ChunkId) -> String {
103        let file_name = Self::chunk_file_name(chunk_id);
104        format!("{}/{}", self.dir, file_name)
105    }
106
107    /// Generates the file name for a given chunk ID
108    ///
109    /// The file name format is "r-{padded_chunk_id}.wal"
110    pub(crate) fn chunk_file_name(chunk_id: ChunkId) -> String {
111        let file_name = num::format_pad_u64(*chunk_id);
112        format!("r-{}.wal", file_name)
113    }
114
115    /// Parses a chunk file name and returns the chunk ID
116    ///
117    /// # Arguments
118    /// * `file_name` - Name of the chunk file (format:
119    ///   "r-{padded_chunk_id}.wal")
120    ///
121    /// # Returns
122    /// * `Ok(u64)` - The chunk ID if parsing succeeds
123    /// * `Err(InvalidChunkFileName)` - If the file name format is invalid
124    pub(crate) fn parse_chunk_file_name(
125        file_name: &str,
126    ) -> Result<u64, InvalidChunkFileName> {
127        // 1. Strip the ".wal" suffix or return an error if it's not there
128        let without_suffix =
129            file_name.strip_suffix(".wal").ok_or_else(|| {
130                InvalidChunkFileName::new(file_name, "has no '.wal' suffix")
131            })?;
132
133        // 2. Strip the "r-" prefix or return an error if it's not there
134        let without_prefix =
135            without_suffix.strip_prefix("r-").ok_or_else(|| {
136                InvalidChunkFileName::new(file_name, "has no 'r-' prefix")
137            })?;
138
139        if without_prefix.len() != 26 {
140            return Err(InvalidChunkFileName::new(
141                file_name,
142                "does not have 26 digit after 'r-' prefix",
143            ));
144        }
145
146        let digits = without_prefix
147            .chars()
148            .filter(|c| c.is_ascii_digit())
149            .collect::<String>();
150
151        // 3. Parse the remaining string as an u64
152        digits.parse::<u64>().map_err(|e| {
153            InvalidChunkFileName::new(
154                file_name,
155                format!("cannot parse as u64: {}", e),
156            )
157        })
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::Config;
164
165    #[test]
166    fn test_parse_chunk_file_name() {
167        assert_eq!(
168            Config::parse_chunk_file_name("r-10_100_000_000_001_200_000.wal"),
169            Ok(10_100_000_000_001_200_000)
170        );
171
172        assert!(
173            Config::parse_chunk_file_name("r-10_100_000_000_001_200_000_1.wal")
174                .is_err()
175        );
176        assert!(Config::parse_chunk_file_name("r-1000000000.wal").is_err());
177        assert!(
178            Config::parse_chunk_file_name("r-10_100_000_000_001_200_000.wall")
179                .is_err()
180        );
181        assert!(
182            Config::parse_chunk_file_name("rrr-10_100_000_000_001_200_000.wal")
183                .is_err()
184        );
185    }
186}