1use std::env;
2use std::path::PathBuf;
3use std::str::FromStr;
4
5#[derive(Debug, Clone)]
6pub struct S3Config {
7 pub endpoint: String,
8 pub bucket: String,
9 pub region: String,
10 pub key_prefix: String,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum WalIoEngine {
15 Auto,
16 IoUring,
17 Posix,
18}
19
20impl WalIoEngine {
21 pub fn as_str(self) -> &'static str {
22 match self {
23 WalIoEngine::Auto => "auto",
24 WalIoEngine::IoUring => "io_uring",
25 WalIoEngine::Posix => "posix",
26 }
27 }
28}
29
30impl FromStr for WalIoEngine {
31 type Err = ();
32
33 fn from_str(s: &str) -> Result<Self, Self::Err> {
34 let s = s.trim().to_ascii_lowercase();
35 match s.as_str() {
36 "auto" => Ok(Self::Auto),
37 "io_uring" | "iouring" | "uring" => Ok(Self::IoUring),
38 "posix" | "sync" => Ok(Self::Posix),
39 _ => Err(()),
40 }
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum WatchBacklogMode {
46 Relaxed,
47 Strict,
48}
49
50impl WatchBacklogMode {
51 pub fn as_str(self) -> &'static str {
52 match self {
53 WatchBacklogMode::Relaxed => "relaxed",
54 WatchBacklogMode::Strict => "strict",
55 }
56 }
57}
58
59impl FromStr for WatchBacklogMode {
60 type Err = ();
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 let s = s.trim().to_ascii_lowercase();
64 match s.as_str() {
65 "relaxed" => Ok(Self::Relaxed),
66 "strict" => Ok(Self::Strict),
67 _ => Err(()),
68 }
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum PutAdaptiveMode {
74 Legacy,
75 QueueBacklogDrain,
76}
77
78impl PutAdaptiveMode {
79 pub fn as_str(self) -> &'static str {
80 match self {
81 PutAdaptiveMode::Legacy => "legacy",
82 PutAdaptiveMode::QueueBacklogDrain => "queue_backlog_drain",
83 }
84 }
85}
86
87impl FromStr for PutAdaptiveMode {
88 type Err = ();
89
90 fn from_str(s: &str) -> Result<Self, Self::Err> {
91 let s = s.trim().to_ascii_lowercase();
92 match s.as_str() {
93 "legacy" => Ok(Self::Legacy),
94 "queue_backlog_drain" | "queue-backlog-drain" | "drain" => Ok(Self::QueueBacklogDrain),
95 _ => Err(()),
96 }
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum AstraProfile {
102 Kubernetes,
103 Omni,
104 Gateway,
105 Auto,
106}
107
108impl AstraProfile {
109 pub fn as_str(self) -> &'static str {
110 match self {
111 AstraProfile::Kubernetes => "kubernetes",
112 AstraProfile::Omni => "omni",
113 AstraProfile::Gateway => "gateway",
114 AstraProfile::Auto => "auto",
115 }
116 }
117}
118
119impl FromStr for AstraProfile {
120 type Err = ();
121
122 fn from_str(s: &str) -> Result<Self, Self::Err> {
123 let s = s.trim().to_ascii_lowercase();
124 match s.as_str() {
125 "kubernetes" | "k8s" => Ok(Self::Kubernetes),
126 "omni" => Ok(Self::Omni),
127 "gateway" => Ok(Self::Gateway),
128 "auto" => Ok(Self::Auto),
129 _ => Err(()),
130 }
131 }
132}
133
134#[derive(Debug, Clone)]
135pub struct AstraConfig {
136 pub node_id: u64,
137 pub client_addr: String,
138 pub raft_addr: String,
139 pub raft_advertise_addr: String,
140 pub peers: Vec<String>,
141 pub data_dir: PathBuf,
142 pub max_memory_mb: usize,
143 pub hot_revision_window: i64,
144 pub watch_ring_capacity: usize,
145 pub watch_broadcast_capacity: usize,
146 pub watch_backlog_mode: WatchBacklogMode,
147 pub tiering_interval_secs: u64,
148 pub sst_target_bytes: usize,
149 pub wal_max_batch_requests: usize,
150 pub wal_max_batch_bytes: usize,
151 pub wal_max_linger_us: u64,
152 pub wal_low_concurrency_threshold: usize,
153 pub wal_low_linger_us: u64,
154 pub wal_pending_limit: usize,
155 pub wal_segment_bytes: u64,
156 pub wal_io_engine: WalIoEngine,
157 pub bg_io_throttle_enabled: bool,
158 pub bg_io_tokens_per_sec: u64,
159 pub bg_io_burst_tokens: u64,
160 pub bg_io_sqe_throttle_enabled: bool,
161 pub bg_io_sqe_tokens_per_sec: u64,
162 pub bg_io_sqe_burst_tokens: u64,
163 pub bg_io_min_chunk_bytes: usize,
164 pub bg_io_max_chunk_bytes: usize,
165 pub lsm_max_l0_files: usize,
166 pub lsm_stall_at_files: usize,
167 pub lsm_stall_max_delay_ms: u64,
168 pub lsm_reject_after_ms: u64,
169 pub lsm_reject_extra_files: usize,
170 pub lsm_synth_file_bytes: usize,
171 pub lsm_delay_band_l0_5_ms: u64,
172 pub lsm_delay_band_l0_6_ms: u64,
173 pub lsm_delay_band_l0_7_ms: u64,
174 pub list_prefix_filter_enabled: bool,
175 pub list_revision_filter_enabled: bool,
176 pub list_prefetch_enabled: bool,
177 pub list_prefetch_pages: usize,
178 pub list_prefetch_cache_entries: usize,
179 pub read_isolation_enabled: bool,
180 pub gateway_read_ticket_enabled: bool,
181 pub gateway_read_ticket_ttl_ms: u64,
182 pub gateway_singleflight_enabled: bool,
183 pub gateway_singleflight_max_waiters: usize,
184 pub put_batch_max_requests: usize,
185 pub put_batch_min_requests: usize,
186 pub put_batch_max_linger_us: u64,
187 pub put_batch_min_linger_us: u64,
188 pub put_batch_max_bytes: usize,
189 pub put_batch_pending_limit: usize,
190 pub put_adaptive_enabled: bool,
191 pub put_adaptive_mode: PutAdaptiveMode,
192 pub put_adaptive_min_request_floor: usize,
193 pub put_dispatch_concurrency: usize,
194 pub put_target_queue_depth: usize,
195 pub put_p99_budget_ms: u64,
196 pub put_target_queue_wait_p99_ms: u64,
197 pub put_target_quorum_ack_p99_ms: u64,
198 pub put_token_lane_enabled: bool,
199 pub put_token_dict_max_entries: usize,
200 pub put_token_min_reuse: usize,
201 pub profile: AstraProfile,
202 pub profile_sample_secs: u64,
203 pub profile_min_dwell_secs: u64,
204 pub qos_tier0_prefixes: Vec<Vec<u8>>,
205 pub qos_tier0_suffixes: Vec<Vec<u8>>,
206 pub qos_tier0_max_batch_requests: usize,
207 pub qos_tier0_max_linger_us: u64,
208 pub raft_timeline_enabled: bool,
209 pub raft_timeline_sample_rate: u64,
210 pub raft_election_timeout_min_ms: u64,
211 pub raft_election_timeout_max_ms: u64,
212 pub raft_heartbeat_interval_ms: u64,
213 pub raft_max_payload_entries: u64,
214 pub raft_replication_lag_threshold: u64,
215 pub grpc_max_concurrent_streams: u32,
216 pub grpc_http2_keepalive_interval_ms: u64,
217 pub grpc_http2_keepalive_timeout_ms: u64,
218 pub grpc_tcp_keepalive_ms: u64,
219 pub chaos_append_ack_delay_enabled: bool,
220 pub chaos_append_ack_delay_min_ms: u64,
221 pub chaos_append_ack_delay_max_ms: u64,
222 pub chaos_append_ack_delay_node_id: u64,
223 pub metrics_enabled: bool,
224 pub metrics_addr: String,
225 pub auth_enabled: bool,
226 pub auth_issuer: Option<String>,
227 pub auth_audience: Option<String>,
228 pub auth_jwks_url: Option<String>,
229 pub auth_jwt_hs256_secret: Option<String>,
230 pub auth_tenant_claim: String,
231 pub tenant_virtualization_enabled: bool,
232 pub s3: Option<S3Config>,
233}
234
235impl AstraConfig {
236 pub fn from_env() -> Self {
237 let node_id = parse_env("ASTRAD_NODE_ID", 1_u64);
238 let client_addr =
239 env::var("ASTRAD_CLIENT_ADDR").unwrap_or_else(|_| "0.0.0.0:2379".to_string());
240 let raft_addr = env::var("ASTRAD_RAFT_ADDR").unwrap_or_else(|_| "0.0.0.0:2380".to_string());
241 let raft_advertise_addr =
242 env::var("ASTRAD_RAFT_ADVERTISE_ADDR").unwrap_or_else(|_| raft_addr.clone());
243 let peers = env::var("ASTRAD_PEERS")
244 .map(|s| {
245 s.split(',')
246 .map(str::trim)
247 .filter(|x| !x.is_empty())
248 .map(ToOwned::to_owned)
249 .collect::<Vec<_>>()
250 })
251 .unwrap_or_default();
252
253 let data_dir = env::var("ASTRAD_DATA_DIR")
254 .map(PathBuf::from)
255 .unwrap_or_else(|_| PathBuf::from("./data"));
256
257 let max_memory_mb = parse_env("ASTRAD_MAX_MEMORY_MB", 256_usize);
258 let hot_revision_window = parse_env("ASTRAD_HOT_REV_WINDOW", 10_000_i64);
259 let watch_ring_capacity = parse_env("ASTRAD_WATCH_RING_CAPACITY", 2_048_usize).max(1);
260 let watch_broadcast_capacity =
261 parse_env("ASTRAD_WATCH_BROADCAST_CAPACITY", 1_024_usize).max(32);
262 let watch_backlog_mode = env::var("ASTRAD_WATCH_BACKLOG_MODE")
263 .ok()
264 .and_then(|v| v.parse::<WatchBacklogMode>().ok())
265 .unwrap_or(WatchBacklogMode::Relaxed);
266 let tiering_interval_secs = parse_env("ASTRAD_TIERING_INTERVAL_SECS", 30_u64);
267 let sst_target_bytes =
268 parse_env("ASTRAD_SST_TARGET_BYTES", 64 * 1024 * 1024_usize).max(64 * 1024 * 1024);
269
270 let wal_max_batch_requests = parse_env(
271 "ASTRAD_WAL_MAX_BATCH_REQUESTS",
272 parse_env("ASTRAD_WAL_BATCH_MAX_ENTRIES", 1_000_usize),
273 )
274 .max(1);
275 let wal_max_batch_bytes =
276 parse_env("ASTRAD_WAL_BATCH_MAX_BYTES", 8 * 1024 * 1024_usize).max(4 * 1024);
277 let wal_max_linger_us = parse_env("ASTRAD_WAL_MAX_LINGER_US", 2_000_u64);
278 let wal_low_concurrency_threshold =
279 parse_env("ASTRAD_WAL_LOW_CONCURRENCY_THRESHOLD", 5_usize).max(1);
280 let wal_low_linger_us = parse_env("ASTRAD_WAL_LOW_LINGER_US", 0_u64);
281 let wal_pending_limit = parse_env("ASTRAD_WAL_PENDING_LIMIT", 2_000_usize).max(1);
282 let wal_segment_bytes =
283 parse_env("ASTRAD_WAL_SEGMENT_BYTES", 64 * 1024 * 1024_u64).max(4 * 1024);
284 let wal_io_engine = env::var("ASTRAD_WAL_IO_ENGINE")
285 .ok()
286 .and_then(|v| v.parse::<WalIoEngine>().ok())
287 .unwrap_or(WalIoEngine::Auto);
288 let bg_io_throttle_enabled = parse_env("ASTRAD_BG_IO_THROTTLE_ENABLED", false);
289 let bg_io_tokens_per_sec = parse_env("ASTRAD_BG_IO_TOKENS_PER_SEC", 8_192_u64).max(1);
290 let bg_io_burst_tokens = parse_env("ASTRAD_BG_IO_BURST_TOKENS", 16_384_u64).max(1);
291 let bg_io_sqe_throttle_enabled = parse_env("ASTRAD_BG_IO_SQE_THROTTLE_ENABLED", true);
292 let bg_io_sqe_tokens_per_sec =
293 parse_env("ASTRAD_BG_IO_SQE_TOKENS_PER_SEC", 1_024_u64).max(1);
294 let bg_io_sqe_burst_tokens = parse_env("ASTRAD_BG_IO_SQE_BURST", 2_048_u64).max(1);
295 let bg_io_min_chunk_bytes =
296 parse_env("ASTRAD_BG_IO_MIN_CHUNK_BYTES", 256 * 1024_usize).max(4 * 1024);
297 let bg_io_max_chunk_bytes =
298 parse_env("ASTRAD_BG_IO_MAX_CHUNK_BYTES", 256 * 1024_usize).max(4 * 1024);
299 let lsm_max_l0_files = parse_env("ASTRAD_LSM_MAX_L0_FILES", 8_usize).max(1);
300 let lsm_stall_at_files = parse_env("ASTRAD_LSM_STALL_AT_FILES", 5_usize)
301 .max(1)
302 .min(lsm_max_l0_files);
303 let lsm_stall_max_delay_ms = parse_env("ASTRAD_LSM_STALL_MAX_DELAY_MS", 200_u64).max(1);
304 let lsm_reject_after_ms = parse_env("ASTRAD_LSM_REJECT_AFTER_MS", 800_u64).max(1);
305 let lsm_reject_extra_files = parse_env("ASTRAD_LSM_REJECT_EXTRA_FILES", 1_usize);
306 let lsm_synth_file_bytes =
307 parse_env("ASTRAD_LSM_SYNTH_FILE_BYTES", 8 * 1024 * 1024_usize).max(4096);
308 let lsm_delay_band_l0_5_ms = parse_env("ASTRAD_LSM_DELAY_BAND_L0_5_MS", 1_u64).max(1);
309 let lsm_delay_band_l0_6_ms =
310 parse_env("ASTRAD_LSM_DELAY_BAND_L0_6_MS", 5_u64).max(lsm_delay_band_l0_5_ms);
311 let lsm_delay_band_l0_7_ms =
312 parse_env("ASTRAD_LSM_DELAY_BAND_L0_7_MS", 20_u64).max(lsm_delay_band_l0_6_ms);
313 let list_prefix_filter_enabled = parse_env("ASTRAD_LIST_PREFIX_FILTER_ENABLED", true);
314 let list_revision_filter_enabled = parse_env("ASTRAD_LIST_REVISION_FILTER_ENABLED", true);
315 let list_prefetch_enabled = parse_env("ASTRAD_LIST_PREFETCH_ENABLED", true);
316 let list_prefetch_pages = parse_env("ASTRAD_LIST_PREFETCH_PAGES", 2_usize).max(1);
317 let list_prefetch_cache_entries =
318 parse_env("ASTRAD_LIST_PREFETCH_CACHE_ENTRIES", 4_096_usize).max(1);
319 let read_isolation_enabled = parse_env("ASTRAD_READ_ISOLATION_ENABLED", true);
320 let gateway_read_ticket_enabled = parse_env("ASTRAD_GATEWAY_READ_TICKET_ENABLED", false);
321 let gateway_read_ticket_ttl_ms =
322 parse_env("ASTRAD_GATEWAY_READ_TICKET_TTL_MS", 20_u64).max(1);
323 let gateway_singleflight_enabled = parse_env("ASTRAD_GATEWAY_SINGLEFLIGHT_ENABLED", false);
324 let gateway_singleflight_max_waiters =
325 parse_env("ASTRAD_GATEWAY_SINGLEFLIGHT_MAX_WAITERS", 4_096_usize).max(1);
326 let put_batch_max_requests = parse_env("ASTRAD_PUT_BATCH_MAX_REQUESTS", 256_usize).max(1);
327 let put_batch_min_requests = parse_env("ASTRAD_PUT_BATCH_MIN_REQUESTS", 16_usize)
328 .max(1)
329 .min(put_batch_max_requests);
330 let put_batch_max_linger_us = parse_env("ASTRAD_PUT_BATCH_MAX_LINGER_US", 2_000_u64);
331 let put_batch_min_linger_us =
332 parse_env("ASTRAD_PUT_BATCH_MIN_LINGER_US", 50_u64).min(put_batch_max_linger_us);
333 let put_batch_max_bytes =
334 parse_env("ASTRAD_PUT_BATCH_MAX_BYTES", 262_144_usize).max(4 * 1024);
335 let put_batch_pending_limit =
336 parse_env("ASTRAD_PUT_BATCH_PENDING_LIMIT", 10_000_usize).max(1);
337 let put_adaptive_enabled = parse_env("ASTRAD_PUT_ADAPTIVE_ENABLED", true);
338 let put_adaptive_mode = env::var("ASTRAD_PUT_ADAPTIVE_MODE")
339 .ok()
340 .and_then(|v| v.parse::<PutAdaptiveMode>().ok())
341 .unwrap_or(PutAdaptiveMode::QueueBacklogDrain);
342 let put_adaptive_min_request_floor =
343 parse_env("ASTRAD_PUT_ADAPTIVE_MIN_REQUEST_FLOOR", 128_usize).max(1);
344 let put_dispatch_concurrency = parse_env("ASTRAD_PUT_DISPATCH_CONCURRENCY", 1_usize).max(1);
345 let put_target_queue_depth = parse_env("ASTRAD_PUT_TARGET_QUEUE_DEPTH", 512_usize).max(1);
346 let put_p99_budget_ms = parse_env("ASTRAD_PUT_P99_BUDGET_MS", 550_u64).max(1);
347 let put_target_queue_wait_p99_ms =
348 parse_env("ASTRAD_PUT_TARGET_QUEUE_WAIT_P99_MS", 120_u64).max(1);
349 let put_target_quorum_ack_p99_ms =
350 parse_env("ASTRAD_PUT_TARGET_QUORUM_ACK_P99_MS", 300_u64).max(1);
351 let put_token_lane_enabled = parse_env("ASTRAD_PUT_TOKEN_LANE_ENABLED", true);
352 let put_token_dict_max_entries =
353 parse_env("ASTRAD_PUT_TOKEN_DICT_MAX_ENTRIES", 4_096_usize).max(1);
354 let put_token_min_reuse = parse_env("ASTRAD_PUT_TOKEN_MIN_REUSE", 2_usize).max(1);
355 let profile = env::var("ASTRAD_PROFILE")
356 .ok()
357 .and_then(|v| v.parse::<AstraProfile>().ok())
358 .unwrap_or(AstraProfile::Auto);
359 let profile_sample_secs = parse_env("ASTRAD_PROFILE_SAMPLE_SECS", 5_u64).max(1);
360 let profile_min_dwell_secs = parse_env("ASTRAD_PROFILE_MIN_DWELL_SECS", 10_u64).max(1);
361 let qos_tier0_prefixes = parse_csv_bytes(
362 "ASTRAD_QOS_TIER0_PREFIXES",
363 &["/registry/leases/", "/omni/locks/"],
364 );
365 let qos_tier0_suffixes =
366 parse_csv_bytes("ASTRAD_QOS_TIER0_SUFFIXES", &["/leader", "/lock"]);
367 let qos_tier0_max_batch_requests =
368 parse_env("ASTRAD_QOS_TIER0_MAX_BATCH_REQUESTS", 32_usize).max(1);
369 let qos_tier0_max_linger_us = parse_env("ASTRAD_QOS_TIER0_MAX_LINGER_US", 0_u64);
370 let raft_timeline_enabled = parse_env("ASTRAD_RAFT_TIMELINE_ENABLED", true);
371 let raft_timeline_sample_rate =
372 parse_env("ASTRAD_RAFT_TIMELINE_SAMPLE_RATE", 64_u64).max(1);
373 let raft_election_timeout_min_ms =
374 parse_env("ASTRAD_RAFT_ELECTION_TIMEOUT_MIN_MS", 2_500_u64).max(100);
375 let raft_election_timeout_max_ms = parse_env(
376 "ASTRAD_RAFT_ELECTION_TIMEOUT_MAX_MS",
377 raft_election_timeout_min_ms.saturating_mul(2).max(500),
378 )
379 .max(raft_election_timeout_min_ms.saturating_add(1));
380 let raft_heartbeat_interval_ms = parse_env("ASTRAD_RAFT_HEARTBEAT_INTERVAL_MS", 350_u64)
381 .max(10)
382 .min(raft_election_timeout_min_ms / 2);
383 let raft_max_payload_entries =
384 parse_env("ASTRAD_RAFT_MAX_PAYLOAD_ENTRIES", 5_000_u64).max(1);
385 let raft_replication_lag_threshold =
386 parse_env("ASTRAD_RAFT_REPLICATION_LAG_THRESHOLD", 2_048_u64).max(1);
387 let grpc_max_concurrent_streams =
388 parse_env("ASTRAD_GRPC_MAX_CONCURRENT_STREAMS", 65_535_u32).max(64);
389 let grpc_http2_keepalive_interval_ms =
390 parse_env("ASTRAD_GRPC_HTTP2_KEEPALIVE_INTERVAL_MS", 15_000_u64).max(1_000);
391 let grpc_http2_keepalive_timeout_ms =
392 parse_env("ASTRAD_GRPC_HTTP2_KEEPALIVE_TIMEOUT_MS", 5_000_u64).max(500);
393 let grpc_tcp_keepalive_ms =
394 parse_env("ASTRAD_GRPC_TCP_KEEPALIVE_MS", 30_000_u64).max(1_000);
395 let chaos_append_ack_delay_enabled =
396 parse_env("ASTRAD_CHAOS_APPEND_ACK_DELAY_ENABLED", false);
397 let chaos_append_ack_delay_min_ms =
398 parse_env("ASTRAD_CHAOS_APPEND_ACK_DELAY_MIN_MS", 500_u64).max(1);
399 let chaos_append_ack_delay_max_ms =
400 parse_env("ASTRAD_CHAOS_APPEND_ACK_DELAY_MAX_MS", 2_000_u64)
401 .max(chaos_append_ack_delay_min_ms);
402 let chaos_append_ack_delay_node_id =
403 parse_env("ASTRAD_CHAOS_APPEND_ACK_DELAY_NODE_ID", 0_u64);
404 let metrics_enabled = parse_env("ASTRAD_METRICS_ENABLED", true);
405 let metrics_addr =
406 env::var("ASTRAD_METRICS_ADDR").unwrap_or_else(|_| "0.0.0.0:9479".to_string());
407 let auth_enabled = parse_env("ASTRAD_AUTH_ENABLED", false);
408 let auth_issuer = opt_env("ASTRAD_AUTH_ISSUER");
409 let auth_audience = opt_env("ASTRAD_AUTH_AUDIENCE");
410 let auth_jwks_url = opt_env("ASTRAD_AUTH_JWKS_URL");
411 let auth_jwt_hs256_secret = opt_env("ASTRAD_AUTH_JWT_HS256_SECRET");
412 let auth_tenant_claim =
413 env::var("ASTRAD_AUTH_TENANT_CLAIM").unwrap_or_else(|_| "tenant_id".to_string());
414 let tenant_virtualization_enabled =
415 parse_env("ASTRAD_TENANT_VIRTUALIZATION_ENABLED", auth_enabled);
416
417 let s3 = match (
418 env::var("ASTRAD_S3_ENDPOINT").ok(),
419 env::var("ASTRAD_S3_BUCKET").ok(),
420 ) {
421 (Some(endpoint), Some(bucket)) => Some(S3Config {
422 endpoint,
423 bucket,
424 region: env::var("ASTRAD_S3_REGION").unwrap_or_else(|_| "us-east-1".to_string()),
425 key_prefix: env::var("ASTRAD_S3_PREFIX").unwrap_or_else(|_| "astra".to_string()),
426 }),
427 _ => None,
428 };
429
430 Self {
431 node_id,
432 client_addr,
433 raft_addr,
434 raft_advertise_addr,
435 peers,
436 data_dir,
437 max_memory_mb,
438 hot_revision_window,
439 watch_ring_capacity,
440 watch_broadcast_capacity,
441 watch_backlog_mode,
442 tiering_interval_secs,
443 sst_target_bytes,
444 wal_max_batch_requests,
445 wal_max_batch_bytes,
446 wal_max_linger_us,
447 wal_low_concurrency_threshold,
448 wal_low_linger_us,
449 wal_pending_limit,
450 wal_segment_bytes,
451 wal_io_engine,
452 bg_io_throttle_enabled,
453 bg_io_tokens_per_sec,
454 bg_io_burst_tokens,
455 bg_io_sqe_throttle_enabled,
456 bg_io_sqe_tokens_per_sec,
457 bg_io_sqe_burst_tokens,
458 bg_io_min_chunk_bytes,
459 bg_io_max_chunk_bytes,
460 lsm_max_l0_files,
461 lsm_stall_at_files,
462 lsm_stall_max_delay_ms,
463 lsm_reject_after_ms,
464 lsm_reject_extra_files,
465 lsm_synth_file_bytes,
466 lsm_delay_band_l0_5_ms,
467 lsm_delay_band_l0_6_ms,
468 lsm_delay_band_l0_7_ms,
469 list_prefix_filter_enabled,
470 list_revision_filter_enabled,
471 list_prefetch_enabled,
472 list_prefetch_pages,
473 list_prefetch_cache_entries,
474 read_isolation_enabled,
475 gateway_read_ticket_enabled,
476 gateway_read_ticket_ttl_ms,
477 gateway_singleflight_enabled,
478 gateway_singleflight_max_waiters,
479 put_batch_max_requests,
480 put_batch_min_requests,
481 put_batch_max_linger_us,
482 put_batch_min_linger_us,
483 put_batch_max_bytes,
484 put_batch_pending_limit,
485 put_adaptive_enabled,
486 put_adaptive_mode,
487 put_adaptive_min_request_floor,
488 put_dispatch_concurrency,
489 put_target_queue_depth,
490 put_p99_budget_ms,
491 put_target_queue_wait_p99_ms,
492 put_target_quorum_ack_p99_ms,
493 put_token_lane_enabled,
494 put_token_dict_max_entries,
495 put_token_min_reuse,
496 profile,
497 profile_sample_secs,
498 profile_min_dwell_secs,
499 qos_tier0_prefixes,
500 qos_tier0_suffixes,
501 qos_tier0_max_batch_requests,
502 qos_tier0_max_linger_us,
503 raft_timeline_enabled,
504 raft_timeline_sample_rate,
505 raft_election_timeout_min_ms,
506 raft_election_timeout_max_ms,
507 raft_heartbeat_interval_ms,
508 raft_max_payload_entries,
509 raft_replication_lag_threshold,
510 grpc_max_concurrent_streams,
511 grpc_http2_keepalive_interval_ms,
512 grpc_http2_keepalive_timeout_ms,
513 grpc_tcp_keepalive_ms,
514 chaos_append_ack_delay_enabled,
515 chaos_append_ack_delay_min_ms,
516 chaos_append_ack_delay_max_ms,
517 chaos_append_ack_delay_node_id,
518 metrics_enabled,
519 metrics_addr,
520 auth_enabled,
521 auth_issuer,
522 auth_audience,
523 auth_jwks_url,
524 auth_jwt_hs256_secret,
525 auth_tenant_claim,
526 tenant_virtualization_enabled,
527 s3,
528 }
529 }
530
531 pub fn max_memory_bytes(&self) -> usize {
532 self.max_memory_mb.saturating_mul(1024 * 1024)
533 }
534}
535
536fn parse_env<T>(key: &str, default: T) -> T
537where
538 T: std::str::FromStr,
539{
540 env::var(key)
541 .ok()
542 .and_then(|s| s.parse::<T>().ok())
543 .unwrap_or(default)
544}
545
546fn opt_env(key: &str) -> Option<String> {
547 env::var(key)
548 .ok()
549 .map(|v| v.trim().to_string())
550 .filter(|v| !v.is_empty())
551}
552
553fn parse_csv_bytes(key: &str, default: &[&str]) -> Vec<Vec<u8>> {
554 if let Ok(raw) = env::var(key) {
555 let parsed = raw
556 .split(',')
557 .map(str::trim)
558 .filter(|s| !s.is_empty())
559 .map(|s| s.as_bytes().to_vec())
560 .collect::<Vec<_>>();
561 if !parsed.is_empty() {
562 return parsed;
563 }
564 }
565 default.iter().map(|s| s.as_bytes().to_vec()).collect()
566}