risingwave_common/config/
storage.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use foyer::{
16    Compression, LfuConfig, LruConfig, RecoverMode, RuntimeOptions, S3FifoConfig, Throttle,
17};
18
19use super::*;
20
21/// The section `[storage]` in `risingwave.toml`.
22#[derive(Clone, Debug, Serialize, Deserialize, DefaultFromSerde, ConfigDoc)]
23pub struct StorageConfig {
24    /// parallelism while syncing share buffers into L0 SST. Should NOT be 0.
25    #[serde(default = "default::storage::share_buffers_sync_parallelism")]
26    pub share_buffers_sync_parallelism: u32,
27
28    /// Worker threads number of dedicated tokio runtime for share buffer compaction. 0 means use
29    /// tokio's default value (number of CPU core).
30    #[serde(default = "default::storage::share_buffer_compaction_worker_threads_number")]
31    pub share_buffer_compaction_worker_threads_number: u32,
32
33    /// Configure the maximum shared buffer size in MB explicitly. Writes attempting to exceed the capacity
34    /// will stall until there is enough space. The overridden value will only be effective if:
35    /// 1. `block_cache_capacity_mb` and `meta_cache_capacity_mb` are also configured explicitly.
36    /// 2. `block_cache_capacity_mb` + `meta_cache_capacity_mb` + `meta_cache_capacity_mb` doesn't exceed 0.3 * non-reserved memory.
37    #[serde(default)]
38    pub shared_buffer_capacity_mb: Option<usize>,
39
40    /// The shared buffer will start flushing data to object when the ratio of memory usage to the
41    /// shared buffer capacity exceed such ratio.
42    #[serde(default = "default::storage::shared_buffer_flush_ratio")]
43    pub shared_buffer_flush_ratio: f32,
44
45    /// The minimum total flush size of shared buffer spill. When a shared buffer spilled is trigger,
46    /// the total flush size across multiple epochs should be at least higher than this size.
47    #[serde(default = "default::storage::shared_buffer_min_batch_flush_size_mb")]
48    pub shared_buffer_min_batch_flush_size_mb: usize,
49
50    /// The threshold for the number of immutable memtables to merge to a new imm.
51    #[serde(default = "default::storage::imm_merge_threshold")]
52    #[deprecated]
53    pub imm_merge_threshold: usize,
54
55    /// Whether to enable write conflict detection
56    #[serde(default = "default::storage::write_conflict_detection_enabled")]
57    pub write_conflict_detection_enabled: bool,
58
59    #[serde(default)]
60    #[config_doc(nested)]
61    pub cache: CacheConfig,
62
63    /// DEPRECATED: This config will be deprecated in the future version, use `storage.cache.block_cache_capacity_mb` instead.
64    #[serde(default)]
65    pub block_cache_capacity_mb: Option<usize>,
66
67    /// DEPRECATED: This config will be deprecated in the future version, use `storage.cache.meta_cache_capacity_mb` instead.
68    #[serde(default)]
69    pub meta_cache_capacity_mb: Option<usize>,
70
71    /// DEPRECATED: This config will be deprecated in the future version, use `storage.cache.block_cache_eviction.high_priority_ratio_in_percent` with `storage.cache.block_cache_eviction.algorithm = "Lru"` instead.
72    #[serde(default)]
73    pub high_priority_ratio_in_percent: Option<usize>,
74
75    /// max memory usage for large query
76    #[serde(default)]
77    pub prefetch_buffer_capacity_mb: Option<usize>,
78
79    #[serde(default = "default::storage::max_cached_recent_versions_number")]
80    pub max_cached_recent_versions_number: usize,
81
82    /// max prefetch block number
83    #[serde(default = "default::storage::max_prefetch_block_number")]
84    pub max_prefetch_block_number: usize,
85
86    #[serde(default = "default::storage::disable_remote_compactor")]
87    pub disable_remote_compactor: bool,
88
89    /// Number of tasks shared buffer can upload in parallel.
90    #[serde(default = "default::storage::share_buffer_upload_concurrency")]
91    pub share_buffer_upload_concurrency: usize,
92
93    #[serde(default)]
94    pub compactor_memory_limit_mb: Option<usize>,
95
96    /// Compactor calculates the maximum number of tasks that can be executed on the node based on
97    /// `worker_num` and `compactor_max_task_multiplier`.
98    /// `max_pull_task_count` = `worker_num` * `compactor_max_task_multiplier`
99    #[serde(default = "default::storage::compactor_max_task_multiplier")]
100    pub compactor_max_task_multiplier: f32,
101
102    /// The percentage of memory available when compactor is deployed separately.
103    /// `non_reserved_memory_bytes` = `system_memory_available_bytes` * `compactor_memory_available_proportion`
104    #[serde(default = "default::storage::compactor_memory_available_proportion")]
105    pub compactor_memory_available_proportion: f64,
106
107    /// Number of SST ids fetched from meta per RPC
108    #[serde(default = "default::storage::sstable_id_remote_fetch_number")]
109    pub sstable_id_remote_fetch_number: u32,
110
111    #[serde(default = "default::storage::min_sstable_size_mb")]
112    pub min_sstable_size_mb: u32,
113
114    #[serde(default)]
115    #[config_doc(nested)]
116    pub data_file_cache: FileCacheConfig,
117
118    #[serde(default)]
119    #[config_doc(nested)]
120    pub meta_file_cache: FileCacheConfig,
121
122    #[serde(default)]
123    #[config_doc(nested)]
124    pub cache_refill: CacheRefillConfig,
125
126    /// Whether to enable streaming upload for sstable.
127    #[serde(default = "default::storage::min_sst_size_for_streaming_upload")]
128    pub min_sst_size_for_streaming_upload: u64,
129
130    #[serde(default = "default::storage::max_concurrent_compaction_task_number")]
131    pub max_concurrent_compaction_task_number: u64,
132
133    #[serde(default = "default::storage::max_preload_wait_time_mill")]
134    pub max_preload_wait_time_mill: u64,
135
136    #[serde(default = "default::storage::max_version_pinning_duration_sec")]
137    pub max_version_pinning_duration_sec: u64,
138
139    #[serde(default = "default::storage::compactor_max_sst_key_count")]
140    pub compactor_max_sst_key_count: u64,
141    // DEPRECATED: This config will be deprecated in the future version, use `storage.compactor_iter_max_io_retry_times` instead.
142    #[serde(default = "default::storage::compact_iter_recreate_timeout_ms")]
143    pub compact_iter_recreate_timeout_ms: u64,
144    #[serde(default = "default::storage::compactor_max_sst_size")]
145    pub compactor_max_sst_size: u64,
146    #[serde(default = "default::storage::enable_fast_compaction")]
147    pub enable_fast_compaction: bool,
148    #[serde(default = "default::storage::check_compaction_result")]
149    pub check_compaction_result: bool,
150    #[serde(default = "default::storage::max_preload_io_retry_times")]
151    pub max_preload_io_retry_times: usize,
152    #[serde(default = "default::storage::compactor_fast_max_compact_delete_ratio")]
153    pub compactor_fast_max_compact_delete_ratio: u32,
154    #[serde(default = "default::storage::compactor_fast_max_compact_task_size")]
155    pub compactor_fast_max_compact_task_size: u64,
156    #[serde(default = "default::storage::compactor_iter_max_io_retry_times")]
157    pub compactor_iter_max_io_retry_times: usize,
158
159    /// Deprecated: The window size of table info statistic history.
160    #[serde(default = "default::storage::table_info_statistic_history_times")]
161    #[deprecated]
162    pub table_info_statistic_history_times: usize,
163
164    #[serde(default, flatten)]
165    #[config_doc(omitted)]
166    pub unrecognized: Unrecognized<Self>,
167
168    /// The spill threshold for mem table.
169    #[serde(default = "default::storage::mem_table_spill_threshold")]
170    pub mem_table_spill_threshold: usize,
171
172    /// The concurrent uploading number of `SSTables` of builder
173    #[serde(default = "default::storage::compactor_concurrent_uploading_sst_count")]
174    pub compactor_concurrent_uploading_sst_count: Option<usize>,
175
176    #[serde(default = "default::storage::compactor_max_overlap_sst_count")]
177    pub compactor_max_overlap_sst_count: usize,
178
179    /// The maximum number of meta files that can be preloaded.
180    /// If the number of meta files exceeds this value, the compactor will try to compute parallelism only through `SstableInfo`, no longer preloading `SstableMeta`.
181    /// This is to prevent the compactor from consuming too much memory, but it may cause the compactor to be less efficient.
182    #[serde(default = "default::storage::compactor_max_preload_meta_file_count")]
183    pub compactor_max_preload_meta_file_count: usize,
184
185    #[serde(default = "default::storage::vector_file_block_size_kb")]
186    pub vector_file_block_size_kb: usize,
187
188    /// Object storage configuration
189    /// 1. General configuration
190    /// 2. Some special configuration of Backend
191    /// 3. Retry and timeout configuration
192    #[serde(default)]
193    pub object_store: ObjectStoreConfig,
194
195    #[serde(default = "default::storage::time_travel_version_cache_capacity")]
196    pub time_travel_version_cache_capacity: u64,
197
198    // iceberg compaction
199    #[serde(default = "default::storage::iceberg_compaction_target_file_size_mb")]
200    pub iceberg_compaction_target_file_size_mb: u32,
201    #[serde(default = "default::storage::iceberg_compaction_enable_validate")]
202    pub iceberg_compaction_enable_validate: bool,
203    #[serde(default = "default::storage::iceberg_compaction_max_record_batch_rows")]
204    pub iceberg_compaction_max_record_batch_rows: usize,
205    #[serde(default = "default::storage::iceberg_compaction_min_size_per_partition_mb")]
206    pub iceberg_compaction_min_size_per_partition_mb: u32,
207    #[serde(default = "default::storage::iceberg_compaction_max_file_count_per_partition")]
208    pub iceberg_compaction_max_file_count_per_partition: u32,
209    #[serde(default = "default::storage::iceberg_compaction_write_parquet_max_row_group_rows")]
210    pub iceberg_compaction_write_parquet_max_row_group_rows: usize,
211
212    /// The ratio of iceberg compaction max parallelism to the number of CPU cores
213    #[serde(default = "default::storage::iceberg_compaction_task_parallelism_ratio")]
214    pub iceberg_compaction_task_parallelism_ratio: f32,
215    /// Whether to enable heuristic output parallelism in iceberg compaction.
216    #[serde(default = "default::storage::iceberg_compaction_enable_heuristic_output_parallelism")]
217    pub iceberg_compaction_enable_heuristic_output_parallelism: bool,
218    /// Maximum number of concurrent file close operations
219    #[serde(default = "default::storage::iceberg_compaction_max_concurrent_closes")]
220    pub iceberg_compaction_max_concurrent_closes: usize,
221    /// Whether to enable dynamic size estimation for iceberg compaction.
222    #[serde(default = "default::storage::iceberg_compaction_enable_dynamic_size_estimation")]
223    pub iceberg_compaction_enable_dynamic_size_estimation: bool,
224    /// The smoothing factor for size estimation in iceberg compaction.(default: 0.3)
225    #[serde(default = "default::storage::iceberg_compaction_size_estimation_smoothing_factor")]
226    pub iceberg_compaction_size_estimation_smoothing_factor: f64,
227}
228
229/// the section `[storage.cache]` in `risingwave.toml`.
230#[derive(Clone, Debug, Serialize, Deserialize, DefaultFromSerde, ConfigDoc)]
231pub struct CacheConfig {
232    /// Configure the capacity of the block cache in MB explicitly.
233    /// The overridden value will only be effective if:
234    /// 1. `meta_cache_capacity_mb` and `shared_buffer_capacity_mb` are also configured explicitly.
235    /// 2. `block_cache_capacity_mb` + `meta_cache_capacity_mb` + `meta_cache_capacity_mb` doesn't exceed 0.3 * non-reserved memory.
236    #[serde(default)]
237    pub block_cache_capacity_mb: Option<usize>,
238
239    /// Configure the number of shards in the block cache explicitly.
240    /// If not set, the shard number will be determined automatically based on cache capacity.
241    #[serde(default)]
242    pub block_cache_shard_num: Option<usize>,
243
244    #[serde(default)]
245    #[config_doc(omitted)]
246    pub block_cache_eviction: CacheEvictionConfig,
247
248    /// Configure the capacity of the block cache in MB explicitly.
249    /// The overridden value will only be effective if:
250    /// 1. `block_cache_capacity_mb` and `shared_buffer_capacity_mb` are also configured explicitly.
251    /// 2. `block_cache_capacity_mb` + `meta_cache_capacity_mb` + `meta_cache_capacity_mb` doesn't exceed 0.3 * non-reserved memory.
252    #[serde(default)]
253    pub meta_cache_capacity_mb: Option<usize>,
254
255    /// Configure the number of shards in the meta cache explicitly.
256    /// If not set, the shard number will be determined automatically based on cache capacity.
257    #[serde(default)]
258    pub meta_cache_shard_num: Option<usize>,
259
260    #[serde(default)]
261    #[config_doc(omitted)]
262    pub meta_cache_eviction: CacheEvictionConfig,
263
264    #[serde(default = "default::storage::vector_block_cache_capacity_mb")]
265    pub vector_block_cache_capacity_mb: usize,
266    #[serde(default = "default::storage::vector_block_cache_shard_num")]
267    pub vector_block_cache_shard_num: usize,
268    #[serde(default)]
269    #[config_doc(omitted)]
270    pub vector_block_cache_eviction_config: CacheEvictionConfig,
271    #[serde(default = "default::storage::vector_meta_cache_capacity_mb")]
272    pub vector_meta_cache_capacity_mb: usize,
273    #[serde(default = "default::storage::vector_meta_cache_shard_num")]
274    pub vector_meta_cache_shard_num: usize,
275    #[serde(default)]
276    #[config_doc(omitted)]
277    pub vector_meta_cache_eviction_config: CacheEvictionConfig,
278}
279
280/// the section `[storage.cache.eviction]` in `risingwave.toml`.
281#[derive(Clone, Debug, Serialize, Deserialize)]
282#[serde(tag = "algorithm")]
283pub enum CacheEvictionConfig {
284    Lru {
285        high_priority_ratio_in_percent: Option<usize>,
286    },
287    Lfu {
288        window_capacity_ratio_in_percent: Option<usize>,
289        protected_capacity_ratio_in_percent: Option<usize>,
290        cmsketch_eps: Option<f64>,
291        cmsketch_confidence: Option<f64>,
292    },
293    S3Fifo {
294        small_queue_capacity_ratio_in_percent: Option<usize>,
295        ghost_queue_capacity_ratio_in_percent: Option<usize>,
296        small_to_main_freq_threshold: Option<u8>,
297    },
298}
299
300impl Default for CacheEvictionConfig {
301    fn default() -> Self {
302        Self::Lru {
303            high_priority_ratio_in_percent: None,
304        }
305    }
306}
307
308#[derive(Clone, Debug, Serialize, Deserialize, DefaultFromSerde, ConfigDoc)]
309pub struct CacheRefillConfig {
310    /// `SSTable` levels to refill.
311    #[serde(default = "default::cache_refill::data_refill_levels")]
312    pub data_refill_levels: Vec<u32>,
313
314    /// Cache refill maximum timeout to apply version delta.
315    #[serde(default = "default::cache_refill::timeout_ms")]
316    pub timeout_ms: u64,
317
318    /// Inflight data cache refill tasks.
319    #[serde(default = "default::cache_refill::concurrency")]
320    pub concurrency: usize,
321
322    /// Block count that a data cache refill request fetches.
323    #[serde(default = "default::cache_refill::unit")]
324    pub unit: usize,
325
326    /// Data cache refill unit admission ratio.
327    ///
328    /// Only unit whose blocks are admitted above the ratio will be refilled.
329    #[serde(default = "default::cache_refill::threshold")]
330    pub threshold: f64,
331
332    /// Recent filter layer count.
333    #[serde(default = "default::cache_refill::recent_filter_layers")]
334    pub recent_filter_layers: usize,
335
336    /// Recent filter layer rotate interval.
337    #[serde(default = "default::cache_refill::recent_filter_rotate_interval_ms")]
338    pub recent_filter_rotate_interval_ms: usize,
339
340    #[serde(default, flatten)]
341    #[config_doc(omitted)]
342    pub unrecognized: Unrecognized<Self>,
343}
344
345/// The subsection `[storage.data_file_cache]` and `[storage.meta_file_cache]` in `risingwave.toml`.
346///
347/// It's put at [`StorageConfig::data_file_cache`] and  [`StorageConfig::meta_file_cache`].
348#[derive(Clone, Debug, Serialize, Deserialize, DefaultFromSerde, ConfigDoc)]
349pub struct FileCacheConfig {
350    #[serde(default = "default::file_cache::dir")]
351    pub dir: String,
352
353    #[serde(default = "default::file_cache::capacity_mb")]
354    pub capacity_mb: usize,
355
356    #[serde(default = "default::file_cache::file_capacity_mb")]
357    pub file_capacity_mb: usize,
358
359    #[serde(default = "default::file_cache::flushers")]
360    pub flushers: usize,
361
362    #[serde(default = "default::file_cache::reclaimers")]
363    pub reclaimers: usize,
364
365    #[serde(default = "default::file_cache::recover_concurrency")]
366    pub recover_concurrency: usize,
367
368    /// Deprecated soon. Please use `throttle` to do I/O throttling instead.
369    #[serde(default = "default::file_cache::insert_rate_limit_mb")]
370    pub insert_rate_limit_mb: usize,
371
372    #[serde(default = "default::file_cache::indexer_shards")]
373    pub indexer_shards: usize,
374
375    #[serde(default = "default::file_cache::compression")]
376    pub compression: Compression,
377
378    #[serde(default = "default::file_cache::flush_buffer_threshold_mb")]
379    pub flush_buffer_threshold_mb: Option<usize>,
380
381    #[serde(default = "default::file_cache::throttle")]
382    pub throttle: Throttle,
383
384    #[serde(default = "default::file_cache::fifo_probation_ratio")]
385    pub fifo_probation_ratio: f64,
386
387    /// Recover mode.
388    ///
389    /// Options:
390    ///
391    /// - "None": Do not recover disk cache.
392    /// - "Quiet": Recover disk cache and skip errors.
393    /// - "Strict": Recover disk cache and panic on errors.
394    ///
395    /// More details, see [`RecoverMode::None`], [`RecoverMode::Quiet`] and [`RecoverMode::Strict`],
396    #[serde(default = "default::file_cache::recover_mode")]
397    pub recover_mode: RecoverMode,
398
399    #[serde(default = "default::file_cache::runtime_config")]
400    pub runtime_config: RuntimeOptions,
401
402    #[serde(default, flatten)]
403    #[config_doc(omitted)]
404    pub unrecognized: Unrecognized<Self>,
405}
406
407/// The subsections `[storage.object_store]`.
408#[derive(Clone, Debug, Serialize, Deserialize, DefaultFromSerde)]
409pub struct ObjectStoreConfig {
410    // alias is for backward compatibility
411    #[serde(
412        default = "default::object_store_config::set_atomic_write_dir",
413        alias = "object_store_set_atomic_write_dir"
414    )]
415    pub set_atomic_write_dir: bool,
416
417    /// Retry and timeout configuration
418    /// Description retry strategy driven by exponential back-off
419    /// Exposes the timeout and retries of each Object store interface. Therefore, the total timeout for each interface is determined based on the interface's timeout/retry configuration and the exponential back-off policy.
420    #[serde(default)]
421    pub retry: ObjectStoreRetryConfig,
422
423    /// Some special configuration of S3 Backend
424    #[serde(default)]
425    pub s3: S3ObjectStoreConfig,
426
427    // TODO: the following field will be deprecated after opendal is stabilized
428    #[serde(default = "default::object_store_config::opendal_upload_concurrency")]
429    pub opendal_upload_concurrency: usize,
430
431    // TODO: the following field will be deprecated after opendal is stabilized
432    #[serde(default)]
433    pub opendal_writer_abort_on_err: bool,
434
435    #[serde(default = "default::object_store_config::upload_part_size")]
436    pub upload_part_size: usize,
437}
438
439impl ObjectStoreConfig {
440    pub fn set_atomic_write_dir(&mut self) {
441        self.set_atomic_write_dir = true;
442    }
443}
444
445/// The subsections `[storage.object_store.s3]`.
446#[derive(Clone, Debug, Serialize, Deserialize, DefaultFromSerde)]
447pub struct S3ObjectStoreConfig {
448    // alias is for backward compatibility
449    #[serde(
450        default = "default::object_store_config::s3::keepalive_ms",
451        alias = "object_store_keepalive_ms"
452    )]
453    pub keepalive_ms: Option<u64>,
454    #[serde(
455        default = "default::object_store_config::s3::recv_buffer_size",
456        alias = "object_store_recv_buffer_size"
457    )]
458    pub recv_buffer_size: Option<usize>,
459    #[serde(
460        default = "default::object_store_config::s3::send_buffer_size",
461        alias = "object_store_send_buffer_size"
462    )]
463    pub send_buffer_size: Option<usize>,
464    #[serde(
465        default = "default::object_store_config::s3::nodelay",
466        alias = "object_store_nodelay"
467    )]
468    pub nodelay: Option<bool>,
469    /// For backwards compatibility, users should use `S3ObjectStoreDeveloperConfig` instead.
470    #[serde(default = "default::object_store_config::s3::developer::retry_unknown_service_error")]
471    pub retry_unknown_service_error: bool,
472    #[serde(default = "default::object_store_config::s3::identity_resolution_timeout_s")]
473    pub identity_resolution_timeout_s: u64,
474    #[serde(default)]
475    pub developer: S3ObjectStoreDeveloperConfig,
476}
477
478/// The subsections `[storage.object_store.s3.developer]`.
479#[derive(Clone, Debug, Serialize, Deserialize, DefaultFromSerde)]
480pub struct S3ObjectStoreDeveloperConfig {
481    /// Whether to retry s3 sdk error from which no error metadata is provided.
482    #[serde(
483        default = "default::object_store_config::s3::developer::retry_unknown_service_error",
484        alias = "object_store_retry_unknown_service_error"
485    )]
486    pub retry_unknown_service_error: bool,
487    /// An array of error codes that should be retried.
488    /// e.g. `["SlowDown", "TooManyRequests"]`
489    #[serde(
490        default = "default::object_store_config::s3::developer::retryable_service_error_codes",
491        alias = "object_store_retryable_service_error_codes"
492    )]
493    pub retryable_service_error_codes: Vec<String>,
494
495    // TODO: deprecate this config when we are completely deprecate aws sdk.
496    #[serde(default = "default::object_store_config::s3::developer::use_opendal")]
497    pub use_opendal: bool,
498}
499
500#[derive(Clone, Debug, Serialize, Deserialize, DefaultFromSerde)]
501pub struct ObjectStoreRetryConfig {
502    // A retry strategy driven by exponential back-off.
503    // The retry strategy is used for all object store operations.
504    /// Given a base duration for retry strategy in milliseconds.
505    #[serde(default = "default::object_store_config::object_store_req_backoff_interval_ms")]
506    pub req_backoff_interval_ms: u64,
507
508    /// The max delay interval for the retry strategy. No retry delay will be longer than this `Duration`.
509    #[serde(default = "default::object_store_config::object_store_req_backoff_max_delay_ms")]
510    pub req_backoff_max_delay_ms: u64,
511
512    /// A multiplicative factor that will be applied to the exponential back-off retry delay.
513    #[serde(default = "default::object_store_config::object_store_req_backoff_factor")]
514    pub req_backoff_factor: u64,
515
516    /// Maximum timeout for `upload` operation
517    #[serde(default = "default::object_store_config::object_store_upload_attempt_timeout_ms")]
518    pub upload_attempt_timeout_ms: u64,
519
520    /// Total counts of `upload` operation retries
521    #[serde(default = "default::object_store_config::object_store_upload_retry_attempts")]
522    pub upload_retry_attempts: usize,
523
524    /// Maximum timeout for `streaming_upload_init` and `streaming_upload`
525    #[serde(
526        default = "default::object_store_config::object_store_streaming_upload_attempt_timeout_ms"
527    )]
528    pub streaming_upload_attempt_timeout_ms: u64,
529
530    /// Total counts of `streaming_upload` operation retries
531    #[serde(
532        default = "default::object_store_config::object_store_streaming_upload_retry_attempts"
533    )]
534    pub streaming_upload_retry_attempts: usize,
535
536    /// Maximum timeout for `read` operation
537    #[serde(default = "default::object_store_config::object_store_read_attempt_timeout_ms")]
538    pub read_attempt_timeout_ms: u64,
539
540    /// Total counts of `read` operation retries
541    #[serde(default = "default::object_store_config::object_store_read_retry_attempts")]
542    pub read_retry_attempts: usize,
543
544    /// Maximum timeout for `streaming_read_init` and `streaming_read` operation
545    #[serde(
546        default = "default::object_store_config::object_store_streaming_read_attempt_timeout_ms"
547    )]
548    pub streaming_read_attempt_timeout_ms: u64,
549
550    /// Total counts of `streaming_read operation` retries
551    #[serde(default = "default::object_store_config::object_store_streaming_read_retry_attempts")]
552    pub streaming_read_retry_attempts: usize,
553
554    /// Maximum timeout for `metadata` operation
555    #[serde(default = "default::object_store_config::object_store_metadata_attempt_timeout_ms")]
556    pub metadata_attempt_timeout_ms: u64,
557
558    /// Total counts of `metadata` operation retries
559    #[serde(default = "default::object_store_config::object_store_metadata_retry_attempts")]
560    pub metadata_retry_attempts: usize,
561
562    /// Maximum timeout for `delete` operation
563    #[serde(default = "default::object_store_config::object_store_delete_attempt_timeout_ms")]
564    pub delete_attempt_timeout_ms: u64,
565
566    /// Total counts of `delete` operation retries
567    #[serde(default = "default::object_store_config::object_store_delete_retry_attempts")]
568    pub delete_retry_attempts: usize,
569
570    /// Maximum timeout for `delete_object` operation
571    #[serde(
572        default = "default::object_store_config::object_store_delete_objects_attempt_timeout_ms"
573    )]
574    pub delete_objects_attempt_timeout_ms: u64,
575
576    /// Total counts of `delete_object` operation retries
577    #[serde(default = "default::object_store_config::object_store_delete_objects_retry_attempts")]
578    pub delete_objects_retry_attempts: usize,
579
580    /// Maximum timeout for `list` operation
581    #[serde(default = "default::object_store_config::object_store_list_attempt_timeout_ms")]
582    pub list_attempt_timeout_ms: u64,
583
584    /// Total counts of `list` operation retries
585    #[serde(default = "default::object_store_config::object_store_list_retry_attempts")]
586    pub list_retry_attempts: usize,
587}
588
589#[derive(Debug, Clone)]
590pub enum EvictionConfig {
591    Lru(LruConfig),
592    Lfu(LfuConfig),
593    S3Fifo(S3FifoConfig),
594}
595
596impl EvictionConfig {
597    pub fn for_test() -> Self {
598        Self::Lru(LruConfig {
599            high_priority_pool_ratio: 0.0,
600        })
601    }
602}
603
604impl From<EvictionConfig> for foyer::EvictionConfig {
605    fn from(value: EvictionConfig) -> Self {
606        match value {
607            EvictionConfig::Lru(lru) => foyer::EvictionConfig::Lru(lru),
608            EvictionConfig::Lfu(lfu) => foyer::EvictionConfig::Lfu(lfu),
609            EvictionConfig::S3Fifo(s3fifo) => foyer::EvictionConfig::S3Fifo(s3fifo),
610        }
611    }
612}
613
614pub struct StorageMemoryConfig {
615    pub block_cache_capacity_mb: usize,
616    pub block_cache_shard_num: usize,
617    pub meta_cache_capacity_mb: usize,
618    pub meta_cache_shard_num: usize,
619    pub vector_block_cache_capacity_mb: usize,
620    pub vector_block_cache_shard_num: usize,
621    pub vector_meta_cache_capacity_mb: usize,
622    pub vector_meta_cache_shard_num: usize,
623    pub shared_buffer_capacity_mb: usize,
624    pub compactor_memory_limit_mb: usize,
625    pub prefetch_buffer_capacity_mb: usize,
626    pub block_cache_eviction_config: EvictionConfig,
627    pub meta_cache_eviction_config: EvictionConfig,
628    pub vector_block_cache_eviction_config: EvictionConfig,
629    pub vector_meta_cache_eviction_config: EvictionConfig,
630    pub block_file_cache_flush_buffer_threshold_mb: usize,
631    pub meta_file_cache_flush_buffer_threshold_mb: usize,
632}
633
634pub fn extract_storage_memory_config(s: &RwConfig) -> StorageMemoryConfig {
635    let block_cache_capacity_mb = s.storage.cache.block_cache_capacity_mb.unwrap_or(
636        // adapt to old version
637        s.storage
638            .block_cache_capacity_mb
639            .unwrap_or(default::storage::block_cache_capacity_mb()),
640    );
641    let meta_cache_capacity_mb = s.storage.cache.meta_cache_capacity_mb.unwrap_or(
642        // adapt to old version
643        s.storage
644            .block_cache_capacity_mb
645            .unwrap_or(default::storage::meta_cache_capacity_mb()),
646    );
647    let shared_buffer_capacity_mb = s
648        .storage
649        .shared_buffer_capacity_mb
650        .unwrap_or(default::storage::shared_buffer_capacity_mb());
651    let meta_cache_shard_num = s.storage.cache.meta_cache_shard_num.unwrap_or_else(|| {
652        let mut shard_bits = MAX_META_CACHE_SHARD_BITS;
653        while (meta_cache_capacity_mb >> shard_bits) < MIN_BUFFER_SIZE_PER_SHARD && shard_bits > 0 {
654            shard_bits -= 1;
655        }
656        shard_bits
657    });
658    let block_cache_shard_num = s.storage.cache.block_cache_shard_num.unwrap_or_else(|| {
659        let mut shard_bits = MAX_BLOCK_CACHE_SHARD_BITS;
660        while (block_cache_capacity_mb >> shard_bits) < MIN_BUFFER_SIZE_PER_SHARD && shard_bits > 0
661        {
662            shard_bits -= 1;
663        }
664        shard_bits
665    });
666    let compactor_memory_limit_mb = s
667        .storage
668        .compactor_memory_limit_mb
669        .unwrap_or(default::storage::compactor_memory_limit_mb());
670
671    let get_eviction_config = |c: &CacheEvictionConfig| {
672        match c {
673            CacheEvictionConfig::Lru {
674                high_priority_ratio_in_percent,
675            } => EvictionConfig::Lru(LruConfig {
676                high_priority_pool_ratio: high_priority_ratio_in_percent.unwrap_or(
677                    // adapt to old version
678                    s.storage
679                        .high_priority_ratio_in_percent
680                        .unwrap_or(default::storage::high_priority_ratio_in_percent()),
681                ) as f64
682                    / 100.0,
683            }),
684            CacheEvictionConfig::Lfu {
685                window_capacity_ratio_in_percent,
686                protected_capacity_ratio_in_percent,
687                cmsketch_eps,
688                cmsketch_confidence,
689            } => EvictionConfig::Lfu(LfuConfig {
690                window_capacity_ratio: window_capacity_ratio_in_percent
691                    .unwrap_or(default::storage::window_capacity_ratio_in_percent())
692                    as f64
693                    / 100.0,
694                protected_capacity_ratio: protected_capacity_ratio_in_percent
695                    .unwrap_or(default::storage::protected_capacity_ratio_in_percent())
696                    as f64
697                    / 100.0,
698                cmsketch_eps: cmsketch_eps.unwrap_or(default::storage::cmsketch_eps()),
699                cmsketch_confidence: cmsketch_confidence
700                    .unwrap_or(default::storage::cmsketch_confidence()),
701            }),
702            CacheEvictionConfig::S3Fifo {
703                small_queue_capacity_ratio_in_percent,
704                ghost_queue_capacity_ratio_in_percent,
705                small_to_main_freq_threshold,
706            } => EvictionConfig::S3Fifo(S3FifoConfig {
707                small_queue_capacity_ratio: small_queue_capacity_ratio_in_percent
708                    .unwrap_or(default::storage::small_queue_capacity_ratio_in_percent())
709                    as f64
710                    / 100.0,
711                ghost_queue_capacity_ratio: ghost_queue_capacity_ratio_in_percent
712                    .unwrap_or(default::storage::ghost_queue_capacity_ratio_in_percent())
713                    as f64
714                    / 100.0,
715                small_to_main_freq_threshold: small_to_main_freq_threshold
716                    .unwrap_or(default::storage::small_to_main_freq_threshold()),
717            }),
718        }
719    };
720
721    let block_cache_eviction_config = get_eviction_config(&s.storage.cache.block_cache_eviction);
722    let meta_cache_eviction_config = get_eviction_config(&s.storage.cache.meta_cache_eviction);
723    let vector_block_cache_eviction_config =
724        get_eviction_config(&s.storage.cache.vector_block_cache_eviction_config);
725    let vector_meta_cache_eviction_config =
726        get_eviction_config(&s.storage.cache.vector_meta_cache_eviction_config);
727
728    let prefetch_buffer_capacity_mb =
729        s.storage
730            .shared_buffer_capacity_mb
731            .unwrap_or(match &block_cache_eviction_config {
732                EvictionConfig::Lru(lru) => {
733                    ((1.0 - lru.high_priority_pool_ratio) * block_cache_capacity_mb as f64) as usize
734                }
735                EvictionConfig::Lfu(lfu) => {
736                    ((1.0 - lfu.protected_capacity_ratio) * block_cache_capacity_mb as f64) as usize
737                }
738                EvictionConfig::S3Fifo(s3fifo) => {
739                    (s3fifo.small_queue_capacity_ratio * block_cache_capacity_mb as f64) as usize
740                }
741            });
742
743    let block_file_cache_flush_buffer_threshold_mb = s
744        .storage
745        .data_file_cache
746        .flush_buffer_threshold_mb
747        .unwrap_or(default::storage::block_file_cache_flush_buffer_threshold_mb());
748    let meta_file_cache_flush_buffer_threshold_mb = s
749        .storage
750        .meta_file_cache
751        .flush_buffer_threshold_mb
752        .unwrap_or(default::storage::block_file_cache_flush_buffer_threshold_mb());
753
754    StorageMemoryConfig {
755        block_cache_capacity_mb,
756        block_cache_shard_num,
757        meta_cache_capacity_mb,
758        meta_cache_shard_num,
759        vector_block_cache_capacity_mb: s.storage.cache.vector_block_cache_capacity_mb,
760        vector_block_cache_shard_num: s.storage.cache.vector_block_cache_shard_num,
761        vector_meta_cache_capacity_mb: s.storage.cache.vector_meta_cache_capacity_mb,
762        vector_meta_cache_shard_num: s.storage.cache.vector_meta_cache_shard_num,
763        shared_buffer_capacity_mb,
764        compactor_memory_limit_mb,
765        prefetch_buffer_capacity_mb,
766        block_cache_eviction_config,
767        meta_cache_eviction_config,
768        vector_block_cache_eviction_config,
769        vector_meta_cache_eviction_config,
770        block_file_cache_flush_buffer_threshold_mb,
771        meta_file_cache_flush_buffer_threshold_mb,
772    }
773}
774
775pub mod default {
776
777    pub mod storage {
778        pub fn share_buffers_sync_parallelism() -> u32 {
779            1
780        }
781
782        pub fn share_buffer_compaction_worker_threads_number() -> u32 {
783            4
784        }
785
786        pub fn shared_buffer_capacity_mb() -> usize {
787            1024
788        }
789
790        pub fn shared_buffer_flush_ratio() -> f32 {
791            0.8
792        }
793
794        pub fn shared_buffer_min_batch_flush_size_mb() -> usize {
795            800
796        }
797
798        pub fn imm_merge_threshold() -> usize {
799            0 // disable
800        }
801
802        pub fn write_conflict_detection_enabled() -> bool {
803            cfg!(debug_assertions)
804        }
805
806        pub fn max_cached_recent_versions_number() -> usize {
807            60
808        }
809
810        pub fn block_cache_capacity_mb() -> usize {
811            512
812        }
813
814        pub fn high_priority_ratio_in_percent() -> usize {
815            70
816        }
817
818        pub fn window_capacity_ratio_in_percent() -> usize {
819            10
820        }
821
822        pub fn protected_capacity_ratio_in_percent() -> usize {
823            80
824        }
825
826        pub fn cmsketch_eps() -> f64 {
827            0.002
828        }
829
830        pub fn cmsketch_confidence() -> f64 {
831            0.95
832        }
833
834        pub fn small_queue_capacity_ratio_in_percent() -> usize {
835            10
836        }
837
838        pub fn ghost_queue_capacity_ratio_in_percent() -> usize {
839            1000
840        }
841
842        pub fn small_to_main_freq_threshold() -> u8 {
843            1
844        }
845
846        pub fn meta_cache_capacity_mb() -> usize {
847            128
848        }
849
850        pub fn disable_remote_compactor() -> bool {
851            false
852        }
853
854        pub fn share_buffer_upload_concurrency() -> usize {
855            8
856        }
857
858        pub fn compactor_memory_limit_mb() -> usize {
859            512
860        }
861
862        pub fn compactor_max_task_multiplier() -> f32 {
863            match std::env::var("RW_COMPACTOR_MODE")
864                .unwrap_or_default()
865                .as_str()
866            {
867                mode if mode.contains("iceberg") => 12.0000,
868                _ => 3.0000,
869            }
870        }
871
872        pub fn compactor_memory_available_proportion() -> f64 {
873            0.8
874        }
875
876        pub fn sstable_id_remote_fetch_number() -> u32 {
877            10
878        }
879
880        pub fn min_sstable_size_mb() -> u32 {
881            32
882        }
883
884        pub fn min_sst_size_for_streaming_upload() -> u64 {
885            // 32MB
886            32 * 1024 * 1024
887        }
888
889        pub fn max_concurrent_compaction_task_number() -> u64 {
890            16
891        }
892
893        pub fn max_preload_wait_time_mill() -> u64 {
894            0
895        }
896
897        pub fn max_version_pinning_duration_sec() -> u64 {
898            3 * 3600
899        }
900
901        pub fn compactor_max_sst_key_count() -> u64 {
902            2 * 1024 * 1024 // 200w
903        }
904
905        pub fn compact_iter_recreate_timeout_ms() -> u64 {
906            10 * 60 * 1000
907        }
908
909        pub fn compactor_iter_max_io_retry_times() -> usize {
910            8
911        }
912
913        pub fn compactor_max_sst_size() -> u64 {
914            512 * 1024 * 1024 // 512m
915        }
916
917        pub fn enable_fast_compaction() -> bool {
918            true
919        }
920
921        pub fn check_compaction_result() -> bool {
922            false
923        }
924
925        pub fn max_preload_io_retry_times() -> usize {
926            3
927        }
928
929        pub fn mem_table_spill_threshold() -> usize {
930            4 << 20
931        }
932
933        pub fn compactor_fast_max_compact_delete_ratio() -> u32 {
934            40
935        }
936
937        pub fn compactor_fast_max_compact_task_size() -> u64 {
938            2 * 1024 * 1024 * 1024 // 2g
939        }
940
941        pub fn max_prefetch_block_number() -> usize {
942            16
943        }
944
945        pub fn compactor_concurrent_uploading_sst_count() -> Option<usize> {
946            None
947        }
948
949        pub fn compactor_max_overlap_sst_count() -> usize {
950            64
951        }
952
953        pub fn compactor_max_preload_meta_file_count() -> usize {
954            32
955        }
956
957        pub fn vector_file_block_size_kb() -> usize {
958            1024
959        }
960
961        pub fn vector_block_cache_capacity_mb() -> usize {
962            16
963        }
964
965        pub fn vector_block_cache_shard_num() -> usize {
966            16
967        }
968
969        pub fn vector_meta_cache_capacity_mb() -> usize {
970            16
971        }
972
973        pub fn vector_meta_cache_shard_num() -> usize {
974            16
975        }
976
977        // deprecated
978        pub fn table_info_statistic_history_times() -> usize {
979            240
980        }
981
982        pub fn block_file_cache_flush_buffer_threshold_mb() -> usize {
983            256
984        }
985
986        pub fn meta_file_cache_flush_buffer_threshold_mb() -> usize {
987            64
988        }
989
990        pub fn time_travel_version_cache_capacity() -> u64 {
991            10
992        }
993
994        pub fn iceberg_compaction_target_file_size_mb() -> u32 {
995            1024
996        }
997
998        pub fn iceberg_compaction_enable_validate() -> bool {
999            false
1000        }
1001
1002        pub fn iceberg_compaction_max_record_batch_rows() -> usize {
1003            1024
1004        }
1005
1006        pub fn iceberg_compaction_write_parquet_max_row_group_rows() -> usize {
1007            1024 * 100 // 100k
1008        }
1009
1010        pub fn iceberg_compaction_min_size_per_partition_mb() -> u32 {
1011            1024
1012        }
1013
1014        pub fn iceberg_compaction_max_file_count_per_partition() -> u32 {
1015            32
1016        }
1017
1018        pub fn iceberg_compaction_task_parallelism_ratio() -> f32 {
1019            4.0
1020        }
1021
1022        pub fn iceberg_compaction_enable_heuristic_output_parallelism() -> bool {
1023            false
1024        }
1025
1026        pub fn iceberg_compaction_max_concurrent_closes() -> usize {
1027            8
1028        }
1029
1030        pub fn iceberg_compaction_enable_dynamic_size_estimation() -> bool {
1031            true
1032        }
1033
1034        pub fn iceberg_compaction_size_estimation_smoothing_factor() -> f64 {
1035            0.3
1036        }
1037    }
1038
1039    pub mod file_cache {
1040        use std::num::NonZeroUsize;
1041
1042        use foyer::{Compression, RecoverMode, RuntimeOptions, Throttle, TokioRuntimeOptions};
1043
1044        pub fn dir() -> String {
1045            "".to_owned()
1046        }
1047
1048        pub fn capacity_mb() -> usize {
1049            1024
1050        }
1051
1052        pub fn file_capacity_mb() -> usize {
1053            64
1054        }
1055
1056        pub fn flushers() -> usize {
1057            4
1058        }
1059
1060        pub fn reclaimers() -> usize {
1061            4
1062        }
1063
1064        pub fn recover_concurrency() -> usize {
1065            8
1066        }
1067
1068        pub fn insert_rate_limit_mb() -> usize {
1069            0
1070        }
1071
1072        pub fn indexer_shards() -> usize {
1073            64
1074        }
1075
1076        pub fn compression() -> Compression {
1077            Compression::None
1078        }
1079
1080        pub fn flush_buffer_threshold_mb() -> Option<usize> {
1081            None
1082        }
1083
1084        pub fn fifo_probation_ratio() -> f64 {
1085            0.1
1086        }
1087
1088        pub fn recover_mode() -> RecoverMode {
1089            RecoverMode::Quiet
1090        }
1091
1092        pub fn runtime_config() -> RuntimeOptions {
1093            RuntimeOptions::Unified(TokioRuntimeOptions::default())
1094        }
1095
1096        pub fn throttle() -> Throttle {
1097            Throttle::new()
1098                .with_iops_counter(foyer::IopsCounter::PerIoSize(
1099                    NonZeroUsize::new(128 * 1024).unwrap(),
1100                ))
1101                .with_read_iops(100000)
1102                .with_write_iops(100000)
1103                .with_write_throughput(1024 * 1024 * 1024)
1104                .with_read_throughput(1024 * 1024 * 1024)
1105        }
1106    }
1107
1108    pub mod cache_refill {
1109        pub fn data_refill_levels() -> Vec<u32> {
1110            vec![]
1111        }
1112
1113        pub fn timeout_ms() -> u64 {
1114            6000
1115        }
1116
1117        pub fn concurrency() -> usize {
1118            10
1119        }
1120
1121        pub fn unit() -> usize {
1122            64
1123        }
1124
1125        pub fn threshold() -> f64 {
1126            0.5
1127        }
1128
1129        pub fn recent_filter_layers() -> usize {
1130            6
1131        }
1132
1133        pub fn recent_filter_rotate_interval_ms() -> usize {
1134            10000
1135        }
1136    }
1137
1138    pub mod object_store_config {
1139        const DEFAULT_REQ_BACKOFF_INTERVAL_MS: u64 = 1000; // 1s
1140        const DEFAULT_REQ_BACKOFF_MAX_DELAY_MS: u64 = 10 * 1000; // 10s
1141        const DEFAULT_REQ_MAX_RETRY_ATTEMPTS: usize = 3;
1142
1143        pub fn set_atomic_write_dir() -> bool {
1144            false
1145        }
1146
1147        pub fn object_store_req_backoff_interval_ms() -> u64 {
1148            DEFAULT_REQ_BACKOFF_INTERVAL_MS
1149        }
1150
1151        pub fn object_store_req_backoff_max_delay_ms() -> u64 {
1152            DEFAULT_REQ_BACKOFF_MAX_DELAY_MS // 10s
1153        }
1154
1155        pub fn object_store_req_backoff_factor() -> u64 {
1156            2
1157        }
1158
1159        pub fn object_store_upload_attempt_timeout_ms() -> u64 {
1160            8 * 1000 // 8s
1161        }
1162
1163        pub fn object_store_upload_retry_attempts() -> usize {
1164            DEFAULT_REQ_MAX_RETRY_ATTEMPTS
1165        }
1166
1167        // init + upload_part + finish
1168        pub fn object_store_streaming_upload_attempt_timeout_ms() -> u64 {
1169            5 * 1000 // 5s
1170        }
1171
1172        pub fn object_store_streaming_upload_retry_attempts() -> usize {
1173            DEFAULT_REQ_MAX_RETRY_ATTEMPTS
1174        }
1175
1176        // tips: depend on block_size
1177        pub fn object_store_read_attempt_timeout_ms() -> u64 {
1178            8 * 1000 // 8s
1179        }
1180
1181        pub fn object_store_read_retry_attempts() -> usize {
1182            DEFAULT_REQ_MAX_RETRY_ATTEMPTS
1183        }
1184
1185        pub fn object_store_streaming_read_attempt_timeout_ms() -> u64 {
1186            3 * 1000 // 3s
1187        }
1188
1189        pub fn object_store_streaming_read_retry_attempts() -> usize {
1190            DEFAULT_REQ_MAX_RETRY_ATTEMPTS
1191        }
1192
1193        pub fn object_store_metadata_attempt_timeout_ms() -> u64 {
1194            60 * 1000 // 1min
1195        }
1196
1197        pub fn object_store_metadata_retry_attempts() -> usize {
1198            DEFAULT_REQ_MAX_RETRY_ATTEMPTS
1199        }
1200
1201        pub fn object_store_delete_attempt_timeout_ms() -> u64 {
1202            5 * 1000
1203        }
1204
1205        pub fn object_store_delete_retry_attempts() -> usize {
1206            DEFAULT_REQ_MAX_RETRY_ATTEMPTS
1207        }
1208
1209        // tips: depend on batch size
1210        pub fn object_store_delete_objects_attempt_timeout_ms() -> u64 {
1211            5 * 1000
1212        }
1213
1214        pub fn object_store_delete_objects_retry_attempts() -> usize {
1215            DEFAULT_REQ_MAX_RETRY_ATTEMPTS
1216        }
1217
1218        pub fn object_store_list_attempt_timeout_ms() -> u64 {
1219            10 * 60 * 1000
1220        }
1221
1222        pub fn object_store_list_retry_attempts() -> usize {
1223            DEFAULT_REQ_MAX_RETRY_ATTEMPTS
1224        }
1225
1226        pub fn opendal_upload_concurrency() -> usize {
1227            256
1228        }
1229
1230        pub fn upload_part_size() -> usize {
1231            // 16m
1232            16 * 1024 * 1024
1233        }
1234
1235        pub mod s3 {
1236            const DEFAULT_IDENTITY_RESOLUTION_TIMEOUT_S: u64 = 5;
1237
1238            const DEFAULT_KEEPALIVE_MS: u64 = 600 * 1000; // 10min
1239
1240            pub fn keepalive_ms() -> Option<u64> {
1241                Some(DEFAULT_KEEPALIVE_MS) // 10min
1242            }
1243
1244            pub fn recv_buffer_size() -> Option<usize> {
1245                Some(1 << 21) // 2m
1246            }
1247
1248            pub fn send_buffer_size() -> Option<usize> {
1249                None
1250            }
1251
1252            pub fn nodelay() -> Option<bool> {
1253                Some(true)
1254            }
1255
1256            pub fn identity_resolution_timeout_s() -> u64 {
1257                DEFAULT_IDENTITY_RESOLUTION_TIMEOUT_S
1258            }
1259
1260            pub mod developer {
1261                pub fn retry_unknown_service_error() -> bool {
1262                    false
1263                }
1264
1265                pub fn retryable_service_error_codes() -> Vec<String> {
1266                    vec!["SlowDown".into(), "TooManyRequests".into()]
1267                }
1268
1269                pub fn use_opendal() -> bool {
1270                    true
1271                }
1272            }
1273        }
1274    }
1275}