1mod selector;
16pub(crate) mod topic_creator;
17mod topic_manager;
18pub(crate) mod topic_pool;
19
20use std::collections::{BTreeSet, HashMap};
21use std::sync::Arc;
22
23use async_trait::async_trait;
24use common_procedure::Context as ProcedureContext;
25use common_procedure::local::DynamicKeyLockGuard;
26use common_wal::config::MetasrvWalConfig;
27use common_wal::options::{KafkaWalOptions, WAL_OPTIONS_KEY, WalOptions};
28use serde::{Deserialize, Deserializer, Serialize, Serializer};
29use snafu::ensure;
30use store_api::storage::{RegionId, RegionNumber};
31
32use crate::ddl::allocator::wal_options::WalOptionsAllocator;
33use crate::error::{InvalidTopicNamePrefixSnafu, Result};
34use crate::key::topic_name::TopicNameKey;
35use crate::key::{TOPIC_NAME_PATTERN_REGEX, TableMetadataManagerRef};
36use crate::kv_backend::KvBackendRef;
37use crate::leadership_notifier::LeadershipChangeListener;
38use crate::lock_key::RemoteWalLock;
39pub use crate::wal_provider::topic_creator::{build_kafka_client, build_kafka_topic_creator};
40use crate::wal_provider::topic_pool::KafkaTopicPool;
41
42pub type RegionWalOptions = HashMap<RegionNumber, WalOptions>;
44
45pub fn remote_wal_topics(region_wal_options: &RegionWalOptions) -> Vec<&str> {
47 region_wal_options
48 .values()
49 .filter_map(|wal_options| match wal_options {
50 WalOptions::Kafka(kafka_options) => Some(kafka_options.topic.as_str()),
51 _ => None,
52 })
53 .collect::<BTreeSet<_>>()
54 .into_iter()
55 .collect()
56}
57
58pub async fn acquire_remote_wal_read_locks(
60 ctx: &ProcedureContext,
61 region_wal_options: &RegionWalOptions,
62) -> Vec<DynamicKeyLockGuard> {
63 let topics = remote_wal_topics(region_wal_options)
64 .into_iter()
65 .map(str::to_string)
66 .collect::<Vec<_>>();
67 let mut guards = Vec::with_capacity(topics.len());
68 for topic in topics {
69 let guard = ctx
70 .provider
71 .acquire_lock(&(RemoteWalLock::Read(topic).into()))
72 .await;
73 guards.push(guard);
74 }
75 guards
76}
77
78pub async fn refresh_initial_pruned_entry_ids(
80 table_metadata_manager: &TableMetadataManagerRef,
81 region_wal_options: &mut RegionWalOptions,
82) -> Result<()> {
83 let topics = remote_wal_topics(region_wal_options);
84 if topics.is_empty() {
85 return Ok(());
86 }
87
88 let topic_values = table_metadata_manager
89 .topic_name_manager()
90 .batch_get(
91 topics
92 .iter()
93 .map(|topic| TopicNameKey::new(topic))
94 .collect(),
95 )
96 .await?;
97
98 for wal_options in region_wal_options.values_mut() {
99 let WalOptions::Kafka(kafka_options) = wal_options else {
100 continue;
101 };
102 kafka_options.initial_pruned_entry_id = Some(
103 topic_values
104 .get(&kafka_options.topic)
105 .map(|value| value.pruned_entry_id)
106 .unwrap_or_default(),
107 );
108 }
109
110 Ok(())
111}
112
113#[derive(Deserialize)]
114#[serde(untagged)]
115enum WalOptionsCompat {
116 Encoded(String),
117 Structured(WalOptions),
118}
119
120fn deserialize_region_wal_options<E>(
121 values: HashMap<String, WalOptionsCompat>,
122) -> std::result::Result<RegionWalOptions, E>
123where
124 E: serde::de::Error,
125{
126 values
127 .into_iter()
128 .map(|(region_number, wal_options)| {
129 let region_number = region_number.parse::<RegionNumber>().map_err(|err| {
130 E::custom(format!(
131 "invalid region number in region_wal_options: {region_number}, err: {err}"
132 ))
133 })?;
134 let wal_options = match wal_options {
135 WalOptionsCompat::Encoded(encoded) => serde_json::from_str(&encoded).map_err(|err| {
136 E::custom(format!(
137 "failed to decode legacy wal options for region {region_number}: {encoded}, err: {err}"
138 ))
139 })?,
140 WalOptionsCompat::Structured(wal_options) => wal_options,
141 };
142 Ok((region_number, wal_options))
143 })
144 .collect()
145}
146
147fn serialize_region_wal_options<S>(
148 value: &RegionWalOptions,
149 serializer: S,
150) -> std::result::Result<S::Ok, S::Error>
151where
152 S: Serializer,
153{
154 let values = value
155 .iter()
156 .map(|(region_number, wal_options)| {
157 serde_json::to_string(wal_options).map(|encoded| (*region_number, encoded))
158 })
159 .collect::<std::result::Result<HashMap<_, _>, _>>()
160 .map_err(serde::ser::Error::custom)?;
161 values.serialize(serializer)
162}
163
164pub mod region_wal_options_serde {
169 use super::*;
170
171 pub fn serialize<S>(
173 value: &RegionWalOptions,
174 serializer: S,
175 ) -> std::result::Result<S::Ok, S::Error>
176 where
177 S: Serializer,
178 {
179 serialize_region_wal_options(value, serializer)
180 }
181
182 pub fn deserialize<'de, D>(deserializer: D) -> std::result::Result<RegionWalOptions, D::Error>
184 where
185 D: Deserializer<'de>,
186 {
187 let values = HashMap::<String, WalOptionsCompat>::deserialize(deserializer)?;
188 deserialize_region_wal_options(values)
189 }
190}
191
192pub mod optional_region_wal_options_serde {
194 use super::*;
195
196 pub fn serialize<S>(
198 value: &Option<RegionWalOptions>,
199 serializer: S,
200 ) -> std::result::Result<S::Ok, S::Error>
201 where
202 S: Serializer,
203 {
204 match value {
205 Some(value) => serialize_region_wal_options(value, serializer),
206 None => serializer.serialize_none(),
207 }
208 }
209
210 pub fn deserialize<'de, D>(
212 deserializer: D,
213 ) -> std::result::Result<Option<RegionWalOptions>, D::Error>
214 where
215 D: Deserializer<'de>,
216 {
217 let Some(values) = Option::<HashMap<String, WalOptionsCompat>>::deserialize(deserializer)?
218 else {
219 return Ok(None);
220 };
221
222 deserialize_region_wal_options(values).map(Some)
223 }
224}
225
226#[derive(Default, Debug)]
228pub enum WalProvider {
229 #[default]
230 RaftEngine,
231 Kafka(KafkaTopicPool),
232}
233
234pub type WalProviderRef = Arc<WalProvider>;
236
237#[async_trait::async_trait]
238impl WalOptionsAllocator for WalProvider {
239 async fn allocate(
240 &self,
241 region_numbers: &[RegionNumber],
242 skip_wal: bool,
243 ) -> Result<RegionWalOptions> {
244 let wal_options = self.alloc_batch(region_numbers.len(), skip_wal).await?;
245
246 Ok(region_numbers.iter().copied().zip(wal_options).collect())
247 }
248}
249
250impl WalProvider {
251 pub async fn start(&self) -> Result<()> {
253 match self {
254 Self::RaftEngine => Ok(()),
255 Self::Kafka(kafka_topic_manager) => kafka_topic_manager.activate().await,
256 }
257 }
258
259 pub async fn alloc_batch(&self, num_regions: usize, skip_wal: bool) -> Result<Vec<WalOptions>> {
262 if skip_wal {
263 return Ok(vec![WalOptions::Noop; num_regions]);
264 }
265 match self {
266 WalProvider::RaftEngine => Ok(vec![WalOptions::RaftEngine; num_regions]),
267 WalProvider::Kafka(topic_manager) => {
268 let options_batch = topic_manager
269 .select_batch(num_regions)?
270 .into_iter()
271 .map(|topic| WalOptions::Kafka(KafkaWalOptions::new(topic.clone())))
272 .collect();
273 Ok(options_batch)
274 }
275 }
276 }
277
278 pub fn is_remote_wal(&self) -> bool {
280 matches!(&self, WalProvider::Kafka(_))
281 }
282}
283
284#[async_trait]
285impl LeadershipChangeListener for WalProvider {
286 fn name(&self) -> &str {
287 "WalProvider"
288 }
289
290 async fn on_leader_start(&self) -> Result<()> {
291 self.start().await
292 }
293
294 async fn on_leader_stop(&self) -> Result<()> {
295 Ok(())
296 }
297}
298
299pub async fn build_wal_provider(
301 config: &MetasrvWalConfig,
302 kv_backend: KvBackendRef,
303) -> Result<WalProvider> {
304 match config {
305 MetasrvWalConfig::RaftEngine => Ok(WalProvider::RaftEngine),
306 MetasrvWalConfig::Kafka(kafka_config) => {
307 let prefix = &kafka_config.kafka_topic.topic_name_prefix;
308 ensure!(
309 TOPIC_NAME_PATTERN_REGEX.is_match(prefix),
310 InvalidTopicNamePrefixSnafu { prefix }
311 );
312 let topic_creator =
313 build_kafka_topic_creator(&kafka_config.connection, &kafka_config.kafka_topic)
314 .await?;
315 let topic_pool = KafkaTopicPool::new(kafka_config, kv_backend, topic_creator);
316 Ok(WalProvider::Kafka(topic_pool))
317 }
318 }
319}
320
321pub fn serialize_wal_options(
323 options: &mut HashMap<String, String>,
324 region_id: RegionId,
325 region_wal_options: &RegionWalOptions,
326) -> std::result::Result<(), serde_json::Error> {
327 if let Some(wal_options) = region_wal_options.get(®ion_id.region_number()) {
328 let encoded = serde_json::to_string(wal_options)?;
329 options.insert(WAL_OPTIONS_KEY.to_string(), encoded);
330 }
331 Ok(())
332}
333
334pub fn extract_topic_from_wal_options(
336 region_id: RegionId,
337 region_options: &RegionWalOptions,
338) -> Option<String> {
339 region_options
340 .get(®ion_id.region_number())
341 .and_then(|wal_options| match wal_options {
342 WalOptions::Kafka(kafka_wal_option) => Some(kafka_wal_option.topic.clone()),
343 _ => None,
344 })
345}
346
347#[cfg(test)]
348mod tests {
349 use std::assert_matches;
350
351 use common_wal::config::kafka::MetasrvKafkaConfig;
352 use common_wal::config::kafka::common::KafkaTopicConfig;
353 use common_wal::maybe_skip_kafka_integration_test;
354 use common_wal::test_util::get_kafka_endpoints;
355
356 use super::*;
357 use crate::error::Error;
358 use crate::kv_backend::memory::MemoryKvBackend;
359 use crate::test_util::test_kafka_topic_pool;
360 use crate::wal_provider::selector::RoundRobinTopicSelector;
361
362 #[tokio::test]
364 async fn test_provider_with_raft_engine() {
365 let kv_backend = Arc::new(MemoryKvBackend::new()) as KvBackendRef;
366 let wal_config = MetasrvWalConfig::RaftEngine;
367 let provider = build_wal_provider(&wal_config, kv_backend).await.unwrap();
368 provider.start().await.unwrap();
369
370 let num_regions = 32;
371 let regions = (0..num_regions).collect::<Vec<_>>();
372 let got = provider.allocate(®ions, false).await.unwrap();
373
374 let expected = regions
375 .into_iter()
376 .zip(vec![WalOptions::RaftEngine; num_regions as usize])
377 .collect();
378 assert_eq!(got, expected);
379 }
380
381 #[tokio::test]
382 async fn test_refuse_invalid_topic_name_prefix() {
383 let kv_backend = Arc::new(MemoryKvBackend::new()) as KvBackendRef;
384 let wal_config = MetasrvWalConfig::Kafka(MetasrvKafkaConfig {
385 kafka_topic: KafkaTopicConfig {
386 topic_name_prefix: "``````".to_string(),
387 ..Default::default()
388 },
389 ..Default::default()
390 });
391 let got = build_wal_provider(&wal_config, kv_backend)
392 .await
393 .unwrap_err();
394 assert_matches!(got, Error::InvalidTopicNamePrefix { .. });
395 }
396
397 #[tokio::test]
398 async fn test_provider_with_kafka_allocate_wal_options() {
399 common_telemetry::init_default_ut_logging();
400 maybe_skip_kafka_integration_test!();
401 let num_topics = 5;
402 let mut topic_pool = test_kafka_topic_pool(
403 get_kafka_endpoints(),
404 num_topics,
405 true,
406 Some("test_allocator_with_kafka"),
407 )
408 .await;
409 topic_pool.selector = Arc::new(RoundRobinTopicSelector::default());
410 let topics = topic_pool.topics.clone();
411 let topic_creator = topic_pool.topic_creator();
413 topic_creator.delete_topics(&topics).await.unwrap();
414
415 let provider = WalProvider::Kafka(topic_pool);
417 provider.start().await.unwrap();
418
419 let num_regions = 3;
420 let regions = (0..num_regions).collect::<Vec<_>>();
421 let got = provider.allocate(®ions, false).await.unwrap();
422
423 let expected = (0..num_regions)
425 .map(|i| {
426 let options = WalOptions::Kafka(KafkaWalOptions::new(topics[i as usize].clone()));
427 (i, options)
428 })
429 .collect::<HashMap<_, _>>();
430 assert_eq!(got, expected);
431 }
432
433 #[derive(Debug, PartialEq, Serialize, Deserialize)]
434 struct RegionWalOptionsWrapper {
435 #[serde(with = "region_wal_options_serde")]
436 region_wal_options: RegionWalOptions,
437 }
438
439 #[derive(Debug, PartialEq, Serialize, Deserialize)]
440 struct OptionalRegionWalOptionsWrapper {
441 #[serde(with = "optional_region_wal_options_serde")]
442 region_wal_options: Option<RegionWalOptions>,
443 }
444
445 #[test]
446 fn test_deserialize_legacy_region_wal_options_from_encoded_map() {
447 let legacy_region_wal_options = HashMap::from([
448 (1, serde_json::to_string(&WalOptions::RaftEngine).unwrap()),
449 (
450 2,
451 serde_json::to_string(&WalOptions::Kafka(KafkaWalOptions::new(
452 "topic_a".to_string(),
453 )))
454 .unwrap(),
455 ),
456 ]);
457 let legacy_json = serde_json::json!({
458 "region_wal_options": legacy_region_wal_options,
459 });
460
461 assert_eq!(
462 legacy_json.to_string(),
463 r#"{"region_wal_options":{"1":"{\"wal.provider\":\"raft_engine\"}","2":"{\"wal.provider\":\"kafka\",\"wal.kafka.topic\":\"topic_a\"}"}}"#
464 );
465
466 let decoded: RegionWalOptionsWrapper = serde_json::from_value(legacy_json).unwrap();
467
468 assert_eq!(
469 decoded.region_wal_options,
470 HashMap::from([
471 (1, WalOptions::RaftEngine),
472 (
473 2,
474 WalOptions::Kafka(KafkaWalOptions::new("topic_a".to_string())),
475 ),
476 ])
477 );
478 }
479
480 #[test]
481 fn test_deserialize_structured_region_wal_options() {
482 let json = r#"{
483 "region_wal_options": {
484 "1": {"wal.provider":"raft_engine"},
485 "2": {"wal.provider":"noop"}
486 }
487 }"#;
488
489 let decoded: RegionWalOptionsWrapper = serde_json::from_str(json).unwrap();
490
491 assert_eq!(
492 decoded.region_wal_options,
493 HashMap::from([(1, WalOptions::RaftEngine), (2, WalOptions::Noop)])
494 );
495 }
496
497 #[test]
498 fn test_serialize_legacy_region_wal_options() {
499 let wrapper = RegionWalOptionsWrapper {
500 region_wal_options: HashMap::from([(1, WalOptions::RaftEngine)]),
501 };
502
503 let encoded = serde_json::to_string(&wrapper).unwrap();
504
505 assert_eq!(
506 encoded,
507 r#"{"region_wal_options":{"1":"{\"wal.provider\":\"raft_engine\"}"}}"#
508 );
509 }
510
511 #[test]
512 fn test_serialize_optional_region_wal_options() {
513 let wrapper = OptionalRegionWalOptionsWrapper {
514 region_wal_options: Some(HashMap::from([(1, WalOptions::RaftEngine)])),
515 };
516
517 let encoded = serde_json::to_string(&wrapper).unwrap();
518
519 assert_eq!(
520 encoded,
521 r#"{"region_wal_options":{"1":"{\"wal.provider\":\"raft_engine\"}"}}"#
522 );
523 assert_eq!(
524 serde_json::from_str::<OptionalRegionWalOptionsWrapper>(&encoded).unwrap(),
525 wrapper
526 );
527
528 let none = OptionalRegionWalOptionsWrapper {
529 region_wal_options: None,
530 };
531 assert_eq!(
532 serde_json::to_string(&none).unwrap(),
533 r#"{"region_wal_options":null}"#
534 );
535 }
536
537 #[tokio::test]
538 async fn test_provider_with_skip_wal() {
539 let provider = WalProvider::RaftEngine;
540 provider.start().await.unwrap();
541
542 let num_regions = 32;
543 let regions = (0..num_regions).collect::<Vec<_>>();
544 let got = provider.allocate(®ions, true).await.unwrap();
545 assert_eq!(got.len(), num_regions as usize);
546 for wal_options in got.values() {
547 assert_eq!(wal_options, &WalOptions::Noop);
548 }
549 }
550}