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