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