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 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
94pub 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 let receiver = self.region_event_receiver.take().unwrap();
132
133 task.start(receiver, self.leases_notifier.clone()).await?;
134 }
135 Ok(())
136 }
137
138 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 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 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 #[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 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 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 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 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 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 if controlled_by_metasrv && let Some(mito) = region_server.mito_engine() {
377 mito.require_write_lease();
378 }
379
380 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 is_recovery_mode,
399 );
400
401 if self.opts.init_regions_in_background {
402 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(®ion_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 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 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 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 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 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 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 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 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 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(), 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 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 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 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 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 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 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
891pub(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
902fn 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
928fn 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
949fn 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
1013async 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 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 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 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 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 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 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 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 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 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 assert!(!data_home.path().join(".greptime_probe").exists());
1573 }
1574
1575 #[tokio::test]
1576 async fn test_object_store_probe_failure_aborts_build() {
1577 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 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 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 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 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 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 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(®ion_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(®ion_server, &schema, durable_rows..skipped, false).await;
1834 put_rows(
1835 ®ion_server,
1836 &schema,
1837 skipped..skipped + skipped_rows,
1838 true,
1839 )
1840 .await;
1841 (datanode, log_store)
1842 }
1843
1844 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 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 #[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 #[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 #[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}