Skip to main content

astra_core/
config.rs

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}