Skip to main content

datanode/
datanode.rs

1// Copyright 2023 Greptime Team
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
15//! Datanode implementation.
16
17use std::collections::HashMap;
18use std::path::Path;
19use std::sync::Arc;
20use std::time::{Duration, Instant};
21
22use catalog::information_schema::NoopInformationExtension;
23use catalog::kvbackend::KvBackendCatalogManagerBuilder;
24use client::client_manager::NodeClients;
25use common_base::Plugins;
26use common_catalog::consts::{FILE_ENGINE, METRIC_ENGINE, MITO_ENGINE};
27use common_datasource::object_store::LocalFileAccess;
28use common_error::ext::BoxedError;
29use common_greptimedb_telemetry::GreptimeDBTelemetryTask;
30use common_grpc::channel_manager::ChannelConfig;
31use common_meta::cache::{LayeredCacheRegistry, SchemaCacheRef, TableSchemaCacheRef};
32use common_meta::cache_invalidator::CacheInvalidatorRef;
33use common_meta::datanode::TopicStatsReporter;
34use common_meta::key::runtime_switch::RuntimeSwitchManager;
35use common_meta::key::{SchemaMetadataManager, SchemaMetadataManagerRef};
36use common_meta::kv_backend::KvBackendRef;
37use common_meta::node_manager::NodeManagerRef;
38pub use common_procedure::options::ProcedureConfig;
39use common_query::prelude::set_default_prefix;
40use common_stat::ResourceStatImpl;
41use common_telemetry::{error, info, warn};
42use common_wal::config::DatanodeWalConfig;
43use common_wal::config::kafka::DatanodeKafkaConfig;
44use common_wal::config::object_store::{ObjectStoreWalConfig, STANDALONE_GENERATION};
45use common_wal::config::raft_engine::RaftEngineConfig;
46use file_engine::engine::FileRegionEngine;
47use futures::future::join_all;
48use log_store::ObjectStoreLogStore;
49use log_store::kafka::log_store::KafkaLogStore;
50use log_store::noop::log_store::NoopLogStore;
51use log_store::raft_engine::log_store::RaftEngineLogStore;
52use meta_client::MetaClientRef;
53use metric_engine::engine::MetricEngine;
54use mito2::config::MitoConfig;
55use mito2::engine::{MitoEngine, MitoEngineBuilder};
56use mito2::region::opener::PartitionExprFetcherRef;
57use mito2::sst::file_ref::{FileReferenceManager, FileReferenceManagerRef};
58use object_store::ObjectStore;
59use object_store::manager::{ObjectStoreManager, ObjectStoreManagerRef};
60use object_store::util::normalize_dir;
61use query::QueryEngineFactory;
62use query::dummy_catalog::TableProviderFactoryRef;
63use query::region_query::RegionQueryHandlerFactoryRef;
64use servers::server::ServerHandlers;
65use snafu::{OptionExt, ResultExt, ensure};
66use store_api::logstore::LogStore;
67use store_api::path_utils::WAL_DIR;
68use store_api::region_engine::{
69    RegionEngineRef, RegionRole, SetRegionRoleStateResponse, SettableRegionRoleState,
70};
71use tokio::fs;
72use tokio::sync::Notify;
73
74use crate::config::{DatanodeOptions, RegionEngineConfig, StorageConfig};
75use crate::error::{
76    self, BuildDatanodeSnafu, BuildMetricEngineSnafu, BuildMitoEngineSnafu, CreateDirSnafu,
77    DataFusionSnafu, DuplicateRegionEngineConfigSnafu, GetMetadataSnafu,
78    InvalidObjectStoreWalConfigSnafu, MissingCacheSnafu, MissingNodeIdSnafu,
79    ObjectStoreWalNotStandaloneSnafu, OpenLogStoreSnafu, Result, ShutdownInstanceSnafu,
80    ShutdownServerSnafu, StartServerSnafu,
81};
82use crate::event_listener::{
83    NoopRegionServerEventListener, RegionServerEventListenerRef, RegionServerEventReceiver,
84    new_region_server_event_channel,
85};
86use crate::greptimedb_telemetry::get_greptimedb_telemetry_task;
87use crate::heartbeat::HeartbeatTask;
88use crate::partition_expr_fetcher::MetaPartitionExprFetcher;
89use crate::region_query::DatanodeRegionQueryHandler;
90use crate::region_server::{DummyTableProviderFactory, RegionServer};
91use crate::store;
92use crate::utils::{RegionOpenRequests, build_region_open_requests};
93
94/// Datanode service.
95pub struct Datanode {
96    services: ServerHandlers,
97    heartbeat_task: Option<HeartbeatTask>,
98    region_event_receiver: Option<RegionServerEventReceiver>,
99    region_server: RegionServer,
100    greptimedb_telemetry_task: Arc<GreptimeDBTelemetryTask>,
101    leases_notifier: Option<Arc<Notify>>,
102    plugins: Plugins,
103    object_store_log_store: Option<Arc<ObjectStoreLogStore>>,
104}
105
106impl Datanode {
107    pub async fn start(&mut self) -> Result<()> {
108        info!("Starting datanode instance...");
109
110        self.start_heartbeat().await?;
111        self.wait_coordinated().await;
112
113        self.start_telemetry();
114
115        self.services.start_all().await.context(StartServerSnafu)
116    }
117
118    pub fn server_handlers(&self) -> &ServerHandlers {
119        &self.services
120    }
121
122    pub fn start_telemetry(&self) {
123        if let Err(e) = self.greptimedb_telemetry_task.start() {
124            warn!(e; "Failed to start telemetry task!");
125        }
126    }
127
128    pub async fn start_heartbeat(&mut self) -> Result<()> {
129        if let Some(task) = &self.heartbeat_task {
130            // Safety: The event_receiver must exist.
131            let receiver = self.region_event_receiver.take().unwrap();
132
133            task.start(receiver, self.leases_notifier.clone()).await?;
134        }
135        Ok(())
136    }
137
138    /// If `leases_notifier` exists, it waits until leases have been obtained in all regions.
139    pub async fn wait_coordinated(&mut self) {
140        if let Some(notifier) = self.leases_notifier.take() {
141            notifier.notified().await;
142        }
143    }
144
145    pub fn setup_services(&mut self, services: ServerHandlers) {
146        self.services = services;
147    }
148
149    /// Shuts down every component in order. A failed step does not skip the
150    /// later ones; the first error is returned and the rest are logged.
151    pub async fn shutdown(&mut self) -> Result<()> {
152        let mut first_error = None;
153        record_shutdown_error(
154            &mut first_error,
155            self.services
156                .shutdown_all()
157                .await
158                .context(ShutdownServerSnafu),
159        );
160
161        let _ = self.greptimedb_telemetry_task.stop().await;
162        if let Some(heartbeat_task) = &self.heartbeat_task {
163            record_shutdown_error(
164                &mut first_error,
165                heartbeat_task
166                    .close()
167                    .map_err(BoxedError::new)
168                    .context(ShutdownInstanceSnafu),
169            );
170        }
171        // The object store WAL stops before the regions close: its stop
172        // uploads the backlog or drops it, and answers every wait for its
173        // entries, so a flush that waits for the WAL, queued or run by the
174        // close of a region, cannot hold up the shutdown.
175        if let Some(log_store) = &self.object_store_log_store {
176            record_shutdown_error(
177                &mut first_error,
178                log_store
179                    .stop()
180                    .await
181                    .map_err(BoxedError::new)
182                    .context(ShutdownInstanceSnafu),
183            );
184        }
185        record_shutdown_error(&mut first_error, self.region_server.stop().await);
186        first_error.map_or(Ok(()), Err)
187    }
188
189    pub fn region_server(&self) -> RegionServer {
190        self.region_server.clone()
191    }
192
193    /// Returns the object store log store when the datanode runs on the object
194    /// store WAL.
195    #[cfg(any(test, feature = "testing"))]
196    pub fn object_store_log_store(&self) -> Option<Arc<ObjectStoreLogStore>> {
197        self.object_store_log_store.clone()
198    }
199
200    pub fn plugins(&self) -> Plugins {
201        self.plugins.clone()
202    }
203}
204
205pub struct DatanodeBuilder {
206    opts: DatanodeOptions,
207    table_provider_factory: Option<TableProviderFactoryRef>,
208    plugins: Plugins,
209    meta_client: Option<MetaClientRef>,
210    kv_backend: KvBackendRef,
211    cache_registry: Option<Arc<LayeredCacheRegistry>>,
212    topic_stats_reporter: Option<Box<dyn TopicStatsReporter>>,
213    open_regions_writable_override: Option<bool>,
214    local_file_access: LocalFileAccess,
215    #[cfg(feature = "enterprise")]
216    extension_range_provider_factory: Option<mito2::extension::BoxedExtensionRangeProviderFactory>,
217    /// Set as soon as the object store log store exists so that a failed build can stop it.
218    object_store_log_store: Option<Arc<ObjectStoreLogStore>>,
219}
220
221impl DatanodeBuilder {
222    pub fn new(opts: DatanodeOptions, plugins: Plugins, kv_backend: KvBackendRef) -> Self {
223        Self {
224            opts,
225            table_provider_factory: None,
226            plugins,
227            meta_client: None,
228            kv_backend,
229            cache_registry: None,
230            open_regions_writable_override: None,
231            local_file_access: LocalFileAccess::Disabled,
232            #[cfg(feature = "enterprise")]
233            extension_range_provider_factory: None,
234            topic_stats_reporter: None,
235            object_store_log_store: None,
236        }
237    }
238
239    pub fn options(&self) -> &DatanodeOptions {
240        &self.opts
241    }
242
243    pub fn with_meta_client(&mut self, client: MetaClientRef) -> &mut Self {
244        self.meta_client = Some(client);
245        self
246    }
247
248    pub fn with_cache_registry(&mut self, registry: Arc<LayeredCacheRegistry>) -> &mut Self {
249        self.cache_registry = Some(registry);
250        self
251    }
252
253    pub fn with_local_file_access(&mut self, local_file_access: LocalFileAccess) -> &mut Self {
254        self.local_file_access = local_file_access;
255        self
256    }
257
258    pub fn kv_backend(&self) -> &KvBackendRef {
259        &self.kv_backend
260    }
261
262    pub fn meta_client(&self) -> Option<&MetaClientRef> {
263        self.meta_client.as_ref()
264    }
265
266    pub fn cache_registry(&self) -> Option<&Arc<LayeredCacheRegistry>> {
267        self.cache_registry.as_ref()
268    }
269
270    pub fn set_plugins(&mut self, plugins: Plugins) {
271        self.plugins = plugins;
272    }
273
274    pub fn with_table_provider_factory(&mut self, factory: TableProviderFactoryRef) -> &mut Self {
275        self.table_provider_factory = Some(factory);
276        self
277    }
278
279    /// Overrides whether regions opened during datanode startup should become writable.
280    ///
281    /// When unset, the builder uses its default writable policy for reopened regions
282    /// (writable only when no metasrv client is configured).
283    ///
284    /// Warning: setting this to `true` on a metasrv-controlled datanode (one built
285    /// with `with_meta_client`) will promote regions to Leader before heartbeat and
286    /// lease coordination begin, bypassing the metasrv safety contract and creating a
287    /// potential split-brain window during startup.
288    pub fn with_open_regions_writable_override(&mut self, writable: bool) -> &mut Self {
289        self.open_regions_writable_override = Some(writable);
290        self
291    }
292
293    #[cfg(feature = "enterprise")]
294    pub fn with_extension_range_provider(
295        &mut self,
296        extension_range_provider_factory: mito2::extension::BoxedExtensionRangeProviderFactory,
297    ) -> &mut Self {
298        self.extension_range_provider_factory = Some(extension_range_provider_factory);
299        self
300    }
301
302    pub async fn build(mut self) -> Result<Datanode> {
303        self.try_build().await
304    }
305
306    /// Builds the datanode and stops the object store log store if a later step fails.
307    async fn try_build(&mut self) -> Result<Datanode> {
308        let result = self.build_inner().await;
309        if result.is_err()
310            && let Some(log_store) = &self.object_store_log_store
311            && let Err(err) = log_store.stop().await
312        {
313            warn!(err; "Failed to stop the object store log store after a failed build");
314        }
315        result
316    }
317
318    async fn build_inner(&mut self) -> Result<Datanode> {
319        validate_region_engine_config(&self.opts.region_engine)?;
320        let node_id = self.opts.node_id.context(MissingNodeIdSnafu)?;
321        set_default_prefix(self.opts.default_column_prefix.as_deref())
322            .map_err(BoxedError::new)
323            .context(BuildDatanodeSnafu)?;
324
325        let meta_client = self.meta_client.take();
326
327        // If metasrv client is provided, we will use it to control the region server.
328        // Otherwise the region server is self-controlled, meaning no heartbeat and immediately
329        // writable upon open.
330        let controlled_by_metasrv = meta_client.is_some();
331        if let DatanodeWalConfig::ObjectStore(config) = &self.opts.wal {
332            ensure!(!controlled_by_metasrv, ObjectStoreWalNotStandaloneSnafu);
333            validate_object_store_wal_config(config)?;
334        }
335
336        // build and initialize region server
337        let (region_event_listener, region_event_receiver) = if controlled_by_metasrv {
338            let (tx, rx) = new_region_server_event_channel();
339            (Box::new(tx) as _, Some(rx))
340        } else {
341            (Box::new(NoopRegionServerEventListener) as _, None)
342        };
343
344        let cache_registry = self.cache_registry.take().context(MissingCacheSnafu)?;
345        let schema_cache: SchemaCacheRef = cache_registry.get().context(MissingCacheSnafu)?;
346        let table_id_schema_cache: TableSchemaCacheRef =
347            cache_registry.get().context(MissingCacheSnafu)?;
348
349        let schema_metadata_manager = Arc::new(SchemaMetadataManager::new(
350            table_id_schema_cache,
351            schema_cache,
352        ));
353
354        let gc_enabled = self.opts.region_engine.iter().any(|engine| {
355            if let RegionEngineConfig::Mito(config) = engine {
356                config.gc.enable
357            } else {
358                false
359            }
360        });
361
362        let file_ref_manager = Arc::new(FileReferenceManager::with_gc_enabled(
363            Some(node_id),
364            gc_enabled,
365        ));
366        let region_server = self
367            .new_region_server(
368                schema_metadata_manager,
369                region_event_listener,
370                file_ref_manager,
371                cache_registry.clone(),
372            )
373            .await?;
374        // A region acknowledges only writes whose WAL append completed within its lease from
375        // the metasrv.
376        if controlled_by_metasrv && let Some(mito) = region_server.mito_engine() {
377            mito.require_write_lease();
378        }
379
380        // TODO(weny): Considering introducing a readonly kv_backend trait.
381        let runtime_switch_manager = RuntimeSwitchManager::new(self.kv_backend.clone());
382        let is_recovery_mode = runtime_switch_manager
383            .recovery_mode()
384            .await
385            .context(GetMetadataSnafu)?;
386
387        let region_open_requests =
388            build_region_open_requests(node_id, self.kv_backend.clone()).await?;
389        let open_with_writable = self
390            .open_regions_writable_override
391            .unwrap_or(!controlled_by_metasrv);
392        let open_all_regions = open_all_regions(
393            region_server.clone(),
394            region_open_requests,
395            open_with_writable,
396            self.opts.init_regions_parallelism,
397            // Ignore nonexistent regions in recovery mode.
398            is_recovery_mode,
399        );
400
401        if self.opts.init_regions_in_background {
402            // Opens regions in background.
403            common_runtime::spawn_global(async move {
404                if let Err(err) = open_all_regions.await {
405                    error!(err; "Failed to open regions during the startup.");
406                }
407            });
408        } else {
409            open_all_regions.await?;
410        }
411
412        let heartbeat_task = if let Some(meta_client) = meta_client {
413            let task = self
414                .create_heartbeat_task(&region_server, meta_client, cache_registry)
415                .await?;
416            Some(task)
417        } else {
418            None
419        };
420
421        let is_standalone = heartbeat_task.is_none();
422        let greptimedb_telemetry_task = get_greptimedb_telemetry_task(
423            Some(self.opts.storage.data_home.clone()),
424            is_standalone && self.opts.enable_telemetry,
425        )
426        .await;
427
428        let leases_notifier = if self.opts.require_lease_before_startup && !is_standalone {
429            Some(Arc::new(Notify::new()))
430        } else {
431            None
432        };
433
434        Ok(Datanode {
435            services: ServerHandlers::default(),
436            heartbeat_task,
437            region_server,
438            greptimedb_telemetry_task,
439            region_event_receiver,
440            leases_notifier,
441            plugins: self.plugins.clone(),
442            object_store_log_store: self.object_store_log_store.take(),
443        })
444    }
445
446    async fn create_heartbeat_task(
447        &self,
448        region_server: &RegionServer,
449        meta_client: MetaClientRef,
450        cache_invalidator: CacheInvalidatorRef,
451    ) -> Result<HeartbeatTask> {
452        let stat = {
453            let mut stat = ResourceStatImpl::default();
454            stat.start_collect_cpu_usage();
455            Arc::new(stat)
456        };
457
458        HeartbeatTask::try_new(
459            &self.opts,
460            region_server.clone(),
461            meta_client,
462            self.kv_backend.clone(),
463            cache_invalidator,
464            self.plugins.clone(),
465            stat,
466        )
467        .await
468    }
469
470    /// Builds [ObjectStoreManager] from [StorageConfig] and probes every remote
471    /// object store it contains, unless `probe_on_startup` is disabled.
472    ///
473    /// The probe fails the build when a store rejects any of the operations the
474    /// engines rely on, so a misconfigured storage surfaces here instead of failing
475    /// on the first write.
476    pub async fn build_object_store_manager(
477        cfg: &StorageConfig,
478        node_id: u64,
479    ) -> Result<ObjectStoreManagerRef> {
480        let object_store = store::new_object_store(cfg.store.clone(), &cfg.data_home).await?;
481        let default_name = cfg.store.config_name();
482        let mut checks = Vec::with_capacity(1 + cfg.providers.len());
483        if cfg.store.is_object_storage() {
484            checks.push((default_name.to_string(), object_store.clone()));
485        }
486        let mut object_store_manager = ObjectStoreManager::new(default_name, object_store);
487        for provider in &cfg.providers {
488            let name = provider.config_name();
489            let object_store = store::new_object_store(provider.clone(), &cfg.data_home).await?;
490            if provider.is_object_storage() {
491                checks.push((name.to_string(), object_store.clone()));
492            }
493            object_store_manager.add(name, object_store);
494        }
495
496        if cfg.probe_on_startup && !checks.is_empty() {
497            let prefix = store::probe_key_prefix(node_id);
498            Self::run_probes(&checks, &prefix).await?;
499        }
500
501        Ok(Arc::new(object_store_manager))
502    }
503
504    /// Probes every store in `checks` under its own key prefix.
505    ///
506    /// Providers often share one object storage, so when there is more than one
507    /// store each probe starts after a random delay rather than all at once.
508    ///
509    /// Each check is bounded by [`store::PROBE_TIMEOUT`], so a store that accepts
510    /// connections but never answers fails startup instead of blocking it, after a
511    /// best-effort removal of its probe.
512    async fn run_probes(checks: &[(String, ObjectStore)], prefix: &str) -> Result<()> {
513        let stagger = checks.len() > 1;
514        let checks = checks
515            .iter()
516            .enumerate()
517            .map(|(index, (name, object_store))| {
518                let prefix = format!("{prefix}/{index}");
519                async move {
520                    if stagger {
521                        tokio::time::sleep(store::probe_start_delay()).await;
522                    }
523                    match tokio::time::timeout(
524                        store::PROBE_TIMEOUT,
525                        store::probe_object_store(object_store, name, &prefix),
526                    )
527                    .await
528                    {
529                        Ok(result) => result,
530                        Err(_) => {
531                            store::remove_probe_object(object_store, &prefix).await;
532                            error::ObjectStoreProbeTimeoutSnafu {
533                                name,
534                                timeout: store::PROBE_TIMEOUT,
535                            }
536                            .fail()
537                        }
538                    }
539                }
540            });
541        for result in join_all(checks).await {
542            result?;
543        }
544        Ok(())
545    }
546
547    #[cfg(test)]
548    /// Open all regions belong to this datanode.
549    async fn initialize_region_server(
550        &self,
551        region_server: &RegionServer,
552        open_with_writable: bool,
553    ) -> Result<()> {
554        let node_id = self.opts.node_id.context(MissingNodeIdSnafu)?;
555
556        // TODO(weny): Considering introducing a readonly kv_backend trait.
557        let runtime_switch_manager = RuntimeSwitchManager::new(self.kv_backend.clone());
558        let is_recovery_mode = runtime_switch_manager
559            .recovery_mode()
560            .await
561            .context(GetMetadataSnafu)?;
562        let region_open_requests =
563            build_region_open_requests(node_id, self.kv_backend.clone()).await?;
564
565        open_all_regions(
566            region_server.clone(),
567            region_open_requests,
568            open_with_writable,
569            self.opts.init_regions_parallelism,
570            is_recovery_mode,
571        )
572        .await
573    }
574
575    async fn new_region_server(
576        &mut self,
577        schema_metadata_manager: SchemaMetadataManagerRef,
578        event_listener: RegionServerEventListenerRef,
579        file_ref_manager: FileReferenceManagerRef,
580        cache_registry: Arc<LayeredCacheRegistry>,
581    ) -> Result<RegionServer> {
582        let opts: &DatanodeOptions = &self.opts;
583
584        // Keep `with_dist_planner` false: datanodes execute received MergeScan plans.
585        let catalog_manager = KvBackendCatalogManagerBuilder::new(
586            Arc::new(NoopInformationExtension),
587            self.kv_backend.clone(),
588            cache_registry.clone(),
589        )
590        .build();
591        let partition_manager = catalog_manager.partition_manager();
592
593        // MergeScan region requests stream until completion, so they have no client timeout.
594        let mut channel_config = ChannelConfig {
595            timeout: None,
596            ..Default::default()
597        };
598        if opts.grpc.flight_compression.transport_compression() {
599            channel_config.accept_compression = true;
600            channel_config.send_compression = true;
601        }
602        let node_manager: NodeManagerRef = Arc::new(NodeClients::new(channel_config));
603        let region_query_handler =
604            if let Some(factory) = self.plugins.get::<RegionQueryHandlerFactoryRef>() {
605                factory.build(partition_manager.clone(), node_manager)
606            } else {
607                DatanodeRegionQueryHandler::arc(partition_manager.clone(), node_manager)
608            };
609
610        let query_engine_factory = QueryEngineFactory::try_new_with_plugins(
611            // query engine in datanode only executes plan with resolved table source.
612            catalog_manager,
613            Some(partition_manager),
614            Some(region_query_handler),
615            None,
616            None,
617            None,
618            false,
619            self.plugins.clone(),
620            opts.query.clone(),
621        )
622        .context(DataFusionSnafu)?;
623        let query_engine = query_engine_factory.query_engine();
624
625        let table_provider_factory = self
626            .table_provider_factory
627            .clone()
628            .unwrap_or_else(|| Arc::new(DummyTableProviderFactory));
629        let mut region_server = RegionServer::with_table_provider(
630            query_engine,
631            common_runtime::global_runtime(),
632            event_listener,
633            table_provider_factory,
634            opts.max_concurrent_queries,
635            opts.concurrent_query_limiter_timeout,
636            opts.grpc.flight_compression,
637        );
638        region_server.install_remote_dyn_filter_receiver_injector(&self.plugins);
639
640        let object_store_manager = Self::build_object_store_manager(
641            &opts.storage,
642            opts.node_id.context(MissingNodeIdSnafu)?,
643        )
644        .await?;
645        let engines = self
646            .build_store_engines(
647                object_store_manager,
648                schema_metadata_manager,
649                file_ref_manager,
650                self.plugins.clone(),
651            )
652            .await?;
653        for engine in engines {
654            region_server.register_engine(engine);
655        }
656        if let Some(topic_stats_reporter) = self.topic_stats_reporter.take() {
657            region_server.set_topic_stats_reporter(topic_stats_reporter);
658        }
659
660        Ok(region_server)
661    }
662
663    // internal utils
664
665    /// Builds [RegionEngineRef] from `store_engine` section in `opts`
666    async fn build_store_engines(
667        &mut self,
668        object_store_manager: ObjectStoreManagerRef,
669        schema_metadata_manager: SchemaMetadataManagerRef,
670        file_ref_manager: FileReferenceManagerRef,
671        plugins: Plugins,
672    ) -> Result<Vec<RegionEngineRef>> {
673        let mut metric_engine_config = metric_engine::config::EngineConfig::default();
674        let mut mito_engine_config = MitoConfig::default();
675        let mut file_engine_config = file_engine::config::EngineConfig::default();
676
677        for engine in &self.opts.region_engine {
678            match engine {
679                RegionEngineConfig::Mito(config) => {
680                    mito_engine_config = config.clone();
681                }
682                RegionEngineConfig::File(config) => {
683                    file_engine_config = config.clone();
684                }
685                RegionEngineConfig::Metric(metric_config) => {
686                    metric_engine_config = metric_config.clone();
687                }
688            }
689        }
690
691        // Build a fetcher to backfill partition_expr on open.
692        let fetcher = Arc::new(MetaPartitionExprFetcher::new(self.kv_backend.clone()));
693        let mito_engine = self
694            .build_mito_engine(
695                object_store_manager.clone(),
696                mito_engine_config,
697                schema_metadata_manager.clone(),
698                file_ref_manager.clone(),
699                fetcher.clone(),
700                plugins.clone(),
701            )
702            .await?;
703
704        let metric_engine = MetricEngine::try_new(mito_engine.clone(), metric_engine_config)
705            .context(BuildMetricEngineSnafu)?;
706
707        let file_engine = FileRegionEngine::new(
708            file_engine_config,
709            object_store_manager.default_object_store().clone(), // TODO: implement custom storage for file engine
710            self.local_file_access.clone(),
711        );
712
713        Ok(vec![
714            Arc::new(mito_engine) as _,
715            Arc::new(metric_engine) as _,
716            Arc::new(file_engine) as _,
717        ])
718    }
719
720    /// Builds [MitoEngine] according to options.
721    async fn build_mito_engine(
722        &mut self,
723        object_store_manager: ObjectStoreManagerRef,
724        mut config: MitoConfig,
725        schema_metadata_manager: SchemaMetadataManagerRef,
726        file_ref_manager: FileReferenceManagerRef,
727        partition_expr_fetcher: PartitionExprFetcherRef,
728        plugins: Plugins,
729    ) -> Result<MitoEngine> {
730        let opts = &self.opts;
731        if opts.storage.is_object_storage() {
732            // Enable the write cache when setting object storage
733            config.enable_write_cache = true;
734            info!("Configured 'enable_write_cache=true' for mito engine.");
735        }
736
737        let mito_engine = match &opts.wal {
738            DatanodeWalConfig::RaftEngine(raft_engine_config) => {
739                let log_store =
740                    Self::build_raft_engine_log_store(&opts.storage.data_home, raft_engine_config)
741                        .await?;
742
743                let builder = MitoEngineBuilder::new(
744                    &opts.storage.data_home,
745                    config,
746                    log_store,
747                    object_store_manager,
748                    schema_metadata_manager,
749                    file_ref_manager,
750                    partition_expr_fetcher.clone(),
751                    plugins,
752                );
753
754                #[cfg(feature = "enterprise")]
755                let builder = builder.with_extension_range_provider_factory(
756                    self.extension_range_provider_factory.take(),
757                );
758
759                builder.try_build().await.context(BuildMitoEngineSnafu)?
760            }
761            DatanodeWalConfig::Kafka(kafka_config) => {
762                let log_store = Self::build_kafka_log_store(kafka_config).await?;
763                self.topic_stats_reporter = Some(log_store.topic_stats_reporter());
764                let builder = MitoEngineBuilder::new(
765                    &opts.storage.data_home,
766                    config,
767                    log_store,
768                    object_store_manager,
769                    schema_metadata_manager,
770                    file_ref_manager,
771                    partition_expr_fetcher,
772                    plugins,
773                );
774
775                #[cfg(feature = "enterprise")]
776                let builder = builder.with_extension_range_provider_factory(
777                    self.extension_range_provider_factory.take(),
778                );
779
780                builder.try_build().await.context(BuildMitoEngineSnafu)?
781            }
782            DatanodeWalConfig::Noop => {
783                let log_store = Arc::new(NoopLogStore);
784
785                let builder = MitoEngineBuilder::new(
786                    &opts.storage.data_home,
787                    config,
788                    log_store,
789                    object_store_manager,
790                    schema_metadata_manager,
791                    file_ref_manager,
792                    partition_expr_fetcher.clone(),
793                    plugins,
794                );
795
796                #[cfg(feature = "enterprise")]
797                let builder = builder.with_extension_range_provider_factory(
798                    self.extension_range_provider_factory.take(),
799                );
800
801                builder.try_build().await.context(BuildMitoEngineSnafu)?
802            }
803            DatanodeWalConfig::ObjectStore(object_store_config) => {
804                let object_store = wal_object_store(
805                    &object_store_manager,
806                    &opts.storage,
807                    &object_store_config.storage_provider,
808                )?;
809                let node_id = opts.node_id.context(MissingNodeIdSnafu)?;
810                let log_store =
811                    Self::build_object_store_log_store(object_store, object_store_config, node_id)
812                        .await?;
813                self.object_store_log_store = Some(log_store.clone());
814
815                let builder = MitoEngineBuilder::new(
816                    &opts.storage.data_home,
817                    config,
818                    log_store,
819                    object_store_manager,
820                    schema_metadata_manager,
821                    file_ref_manager,
822                    partition_expr_fetcher,
823                    plugins,
824                );
825
826                #[cfg(feature = "enterprise")]
827                let builder = builder.with_extension_range_provider_factory(
828                    self.extension_range_provider_factory.take(),
829                );
830
831                builder.try_build().await.context(BuildMitoEngineSnafu)?
832            }
833        };
834        Ok(mito_engine)
835    }
836
837    /// Builds [ObjectStoreLogStore] under the node prefix of `node_id` in the
838    /// standalone generation, the prefix the regions persist in their WAL options.
839    async fn build_object_store_log_store(
840        object_store: ObjectStore,
841        config: &ObjectStoreWalConfig,
842        node_id: u64,
843    ) -> Result<Arc<ObjectStoreLogStore>> {
844        info!(
845            "Creating object store logstore with config: {:?}, node id: {}",
846            config, node_id
847        );
848        ObjectStoreLogStore::try_new(object_store, config, node_id, STANDALONE_GENERATION)
849            .await
850            .map_err(Box::new)
851            .context(OpenLogStoreSnafu)
852    }
853
854    /// Builds [RaftEngineLogStore].
855    async fn build_raft_engine_log_store(
856        data_home: &str,
857        config: &RaftEngineConfig,
858    ) -> Result<Arc<RaftEngineLogStore>> {
859        let data_home = normalize_dir(data_home);
860        let wal_dir = match &config.dir {
861            Some(dir) => dir.clone(),
862            None => format!("{}{WAL_DIR}", data_home),
863        };
864
865        // create WAL directory
866        fs::create_dir_all(Path::new(&wal_dir))
867            .await
868            .context(CreateDirSnafu { dir: &wal_dir })?;
869        info!(
870            "Creating raft-engine logstore with config: {:?} and storage path: {}",
871            config, &wal_dir
872        );
873        let logstore = RaftEngineLogStore::try_new(wal_dir, config)
874            .await
875            .map_err(Box::new)
876            .context(OpenLogStoreSnafu)?;
877
878        Ok(Arc::new(logstore))
879    }
880
881    /// Builds [`KafkaLogStore`].
882    async fn build_kafka_log_store(config: &DatanodeKafkaConfig) -> Result<Arc<KafkaLogStore>> {
883        KafkaLogStore::try_new(config)
884            .await
885            .map_err(Box::new)
886            .context(OpenLogStoreSnafu)
887            .map(Arc::new)
888    }
889}
890
891/// Keeps the first shutdown error and logs the later ones.
892pub(crate) fn record_shutdown_error(first_error: &mut Option<error::Error>, result: Result<()>) {
893    if let Err(err) = result {
894        if first_error.is_none() {
895            *first_error = Some(err);
896        } else {
897            warn!(err; "Ignored a later shutdown error");
898        }
899    }
900}
901
902/// Returns the object store that holds the WAL objects: the default one for an
903/// empty `storage_provider`, otherwise the configured provider with that name.
904fn wal_object_store(
905    object_store_manager: &ObjectStoreManager,
906    storage: &StorageConfig,
907    storage_provider: &str,
908) -> Result<ObjectStore> {
909    if storage_provider.is_empty() {
910        return Ok(object_store_manager.default_object_store().clone());
911    }
912    object_store_manager
913        .find(storage_provider)
914        .cloned()
915        .with_context(|| {
916            let configured = std::iter::once(&storage.store)
917                .chain(storage.providers.iter())
918                .map(|store| store.config_name())
919                .collect::<Vec<_>>();
920            InvalidObjectStoreWalConfigSnafu {
921                field: "storage_provider",
922                value: storage_provider,
923                reason: format!("must name a configured storage provider, one of {configured:?}"),
924            }
925        })
926}
927
928/// Rejects repeated engine types before their configs can silently overwrite each other.
929fn validate_region_engine_config(configs: &[RegionEngineConfig]) -> Result<()> {
930    let mut engine_indices = HashMap::new();
931    for (index, config) in configs.iter().enumerate() {
932        let engine = match config {
933            RegionEngineConfig::Mito(_) => MITO_ENGINE,
934            RegionEngineConfig::File(_) => FILE_ENGINE,
935            RegionEngineConfig::Metric(_) => METRIC_ENGINE,
936        };
937        if let Some(first_index) = engine_indices.insert(engine, index) {
938            return DuplicateRegionEngineConfigSnafu {
939                engine,
940                first_index,
941                duplicate_index: index,
942            }
943            .fail();
944        }
945    }
946    Ok(())
947}
948
949/// Rejects an object store WAL config the log store cannot run on.
950fn validate_object_store_wal_config(config: &ObjectStoreWalConfig) -> Result<()> {
951    let prefix = config.prefix.trim();
952    ensure!(
953        !prefix.is_empty(),
954        InvalidObjectStoreWalConfigSnafu {
955            field: "prefix",
956            value: &config.prefix,
957            reason: "must not be empty",
958        }
959    );
960    ensure!(
961        !prefix.starts_with('/'),
962        InvalidObjectStoreWalConfigSnafu {
963            field: "prefix",
964            value: &config.prefix,
965            reason: "must be a relative path",
966        }
967    );
968    ensure!(
969        !prefix
970            .split('/')
971            .any(|component| component.is_empty() || component == ".."),
972        InvalidObjectStoreWalConfigSnafu {
973            field: "prefix",
974            value: &config.prefix,
975            reason: "must not contain empty or `..` components",
976        }
977    );
978    ensure!(
979        config.flush_interval >= Duration::from_millis(10),
980        InvalidObjectStoreWalConfigSnafu {
981            field: "flush_interval",
982            value: format!("{:?}", config.flush_interval),
983            reason: "must be at least 10ms",
984        }
985    );
986    ensure!(
987        config.max_batch_bytes.as_bytes() > 0,
988        InvalidObjectStoreWalConfigSnafu {
989            field: "max_batch_bytes",
990            value: config.max_batch_bytes.to_string(),
991            reason: "must be greater than 0",
992        }
993    );
994    ensure!(
995        config.max_unpersisted_bytes.as_bytes() > 0,
996        InvalidObjectStoreWalConfigSnafu {
997            field: "max_unpersisted_bytes",
998            value: config.max_unpersisted_bytes.to_string(),
999            reason: "must be greater than 0",
1000        }
1001    );
1002    ensure!(
1003        config.max_unpersisted_age > Duration::ZERO,
1004        InvalidObjectStoreWalConfigSnafu {
1005            field: "max_unpersisted_age",
1006            value: format!("{:?}", config.max_unpersisted_age),
1007            reason: "must be greater than 0",
1008        }
1009    );
1010    Ok(())
1011}
1012
1013/// Open all regions belong to this datanode.
1014async fn open_all_regions(
1015    region_server: RegionServer,
1016    region_open_requests: RegionOpenRequests,
1017    open_with_writable: bool,
1018    init_regions_parallelism: usize,
1019    ignore_nonexistent_region: bool,
1020) -> Result<()> {
1021    let RegionOpenRequests {
1022        leader_regions,
1023        #[cfg(feature = "enterprise")]
1024        follower_regions,
1025    } = region_open_requests;
1026
1027    let leader_region_num = leader_regions.len();
1028    info!("going to open {} region(s)", leader_region_num);
1029    let now = Instant::now();
1030    let open_regions = region_server
1031        .handle_batch_open_requests(
1032            init_regions_parallelism,
1033            leader_regions,
1034            ignore_nonexistent_region,
1035        )
1036        .await?;
1037    info!(
1038        "Opened {} regions in {:?}",
1039        open_regions.len(),
1040        now.elapsed()
1041    );
1042    if !ignore_nonexistent_region {
1043        ensure!(
1044            open_regions.len() == leader_region_num,
1045            error::UnexpectedSnafu {
1046                violated: format!(
1047                    "Expected to open {} of regions, only {} of regions has opened",
1048                    leader_region_num,
1049                    open_regions.len()
1050                )
1051            }
1052        );
1053    } else if open_regions.len() != leader_region_num {
1054        warn!(
1055            "ignore nonexistent region, expected to open {} of regions, only {} of regions has opened",
1056            leader_region_num,
1057            open_regions.len()
1058        );
1059    }
1060
1061    for region_id in open_regions {
1062        if open_with_writable {
1063            let res = region_server.set_region_role(region_id, RegionRole::Leader);
1064            match res {
1065                Ok(_) => {
1066                    // Finalize leadership: persist backfilled metadata.
1067                    if let SetRegionRoleStateResponse::InvalidTransition(err) = region_server
1068                        .set_region_role_state_gracefully(
1069                            region_id,
1070                            SettableRegionRoleState::Leader,
1071                        )
1072                        .await?
1073                    {
1074                        error!(err; "failed to convert region {region_id} to leader");
1075                    }
1076                }
1077                Err(e) => {
1078                    error!(e; "failed to convert region {region_id} to leader");
1079                }
1080            }
1081        }
1082    }
1083
1084    #[cfg(feature = "enterprise")]
1085    if !follower_regions.is_empty() {
1086        use tokio::time::Instant;
1087
1088        let follower_region_num = follower_regions.len();
1089        info!("going to open {} follower region(s)", follower_region_num);
1090
1091        let now = Instant::now();
1092        let open_regions = region_server
1093            .handle_batch_open_requests(
1094                init_regions_parallelism,
1095                follower_regions,
1096                ignore_nonexistent_region,
1097            )
1098            .await?;
1099        info!(
1100            "Opened {} follower regions in {:?}",
1101            open_regions.len(),
1102            now.elapsed()
1103        );
1104
1105        if !ignore_nonexistent_region {
1106            ensure!(
1107                open_regions.len() == follower_region_num,
1108                error::UnexpectedSnafu {
1109                    violated: format!(
1110                        "Expected to open {} of follower regions, only {} of regions has opened",
1111                        follower_region_num,
1112                        open_regions.len()
1113                    )
1114                }
1115            );
1116        } else if open_regions.len() != follower_region_num {
1117            warn!(
1118                "ignore nonexistent region, expected to open {} of follower regions, only {} of regions has opened",
1119                follower_region_num,
1120                open_regions.len()
1121            );
1122        }
1123    }
1124
1125    info!("all regions are opened");
1126
1127    Ok(())
1128}
1129
1130#[cfg(test)]
1131mod tests {
1132    use std::assert_matches;
1133    use std::collections::{BTreeMap, HashMap};
1134    use std::io::Write;
1135    use std::ops::Range;
1136    use std::path::Path;
1137    use std::sync::Arc;
1138    use std::sync::atomic::{AtomicUsize, Ordering};
1139    use std::time::Duration;
1140
1141    use api::v1::{ColumnSchema, Rows};
1142    use cache::build_datanode_layered_cache_registry;
1143    use common_base::Plugins;
1144    use common_base::readable_size::ReadableSize;
1145    use common_base::secrets::SecretString;
1146    use common_config::Configurable;
1147    use common_error::ext::ErrorExt;
1148    use common_error::status_code::StatusCode;
1149    use common_meta::key::RegionRoleSet;
1150    use common_meta::key::datanode_table::DatanodeTableManager;
1151    use common_meta::kv_backend::KvBackendRef;
1152    use common_meta::kv_backend::memory::MemoryKvBackend;
1153    use common_meta::node_manager::NodeManagerRef;
1154    use common_test_util::temp_dir::{create_named_temp_file, create_temp_dir};
1155    use common_wal::config::DatanodeWalConfig;
1156    use common_wal::config::object_store::{AckMode, ObjectStoreWalConfig, STANDALONE_GENERATION};
1157    use common_wal::options::{ObjectStoreWalOptions, WAL_OPTIONS_KEY, WalOptions};
1158    use log_store::ObjectStoreLogStore;
1159    use log_store::error::Error as LogStoreError;
1160    use meta_client::client::MetaClientBuilder;
1161    use mito2::engine::MITO_ENGINE_NAME;
1162    use mito2::test_util::{CreateRequestBuilder, build_rows, rows_schema};
1163    use object_store::ObjectStore;
1164    use object_store::config::{S3Config, S3Connection};
1165    use object_store::manager::ObjectStoreManager;
1166    use partition::manager::PartitionRuleManagerRef;
1167    use query::region_query::{RegionQueryHandlerFactory, RegionQueryHandlerFactoryRef};
1168    use store_api::logstore::LogStore;
1169    use store_api::logstore::provider::Provider;
1170    use store_api::region_request::{PathType, RegionOpenRequest, RegionPutRequest, RegionRequest};
1171    use store_api::storage::RegionId;
1172
1173    use crate::config::{DatanodeOptions, ObjectStoreConfig, RegionEngineConfig, StorageConfig};
1174    use crate::datanode::{
1175        Datanode, DatanodeBuilder, validate_object_store_wal_config, validate_region_engine_config,
1176        wal_object_store,
1177    };
1178    use crate::error::{self, Error};
1179    use crate::region_query::DatanodeRegionQueryHandler;
1180    use crate::region_server::RegionServer;
1181    use crate::tests::{MockRegionEngine, mock_region_server};
1182
1183    const WAIT: Duration = Duration::from_secs(30);
1184
1185    async fn setup_table_datanode(kv: &KvBackendRef) {
1186        let mgr = DatanodeTableManager::new(kv.clone());
1187        let txn = mgr
1188            .build_create_txn(
1189                1028,
1190                MITO_ENGINE_NAME,
1191                "foo/bar/weny",
1192                HashMap::from([("foo".to_string(), "bar".to_string())]),
1193                HashMap::default(),
1194                BTreeMap::from([(0, RegionRoleSet::new(vec![0, 1, 2], vec![]))]),
1195            )
1196            .unwrap();
1197
1198        let r = kv.txn(txn).await.unwrap();
1199        assert!(r.succeeded);
1200    }
1201
1202    #[tokio::test]
1203    async fn test_initialize_region_server() {
1204        common_telemetry::init_default_ut_logging();
1205        let mut mock_region_server = mock_region_server();
1206        let (mock_region, mut mock_region_handler) = MockRegionEngine::new(MITO_ENGINE_NAME);
1207
1208        mock_region_server.register_engine(mock_region.clone());
1209
1210        let kv_backend = Arc::new(MemoryKvBackend::new());
1211        let layered_cache_registry =
1212            Arc::new(build_datanode_layered_cache_registry(kv_backend.clone()));
1213
1214        let mut builder = DatanodeBuilder::new(
1215            DatanodeOptions {
1216                node_id: Some(0),
1217                ..Default::default()
1218            },
1219            Plugins::default(),
1220            kv_backend.clone(),
1221        );
1222        builder.with_cache_registry(layered_cache_registry);
1223        setup_table_datanode(&(kv_backend as _)).await;
1224
1225        builder
1226            .initialize_region_server(&mock_region_server, false)
1227            .await
1228            .unwrap();
1229
1230        for i in 0..3 {
1231            let (region_id, req) = mock_region_handler.recv().await.unwrap();
1232            assert_eq!(region_id, RegionId::new(1028, i));
1233            if let RegionRequest::Open(req) = req {
1234                assert_eq!(
1235                    req.options,
1236                    HashMap::from([("foo".to_string(), "bar".to_string())])
1237                )
1238            } else {
1239                unreachable!()
1240            }
1241        }
1242
1243        assert_matches!(
1244            mock_region_handler.try_recv(),
1245            Err(tokio::sync::mpsc::error::TryRecvError::Empty)
1246        );
1247    }
1248
1249    fn datanode_builder(
1250        data_home: &str,
1251        wal: DatanodeWalConfig,
1252        kv_backend: KvBackendRef,
1253    ) -> DatanodeBuilder {
1254        let layered_cache_registry =
1255            Arc::new(build_datanode_layered_cache_registry(kv_backend.clone()));
1256        let mut opts = DatanodeOptions {
1257            node_id: Some(0),
1258            wal,
1259            enable_telemetry: false,
1260            ..Default::default()
1261        };
1262        opts.storage.data_home = data_home.to_string();
1263
1264        let mut builder = DatanodeBuilder::new(opts, Plugins::default(), kv_backend);
1265        builder.with_cache_registry(layered_cache_registry);
1266        builder
1267    }
1268
1269    fn object_store_wal_builder(data_home: &str, config: ObjectStoreWalConfig) -> DatanodeBuilder {
1270        datanode_builder(
1271            data_home,
1272            DatanodeWalConfig::ObjectStore(config),
1273            Arc::new(MemoryKvBackend::new()),
1274        )
1275    }
1276
1277    async fn build_err(builder: DatanodeBuilder) -> Error {
1278        match builder.build().await {
1279            Ok(_) => panic!("build must fail"),
1280            Err(err) => err,
1281        }
1282    }
1283
1284    fn is_empty_dir(dir: &Path) -> bool {
1285        std::fs::read_dir(dir).unwrap().next().is_none()
1286    }
1287
1288    /// Returns true if the store rejects even an empty append, which it does only once stopped.
1289    async fn is_stopped(log_store: &ObjectStoreLogStore) -> bool {
1290        matches!(
1291            log_store.append_batch(vec![]).await,
1292            Err(LogStoreError::ObjectStoreWalStopped { .. })
1293        )
1294    }
1295
1296    #[test]
1297    fn test_validate_unique_region_engine_config() {
1298        let mut opts = DatanodeOptions::default();
1299        validate_region_engine_config(&opts.region_engine).unwrap();
1300
1301        opts.region_engine
1302            .push(RegionEngineConfig::Metric(Default::default()));
1303        validate_region_engine_config(&opts.region_engine).unwrap();
1304
1305        // Omitted engines still use their defaults during engine construction.
1306        validate_region_engine_config(&[]).unwrap();
1307    }
1308
1309    #[tokio::test]
1310    async fn test_build_rejects_duplicate_region_engine_config() {
1311        for (engine, first_index) in [("mito", 0), ("file", 1), ("metric", 2)] {
1312            let mut file = create_named_temp_file();
1313            write!(
1314                file,
1315                r#"
1316                [[region_engine]]
1317                [region_engine.mito]
1318                global_write_buffer_size = "4GiB"
1319                min_compaction_interval = "600s"
1320
1321                [[region_engine]]
1322                [region_engine.file]
1323
1324                [[region_engine]]
1325                [region_engine.metric]
1326
1327                [[region_engine]]
1328                [region_engine.{engine}]
1329                "#
1330            )
1331            .unwrap();
1332            let opts = DatanodeOptions::load_layered_options(
1333                Some(file.path().to_str().unwrap()),
1334                "DATANODE_DUPLICATE_REGION_ENGINE_UT",
1335            )
1336            .unwrap();
1337            let data_home = create_temp_dir("duplicate-region-engine-config");
1338            let mut builder = datanode_builder(
1339                data_home.path().to_str().unwrap(),
1340                DatanodeWalConfig::default(),
1341                Arc::new(MemoryKvBackend::new()),
1342            );
1343            builder.opts.region_engine = opts.region_engine;
1344            let err = build_err(builder).await;
1345
1346            let Error::DuplicateRegionEngineConfig {
1347                engine: actual_engine,
1348                first_index: actual_first_index,
1349                duplicate_index,
1350                ..
1351            } = &err
1352            else {
1353                panic!("unexpected error for {engine}: {err:?}");
1354            };
1355            assert_eq!(*actual_engine, engine);
1356            assert_eq!(*actual_first_index, first_index);
1357            assert_eq!(*duplicate_index, 3);
1358            assert_eq!(err.status_code(), StatusCode::InvalidArguments);
1359            // Rejected before the builder creates any storage or log store.
1360            assert!(is_empty_dir(data_home.path()));
1361        }
1362    }
1363
1364    #[tokio::test]
1365    async fn test_build_rejects_invalid_object_store_wal_config() {
1366        let cases = [
1367            (
1368                ObjectStoreWalConfig {
1369                    prefix: String::new(),
1370                    ..Default::default()
1371                },
1372                "prefix",
1373            ),
1374            (
1375                ObjectStoreWalConfig {
1376                    prefix: "/wal".to_string(),
1377                    ..Default::default()
1378                },
1379                "prefix",
1380            ),
1381            (
1382                ObjectStoreWalConfig {
1383                    prefix: "wal/../other".to_string(),
1384                    ..Default::default()
1385                },
1386                "prefix",
1387            ),
1388            (
1389                ObjectStoreWalConfig {
1390                    prefix: "wal//objects".to_string(),
1391                    ..Default::default()
1392                },
1393                "prefix",
1394            ),
1395            (
1396                ObjectStoreWalConfig {
1397                    prefix: "wal/".to_string(),
1398                    ..Default::default()
1399                },
1400                "prefix",
1401            ),
1402            (
1403                ObjectStoreWalConfig {
1404                    flush_interval: Duration::from_millis(9),
1405                    ..Default::default()
1406                },
1407                "flush_interval",
1408            ),
1409            (
1410                ObjectStoreWalConfig {
1411                    max_batch_bytes: ReadableSize(0),
1412                    ..Default::default()
1413                },
1414                "max_batch_bytes",
1415            ),
1416            (
1417                ObjectStoreWalConfig {
1418                    max_unpersisted_bytes: ReadableSize(0),
1419                    ..Default::default()
1420                },
1421                "max_unpersisted_bytes",
1422            ),
1423            (
1424                ObjectStoreWalConfig {
1425                    max_unpersisted_age: Duration::ZERO,
1426                    ..Default::default()
1427                },
1428                "max_unpersisted_age",
1429            ),
1430        ];
1431
1432        for (config, expected_field) in cases {
1433            let expected_value = match expected_field {
1434                "prefix" => config.prefix.clone(),
1435                "flush_interval" => format!("{:?}", config.flush_interval),
1436                "max_unpersisted_bytes" => config.max_unpersisted_bytes.to_string(),
1437                "max_unpersisted_age" => format!("{:?}", config.max_unpersisted_age),
1438                _ => config.max_batch_bytes.to_string(),
1439            };
1440            let data_home = create_temp_dir("object-store-wal-invalid-config");
1441            let builder = object_store_wal_builder(data_home.path().to_str().unwrap(), config);
1442            let err = build_err(builder).await;
1443
1444            let Error::InvalidObjectStoreWalConfig { field, value, .. } = &err else {
1445                panic!("unexpected error for {expected_field}: {err:?}");
1446            };
1447            assert_eq!(*field, expected_field);
1448            assert_eq!(*value, expected_value);
1449            assert_eq!(StatusCode::InvalidArguments, err.status_code());
1450            // The builder stops before it creates any storage or log store.
1451            assert!(is_empty_dir(data_home.path()));
1452        }
1453    }
1454
1455    #[tokio::test]
1456    async fn test_build_rejects_object_store_wal_with_meta_client() {
1457        let data_home = create_temp_dir("object-store-wal-meta-client");
1458        let meta_client = Arc::new(MetaClientBuilder::datanode_default_options(0).build());
1459
1460        let mut builder = object_store_wal_builder(
1461            data_home.path().to_str().unwrap(),
1462            ObjectStoreWalConfig::default(),
1463        );
1464        builder.with_meta_client(meta_client);
1465        let err = build_err(builder).await;
1466
1467        assert_matches!(err, Error::ObjectStoreWalNotStandalone { .. });
1468        assert_eq!(StatusCode::InvalidArguments, err.status_code());
1469        // The builder stops before it creates any storage or log store.
1470        assert!(is_empty_dir(data_home.path()));
1471    }
1472
1473    #[tokio::test]
1474    async fn test_build_rejects_unknown_wal_storage_provider() {
1475        let data_home = create_temp_dir("object-store-wal-unknown-provider");
1476        let builder = object_store_wal_builder(
1477            data_home.path().to_str().unwrap(),
1478            ObjectStoreWalConfig {
1479                storage_provider: "s3".to_string(),
1480                ..Default::default()
1481            },
1482        );
1483        let err = build_err(builder).await;
1484
1485        let Error::InvalidObjectStoreWalConfig {
1486            field,
1487            value,
1488            reason,
1489            ..
1490        } = &err
1491        else {
1492            panic!("unexpected error: {err:?}");
1493        };
1494        assert_eq!(*field, "storage_provider");
1495        assert_eq!(value, "s3");
1496        assert!(reason.contains(r#"["File"]"#), "{reason}");
1497        assert_eq!(StatusCode::InvalidArguments, err.status_code());
1498        // No log store is created under the prefix.
1499        assert!(!data_home.path().join("wal").exists());
1500    }
1501
1502    #[test]
1503    fn test_wal_object_store_selects_provider() {
1504        fn fs_object_store(dir: &Path) -> ObjectStore {
1505            let builder = object_store::services::Fs::default().root(dir.to_str().unwrap());
1506            ObjectStore::new(builder).unwrap()
1507        }
1508
1509        let default_dir = create_temp_dir("wal-object-store-default");
1510        let named_dir = create_temp_dir("wal-object-store-named");
1511        let mut manager = ObjectStoreManager::new("File", fs_object_store(default_dir.path()));
1512        manager.add("Wal-Store", fs_object_store(named_dir.path()));
1513        let storage = StorageConfig::default();
1514
1515        let root = |store: ObjectStore| store.info().root().clone();
1516        assert_eq!(
1517            root(wal_object_store(&manager, &storage, "").unwrap()),
1518            root(manager.default_object_store().clone())
1519        );
1520        assert_eq!(
1521            root(wal_object_store(&manager, &storage, "wal-store").unwrap()),
1522            root(manager.find("Wal-Store").unwrap().clone())
1523        );
1524        assert_matches!(
1525            wal_object_store(&manager, &storage, "missing").unwrap_err(),
1526            Error::InvalidObjectStoreWalConfig {
1527                field: "storage_provider",
1528                ..
1529            }
1530        );
1531    }
1532
1533    #[tokio::test]
1534    async fn test_object_store_probe_can_be_disabled() {
1535        // The endpoint does not accept connections, so this only succeeds when the
1536        // probe does not run.
1537        let storage = StorageConfig {
1538            store: ObjectStoreConfig::S3(S3Config {
1539                connection: S3Connection {
1540                    bucket: "test".to_string(),
1541                    endpoint: Some("http://127.0.0.1:1".to_string()),
1542                    region: Some("us-east-1".to_string()),
1543                    access_key_id: SecretString::from("test".to_string()),
1544                    secret_access_key: SecretString::from("test".to_string()),
1545                    ..Default::default()
1546                },
1547                ..Default::default()
1548            }),
1549            probe_on_startup: false,
1550            ..Default::default()
1551        };
1552
1553        DatanodeBuilder::build_object_store_manager(&storage, 1)
1554            .await
1555            .unwrap();
1556    }
1557
1558    #[tokio::test]
1559    async fn test_file_store_is_not_probed() {
1560        // The probe is enabled by default, but a File store must skip it.
1561        let data_home = create_temp_dir("object-store-probe-file");
1562        let storage = StorageConfig {
1563            data_home: data_home.path().to_str().unwrap().to_string(),
1564            ..Default::default()
1565        };
1566
1567        DatanodeBuilder::build_object_store_manager(&storage, 1)
1568            .await
1569            .unwrap();
1570
1571        // The probe would leave this directory behind if it ran.
1572        assert!(!data_home.path().join(".greptime_probe").exists());
1573    }
1574
1575    #[tokio::test]
1576    async fn test_object_store_probe_failure_aborts_build() {
1577        // The endpoint does not accept connections, so the probe write fails and the
1578        // build must fail with it.
1579        let storage = StorageConfig {
1580            store: ObjectStoreConfig::S3(S3Config {
1581                connection: S3Connection {
1582                    bucket: "test".to_string(),
1583                    endpoint: Some("http://127.0.0.1:1".to_string()),
1584                    region: Some("us-east-1".to_string()),
1585                    access_key_id: SecretString::from("test".to_string()),
1586                    secret_access_key: SecretString::from("test".to_string()),
1587                    ..Default::default()
1588                },
1589                ..Default::default()
1590            }),
1591            ..Default::default()
1592        };
1593
1594        let err = DatanodeBuilder::build_object_store_manager(&storage, 1)
1595            .await
1596            .unwrap_err();
1597        assert_matches!(
1598            err,
1599            Error::ObjectStoreProbe {
1600                name,
1601                step: "write",
1602                ..
1603            } if name == storage.store.config_name()
1604        );
1605    }
1606
1607    struct CountingRegionQueryHandlerFactory(Arc<AtomicUsize>);
1608
1609    impl RegionQueryHandlerFactory for CountingRegionQueryHandlerFactory {
1610        fn build(
1611            &self,
1612            partition_manager: PartitionRuleManagerRef,
1613            node_manager: NodeManagerRef,
1614        ) -> query::region_query::RegionQueryHandlerRef {
1615            self.0.fetch_add(1, Ordering::SeqCst);
1616            DatanodeRegionQueryHandler::arc(partition_manager, node_manager)
1617        }
1618    }
1619
1620    #[tokio::test]
1621    async fn test_build_uses_installed_region_query_handler_factory() {
1622        let data_home = create_temp_dir("region-query-handler-factory");
1623        let builds = Arc::new(AtomicUsize::new(0));
1624        let plugins = Plugins::new();
1625        plugins.insert(Arc::new(CountingRegionQueryHandlerFactory(builds.clone()))
1626            as RegionQueryHandlerFactoryRef);
1627        let mut builder = datanode_builder(
1628            data_home.path().to_str().unwrap(),
1629            DatanodeWalConfig::default(),
1630            Arc::new(MemoryKvBackend::new()),
1631        );
1632        builder.set_plugins(plugins);
1633
1634        let mut datanode = builder.build().await.unwrap();
1635        assert_eq!(builds.load(Ordering::SeqCst), 1);
1636        datanode.shutdown().await.unwrap();
1637    }
1638
1639    #[tokio::test]
1640    async fn test_build_and_shutdown_with_raft_engine_wal() {
1641        let data_home = create_temp_dir("raft-engine-wal");
1642        let builder = datanode_builder(
1643            data_home.path().to_str().unwrap(),
1644            DatanodeWalConfig::default(),
1645            Arc::new(MemoryKvBackend::new()),
1646        );
1647
1648        let mut datanode = builder.build().await.unwrap();
1649        assert!(datanode.object_store_log_store.is_none());
1650        assert!(data_home.path().join("wal").exists());
1651        datanode.shutdown().await.unwrap();
1652    }
1653
1654    #[tokio::test]
1655    async fn test_build_and_shutdown_with_object_store_wal() {
1656        common_telemetry::init_default_ut_logging();
1657        let data_home = create_temp_dir("object-store-wal");
1658        let builder = object_store_wal_builder(
1659            data_home.path().to_str().unwrap(),
1660            ObjectStoreWalConfig::default(),
1661        );
1662
1663        let mut datanode = builder.build().await.unwrap();
1664        let log_store = datanode.object_store_log_store.clone().unwrap();
1665        assert!(!is_stopped(&log_store).await);
1666        // The store runs under the node prefix derived from the configured root.
1667        assert!(format!("{log_store:?}").contains(r#"prefix: "wal/datanodes/0/epochs/0""#));
1668
1669        datanode.shutdown().await.unwrap();
1670        assert!(is_stopped(&log_store).await);
1671    }
1672
1673    #[tokio::test]
1674    async fn test_build_with_named_wal_storage_provider() {
1675        let data_home = create_temp_dir("object-store-wal-named-provider");
1676        // The default file store is registered under its provider name.
1677        let builder = object_store_wal_builder(
1678            data_home.path().to_str().unwrap(),
1679            ObjectStoreWalConfig {
1680                storage_provider: "file".to_string(),
1681                ..Default::default()
1682            },
1683        );
1684
1685        let mut datanode = builder.build().await.unwrap();
1686        assert!(datanode.object_store_log_store.is_some());
1687        datanode.shutdown().await.unwrap();
1688    }
1689
1690    #[tokio::test]
1691    async fn test_shutdown_stops_every_engine_and_the_log_store_after_engine_failures() {
1692        common_telemetry::init_default_ut_logging();
1693        let data_home = create_temp_dir("object-store-wal-shutdown-failure");
1694        let builder = object_store_wal_builder(
1695            data_home.path().to_str().unwrap(),
1696            ObjectStoreWalConfig::default(),
1697        );
1698        let mut datanode = builder.build().await.unwrap();
1699        let log_store = datanode.object_store_log_store.clone().unwrap();
1700        // Both engines fail to stop, so whichever the region server stops first fails.
1701        let stop_calls = Arc::new(AtomicUsize::new(0));
1702        for name in ["failing-a", "failing-b"] {
1703            let stop_calls = stop_calls.clone();
1704            let (engine, _) = MockRegionEngine::with_custom_apply_fn(name, move |engine| {
1705                engine.handle_stop_mock_fn = Some(Box::new(move || {
1706                    stop_calls.fetch_add(1, Ordering::Relaxed);
1707                    error::UnexpectedSnafu {
1708                        violated: "stop failed",
1709                    }
1710                    .fail()
1711                }));
1712            });
1713            datanode.region_server().register_engine(engine);
1714        }
1715
1716        let err = datanode.shutdown().await.unwrap_err();
1717
1718        assert_matches!(err, Error::StopRegionEngine { .. });
1719        assert_eq!(2, stop_calls.load(Ordering::Relaxed));
1720        assert!(is_stopped(&log_store).await);
1721    }
1722
1723    #[tokio::test]
1724    async fn test_failed_build_stops_object_store_log_store() {
1725        common_telemetry::init_default_ut_logging();
1726        let data_home = create_temp_dir("object-store-wal-failed-build");
1727        let kv_backend = Arc::new(MemoryKvBackend::new());
1728        // Regions registered in the metadata but missing on disk fail the region open
1729        // that runs after the log store and the engines are built.
1730        setup_table_datanode(&(kv_backend.clone() as _)).await;
1731        let mut builder = datanode_builder(
1732            data_home.path().to_str().unwrap(),
1733            DatanodeWalConfig::ObjectStore(ObjectStoreWalConfig::default()),
1734            kv_backend,
1735        );
1736
1737        assert!(builder.try_build().await.is_err());
1738
1739        let log_store = builder.object_store_log_store.as_ref().unwrap();
1740        assert!(is_stopped(log_store).await);
1741    }
1742
1743    const REGION: RegionId = RegionId::new(1024, 1);
1744
1745    fn enqueued_wal_config(flush_interval: Duration) -> ObjectStoreWalConfig {
1746        ObjectStoreWalConfig {
1747            ack_mode: AckMode::Enqueued,
1748            flush_interval,
1749            ..Default::default()
1750        }
1751    }
1752
1753    /// The options that put a region on the WAL of a datanode with `config`.
1754    fn region_wal_options(config: &ObjectStoreWalConfig) -> HashMap<String, String> {
1755        let prefix = config.node_prefix(0, STANDALONE_GENERATION);
1756        let wal_options =
1757            serde_json::to_string(&WalOptions::ObjectStore(ObjectStoreWalOptions::new(prefix)))
1758                .unwrap();
1759        HashMap::from([(WAL_OPTIONS_KEY.to_string(), wal_options)])
1760    }
1761
1762    async fn put_rows(
1763        region_server: &RegionServer,
1764        schema: &[ColumnSchema],
1765        rows: Range<usize>,
1766        skip_wal: bool,
1767    ) {
1768        if rows.is_empty() {
1769            return;
1770        }
1771        region_server
1772            .handle_request(
1773                REGION,
1774                RegionRequest::Put(RegionPutRequest {
1775                    rows: Rows {
1776                        schema: schema.to_vec(),
1777                        rows: build_rows(rows.start, rows.end),
1778                    },
1779                    skip_wal,
1780                    hint: None,
1781                    partition_expr_version: None,
1782                }),
1783            )
1784            .await
1785            .unwrap();
1786    }
1787
1788    /// Builds a datanode on an `enqueued` object store WAL with
1789    /// `flush_interval` and creates [`REGION`] on it with `durable_rows`
1790    /// written through the WAL and uploaded. Then makes every later create
1791    /// of the WAL fail with a transient error if `uploads_fail`, and writes
1792    /// `rows` through the WAL and `skipped_rows` past it. Returns the datanode
1793    /// and its log store.
1794    async fn datanode_with_object_store_wal(
1795        data_home: &str,
1796        flush_interval: Duration,
1797        durable_rows: usize,
1798        uploads_fail: bool,
1799        rows: usize,
1800        skipped_rows: usize,
1801    ) -> (Datanode, Arc<ObjectStoreLogStore>) {
1802        let config = enqueued_wal_config(flush_interval);
1803        let prefix = config.node_prefix(0, STANDALONE_GENERATION);
1804        let mut request = CreateRequestBuilder::new().build();
1805        request.options.extend(region_wal_options(&config));
1806        let datanode = object_store_wal_builder(data_home, config)
1807            .build()
1808            .await
1809            .unwrap();
1810        let log_store = datanode.object_store_log_store.clone().unwrap();
1811        let region_server = datanode.region_server();
1812        let schema = rows_schema(&request);
1813        region_server
1814            .handle_request(REGION, RegionRequest::Create(request))
1815            .await
1816            .unwrap();
1817
1818        put_rows(&region_server, &schema, 0..durable_rows, false).await;
1819        if durable_rows > 0 {
1820            let provider = Provider::object_store_provider(REGION, prefix);
1821            tokio::time::timeout(WAIT, async {
1822                while log_store.latest_entry_id(&provider).unwrap() == 0 {
1823                    tokio::time::sleep(Duration::from_millis(10)).await;
1824                }
1825            })
1826            .await
1827            .expect("the rows must become durable");
1828        }
1829        if uploads_fail {
1830            log_store.fail_creates();
1831        }
1832        let skipped = durable_rows + rows;
1833        put_rows(&region_server, &schema, durable_rows..skipped, false).await;
1834        put_rows(
1835            &region_server,
1836            &schema,
1837            skipped..skipped + skipped_rows,
1838            true,
1839        )
1840        .await;
1841        (datanode, log_store)
1842    }
1843
1844    /// Restarts a datanode on the files of `data_home`, opens [`REGION`] and
1845    /// returns the number of rows it holds after replaying the WAL.
1846    async fn rows_after_restart(data_home: &str) -> u64 {
1847        let config = enqueued_wal_config(ObjectStoreWalConfig::default().flush_interval);
1848        let options = region_wal_options(&config);
1849        let mut datanode = object_store_wal_builder(data_home, config)
1850            .build()
1851            .await
1852            .unwrap();
1853        let region_server = datanode.region_server();
1854        region_server
1855            .handle_request(
1856                REGION,
1857                RegionRequest::Open(RegionOpenRequest {
1858                    engine: MITO_ENGINE_NAME.to_string(),
1859                    table_dir: CreateRequestBuilder::new().build().table_dir,
1860                    path_type: PathType::Bare,
1861                    options,
1862                    skip_wal_replay: false,
1863                    checkpoint: None,
1864                    requirements: Default::default(),
1865                }),
1866            )
1867            .await
1868            .unwrap();
1869        let rows = region_server.region_statistic(REGION).unwrap().num_rows;
1870        datanode.shutdown().await.unwrap();
1871        rows
1872    }
1873
1874    /// Returns the Parquet files under `dir` and its subdirectories.
1875    fn sst_files(dir: &Path) -> Vec<String> {
1876        let mut files = Vec::new();
1877        for entry in std::fs::read_dir(dir).unwrap() {
1878            let path = entry.unwrap().path();
1879            if path.is_dir() {
1880                files.extend(sst_files(&path));
1881            } else if path.extension().is_some_and(|ext| ext == "parquet") {
1882                files.push(path.display().to_string());
1883            }
1884        }
1885        files
1886    }
1887
1888    /// A flush that waits for WAL uploads that keep failing does not hold up
1889    /// the shutdown: the WAL stops first and drops its backlog, which fails
1890    /// the flush with the stopped error before it writes its manifest edit,
1891    /// and the shutdown reports the dropped backlog. After a restart the rows
1892    /// that were durable before the outage replay once.
1893    #[tokio::test]
1894    async fn test_shutdown_while_a_flush_waits_for_failing_wal_uploads() {
1895        common_telemetry::init_default_ut_logging();
1896        let data_home = create_temp_dir("object-store-wal-shutdown-flush");
1897        let data_home = data_home.path().to_str().unwrap();
1898        let (mut datanode, log_store) = datanode_with_object_store_wal(
1899            data_home,
1900            ObjectStoreWalConfig::default().flush_interval,
1901            3,
1902            true,
1903            2,
1904            0,
1905        )
1906        .await;
1907        let flush = {
1908            let region_server = datanode.region_server();
1909            tokio::spawn(async move {
1910                region_server
1911                    .handle_request(REGION, RegionRequest::Flush(Default::default()))
1912                    .await
1913            })
1914        };
1915        tokio::time::timeout(WAIT, log_store.wait_for_durability_waits(1))
1916            .await
1917            .unwrap()
1918            .unwrap();
1919
1920        let err = tokio::time::timeout(WAIT, datanode.shutdown())
1921            .await
1922            .expect("the shutdown must finish")
1923            .unwrap_err();
1924
1925        assert_matches!(err, Error::ShutdownInstance { .. });
1926        assert!(
1927            format!("{err:?}").contains("injected create failure"),
1928            "{err:?}"
1929        );
1930        let flush_err = flush.await.unwrap().unwrap_err();
1931        assert!(
1932            format!("{flush_err:?}").contains("Object store WAL log store is stopped"),
1933            "{flush_err:?}"
1934        );
1935        assert_eq!(Vec::<String>::new(), sst_files(Path::new(data_home)));
1936        assert!(is_stopped(&log_store).await);
1937        drop(datanode);
1938
1939        assert_eq!(3, rows_after_restart(data_home).await);
1940    }
1941
1942    /// A region that holds writes that skipped the WAL is flushed when it
1943    /// closes. When the WAL's uploads keep failing, the WAL stops first and
1944    /// drops its backlog, so that flush fails at once instead of waiting: the
1945    /// close fails, the writes that skipped the WAL are lost, and the shutdown
1946    /// returns the dropped backlog.
1947    #[tokio::test]
1948    async fn test_shutdown_closes_a_region_after_the_wal_dropped_its_backlog() {
1949        common_telemetry::init_default_ut_logging();
1950        let data_home = create_temp_dir("object-store-wal-shutdown-closing-flush");
1951        let (mut datanode, log_store) = datanode_with_object_store_wal(
1952            data_home.path().to_str().unwrap(),
1953            ObjectStoreWalConfig::default().flush_interval,
1954            0,
1955            true,
1956            3,
1957            2,
1958        )
1959        .await;
1960
1961        let err = tokio::time::timeout(WAIT, datanode.shutdown())
1962            .await
1963            .expect("the shutdown must finish")
1964            .unwrap_err();
1965
1966        assert_matches!(err, Error::ShutdownInstance { .. });
1967        assert!(
1968            format!("{err:?}").contains("injected create failure"),
1969            "{err:?}"
1970        );
1971        assert_eq!(Vec::<String>::new(), sst_files(data_home.path()));
1972        assert!(is_stopped(&log_store).await);
1973    }
1974
1975    /// With a healthy WAL whose seal interval is longer than any test, the
1976    /// close-time flush of a region that holds writes that skipped the WAL
1977    /// commits: the WAL's stop seals and uploads its open batch before the
1978    /// region closes, so the flush finds its entries durable, and every row
1979    /// survives a restart.
1980    #[tokio::test]
1981    async fn test_shutdown_flushes_a_region_while_the_wal_is_healthy() {
1982        common_telemetry::init_default_ut_logging();
1983        let data_home = create_temp_dir("object-store-wal-shutdown-healthy-flush");
1984        let data_home = data_home.path().to_str().unwrap();
1985        let (mut datanode, log_store) =
1986            datanode_with_object_store_wal(data_home, Duration::from_secs(3600), 0, false, 3, 2)
1987                .await;
1988
1989        tokio::time::timeout(WAIT, datanode.shutdown())
1990            .await
1991            .expect("the shutdown must finish")
1992            .unwrap();
1993        assert_eq!(1, sst_files(Path::new(data_home)).len());
1994        assert!(is_stopped(&log_store).await);
1995        drop(datanode);
1996
1997        assert_eq!(5, rows_after_restart(data_home).await);
1998    }
1999
2000    #[test]
2001    fn test_node_prefix_passes_validation() {
2002        let config = ObjectStoreWalConfig::default();
2003        let derived = ObjectStoreWalConfig {
2004            prefix: config.node_prefix(0, STANDALONE_GENERATION),
2005            ..config
2006        };
2007        validate_object_store_wal_config(&derived).unwrap();
2008    }
2009}