Skip to main content

metric_engine/
metadata_region.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::hash_map::Entry;
16use std::collections::{BTreeMap, HashMap};
17use std::sync::{Arc, Mutex, Weak};
18use std::time::Duration;
19
20use api::v1::helper::row;
21use api::v1::value::ValueData;
22use api::v1::{ColumnDataType, ColumnSchema, Rows, SemanticType};
23use async_stream::try_stream;
24use base64::Engine;
25use base64::engine::general_purpose::STANDARD_NO_PAD;
26use common_base::readable_size::ReadableSize;
27use common_recordbatch::{RecordBatch, SendableRecordBatchStream};
28use common_telemetry::{debug, info, warn};
29use datafusion::prelude::{col, lit};
30use futures_util::TryStreamExt;
31use futures_util::stream::BoxStream;
32use mito2::engine::MitoEngine;
33use moka::future::Cache;
34use moka::policy::EvictionPolicy;
35use snafu::{OptionExt, ResultExt};
36use store_api::metadata::ColumnMetadata;
37use store_api::metric_engine_consts::{
38    METADATA_SCHEMA_KEY_COLUMN_INDEX, METADATA_SCHEMA_KEY_COLUMN_NAME,
39    METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME, METADATA_SCHEMA_VALUE_COLUMN_INDEX,
40    METADATA_SCHEMA_VALUE_COLUMN_NAME,
41};
42use store_api::region_engine::RegionEngine;
43use store_api::region_request::{RegionDeleteRequest, RegionPutRequest, RegionRequest};
44use store_api::storage::{RegionId, ScanRequest};
45use tokio::sync::{
46    OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard,
47};
48
49use crate::error::{
50    CacheGetSnafu, CollectRecordBatchStreamSnafu, DecodeColumnValueSnafu,
51    DeserializeColumnMetadataSnafu, LogicalRegionNotFoundSnafu, MitoReadOperationSnafu,
52    MitoWriteOperationSnafu, ParseRegionIdSnafu, Result,
53};
54use crate::utils;
55
56const REGION_PREFIX: &str = "__region_";
57const COLUMN_PREFIX: &str = "__column_";
58
59/// The other two fields key and value will be used as a k-v storage.
60/// It contains two group of key:
61/// - `__region_<LOGICAL_REGION_ID>` is used for marking table existence. It doesn't have value.
62/// - `__column_<LOGICAL_REGION_ID>_<COLUMN_NAME>` is used for marking column existence,
63///   the value is column's semantic type. To avoid the key conflict, this column key
64///   will be encoded by base64([STANDARD_NO_PAD]).
65///
66/// This is a generic handler like [MetricEngine](crate::engine::MetricEngine). It
67/// will handle all the metadata related operations across physical tables. Thus
68/// every operation should be associated to a [RegionId], which is the physical
69/// table id + region sequence. This handler will transform the region group by
70/// itself.
71pub struct MetadataRegion {
72    pub(crate) mito: MitoEngine,
73    /// The cache for contents(key-value pairs) of region metadata.
74    ///
75    /// The cache should be invalidated when any new values are put into the metadata region or any
76    /// values are deleted from the metadata region.
77    cache: Cache<RegionId, RegionMetadataCacheEntry>,
78    /// Serializes cache fills with metadata writes and invalidation per metadata region.
79    ///
80    /// Holds weak references only; strong references live in [`CacheAccessLockLease`]s
81    /// returned by [`Self::cache_access_lock`]. The last lease dropped for a region
82    /// removes its entry, so the map self-cleans on success, error, or cancellation
83    /// without coupling cleanup to region drop.
84    cache_access_locks: CacheAccessLockRegistry,
85    /// Logical lock for operations that need to be serialized. Like update & read region columns.
86    ///
87    /// Region entry will be registered on creating and opening logical region, and deregistered on
88    /// removing logical region.
89    logical_region_lock: RwLock<HashMap<RegionId, Arc<RwLock<()>>>>,
90}
91
92#[derive(Clone)]
93struct RegionMetadataCacheEntry {
94    key_values: Arc<BTreeMap<String, String>>,
95    size: usize,
96}
97
98/// Weak index of per-region cache access locks.
99///
100/// The mutex is never held across `.await`.
101type CacheAccessLockRegistry = Arc<Mutex<HashMap<RegionId, Weak<RwLock<()>>>>>;
102
103/// Lease that keeps a per-region cache access lock alive.
104///
105/// Dropping the last lease for a region removes the matching registry entry.
106struct CacheAccessLockLease {
107    registry: CacheAccessLockRegistry,
108    region_id: RegionId,
109    /// Always `Some` while the lease is alive.
110    ///
111    /// `Option` lets `Drop` release the strong `Arc` before pruning the weak entry.
112    lock: Option<Arc<RwLock<()>>>,
113}
114
115impl CacheAccessLockLease {
116    async fn read(&self) -> RwLockReadGuard<'_, ()> {
117        self.lock().read().await
118    }
119
120    async fn write(&self) -> RwLockWriteGuard<'_, ()> {
121        self.lock().write().await
122    }
123
124    fn lock(&self) -> &RwLock<()> {
125        self.lock
126            .as_deref()
127            // Safety: `lock` is initialized when the lease is created and is taken only
128            // by `Drop`. `read` and `write` borrow `self`, so `Drop` cannot run while
129            // this reference is in use.
130            .expect("cache access lock lease must hold a lock")
131    }
132}
133
134impl Drop for CacheAccessLockLease {
135    fn drop(&mut self) {
136        let Some(lock) = self.lock.take() else {
137            return;
138        };
139        let weak = Arc::downgrade(&lock);
140        // Release this lease before pruning; concurrent drops must not observe
141        // each other's strong refs.
142        drop(lock);
143
144        let mut registry = self
145            .registry
146            .lock()
147            .unwrap_or_else(|poisoned| poisoned.into_inner());
148        let Some(current) = registry.get(&self.region_id) else {
149            return;
150        };
151        // `ptr_eq` protects a newer lock for the same region; `upgrade` ensures it is dead.
152        if current.ptr_eq(&weak) && current.upgrade().is_none() {
153            registry.remove(&self.region_id);
154        }
155    }
156}
157
158/// The max size of the region metadata cache.
159const MAX_CACHE_SIZE: u64 = ReadableSize::mb(128).as_bytes();
160/// The TTL of the region metadata cache.
161const CACHE_TTL: Duration = Duration::from_secs(5 * 60);
162
163impl MetadataRegion {
164    pub fn new(mito: MitoEngine) -> Self {
165        let cache = Cache::builder()
166            .max_capacity(MAX_CACHE_SIZE)
167            // Use the LRU eviction policy to minimize frequent mito scans.
168            // Recently accessed items are retained longer in the cache.
169            .eviction_policy(EvictionPolicy::lru())
170            .time_to_live(CACHE_TTL)
171            .weigher(|_, v: &RegionMetadataCacheEntry| v.size as u32)
172            .build();
173        Self {
174            mito,
175            cache,
176            cache_access_locks: CacheAccessLockRegistry::default(),
177            logical_region_lock: RwLock::new(HashMap::new()),
178        }
179    }
180
181    /// Open a logical region.
182    ///
183    /// Returns true if the logical region is opened for the first time.
184    pub async fn open_logical_region(&self, logical_region_id: RegionId) -> bool {
185        match self
186            .logical_region_lock
187            .write()
188            .await
189            .entry(logical_region_id)
190        {
191            Entry::Occupied(_) => false,
192            Entry::Vacant(vacant_entry) => {
193                vacant_entry.insert(Arc::new(RwLock::new(())));
194                true
195            }
196        }
197    }
198
199    /// Retrieve a read lock guard of given logical region id.
200    pub async fn read_lock_logical_region(
201        &self,
202        logical_region_id: RegionId,
203    ) -> Result<OwnedRwLockReadGuard<()>> {
204        let lock = self
205            .logical_region_lock
206            .read()
207            .await
208            .get(&logical_region_id)
209            .context(LogicalRegionNotFoundSnafu {
210                region_id: logical_region_id,
211            })?
212            .clone();
213        Ok(RwLock::read_owned(lock).await)
214    }
215
216    /// Retrieve a write lock guard of given logical region id.
217    pub async fn write_lock_logical_region(
218        &self,
219        logical_region_id: RegionId,
220    ) -> Result<OwnedRwLockWriteGuard<()>> {
221        let lock = self
222            .logical_region_lock
223            .read()
224            .await
225            .get(&logical_region_id)
226            .context(LogicalRegionNotFoundSnafu {
227                region_id: logical_region_id,
228            })?
229            .clone();
230        Ok(RwLock::write_owned(lock).await)
231    }
232
233    /// Remove a registered logical region from metadata.
234    ///
235    /// This method doesn't check if the previous key exists.
236    pub async fn remove_logical_region(
237        &self,
238        physical_region_id: RegionId,
239        logical_region_id: RegionId,
240    ) -> Result<()> {
241        // concat region key
242        let region_id = utils::to_metadata_region_id(physical_region_id);
243        let region_key = Self::concat_region_key(logical_region_id);
244
245        // concat column keys
246        let logical_columns = self
247            .logical_columns(physical_region_id, logical_region_id)
248            .await?;
249        let mut column_keys = logical_columns
250            .into_iter()
251            .map(|(col, _)| Self::concat_column_key(logical_region_id, &col))
252            .collect::<Vec<_>>();
253
254        // remove region key and column keys
255        column_keys.push(region_key);
256        self.delete(region_id, &column_keys).await?;
257
258        self.logical_region_lock
259            .write()
260            .await
261            .remove(&logical_region_id);
262
263        Ok(())
264    }
265
266    // TODO(ruihang): avoid using `get_all`
267    /// Get all the columns of a given logical region.
268    /// Return a list of (column_name, column_metadata).
269    pub async fn logical_columns(
270        &self,
271        physical_region_id: RegionId,
272        logical_region_id: RegionId,
273    ) -> Result<Vec<(String, ColumnMetadata)>> {
274        let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
275        let region_column_prefix = Self::concat_column_key_prefix(logical_region_id);
276
277        let mut columns = vec![];
278        for (k, v) in self
279            .get_all_with_prefix(metadata_region_id, &region_column_prefix)
280            .await?
281        {
282            if !k.starts_with(&region_column_prefix) {
283                continue;
284            }
285            // Safety: we have checked the prefix
286            let (_, column_name) = Self::parse_column_key(&k)?.unwrap();
287            let column_metadata = Self::deserialize_column_metadata(&v)?;
288            columns.push((column_name, column_metadata));
289        }
290
291        Ok(columns)
292    }
293
294    /// Return all logical regions associated with the physical region.
295    pub async fn logical_regions(&self, physical_region_id: RegionId) -> Result<Vec<RegionId>> {
296        let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
297
298        let mut regions = vec![];
299        for k in self
300            .get_all_key_with_prefix(metadata_region_id, REGION_PREFIX)
301            .await?
302        {
303            if !k.starts_with(REGION_PREFIX) {
304                continue;
305            }
306            // Safety: we have checked the prefix
307            let region_id = Self::parse_region_key(&k).unwrap();
308            let region_id = region_id.parse::<u64>().unwrap().into();
309            regions.push(region_id);
310        }
311
312        Ok(regions)
313    }
314}
315
316// utils to concat and parse key/value
317impl MetadataRegion {
318    pub fn concat_region_key(region_id: RegionId) -> String {
319        format!("{REGION_PREFIX}{}", region_id.as_u64())
320    }
321
322    /// Column name will be encoded by base64([STANDARD_NO_PAD])
323    pub fn concat_column_key(region_id: RegionId, column_name: &str) -> String {
324        let encoded_column_name = STANDARD_NO_PAD.encode(column_name);
325        format!(
326            "{COLUMN_PREFIX}{}_{}",
327            region_id.as_u64(),
328            encoded_column_name
329        )
330    }
331
332    /// Concat a column key prefix without column name
333    pub fn concat_column_key_prefix(region_id: RegionId) -> String {
334        format!("{COLUMN_PREFIX}{}_", region_id.as_u64())
335    }
336
337    pub fn parse_region_key(key: &str) -> Option<&str> {
338        key.strip_prefix(REGION_PREFIX)
339    }
340
341    /// Parse column key to (logical_region_id, column_name)
342    pub fn parse_column_key(key: &str) -> Result<Option<(RegionId, String)>> {
343        if let Some(stripped) = key.strip_prefix(COLUMN_PREFIX) {
344            let mut iter = stripped.split('_');
345
346            let region_id_raw = iter.next().unwrap();
347            let region_id = region_id_raw
348                .parse::<u64>()
349                .with_context(|_| ParseRegionIdSnafu { raw: region_id_raw })?
350                .into();
351
352            let encoded_column_name = iter.next().unwrap();
353            let column_name = STANDARD_NO_PAD
354                .decode(encoded_column_name)
355                .context(DecodeColumnValueSnafu)?;
356
357            Ok(Some((region_id, String::from_utf8(column_name).unwrap())))
358        } else {
359            Ok(None)
360        }
361    }
362
363    pub fn serialize_column_metadata(column_metadata: &ColumnMetadata) -> String {
364        serde_json::to_string(column_metadata).unwrap()
365    }
366
367    pub fn deserialize_column_metadata(column_metadata: &str) -> Result<ColumnMetadata> {
368        serde_json::from_str(column_metadata).with_context(|_| DeserializeColumnMetadataSnafu {
369            raw: column_metadata,
370        })
371    }
372}
373
374/// Decode a record batch stream to a stream of items.
375pub fn decode_batch_stream<T: Send + 'static>(
376    mut record_batch_stream: SendableRecordBatchStream,
377    decode: fn(RecordBatch) -> Vec<T>,
378) -> BoxStream<'static, Result<T>> {
379    let stream = try_stream! {
380        while let Some(batch) = record_batch_stream.try_next().await.context(CollectRecordBatchStreamSnafu)? {
381            for item in decode(batch) {
382                yield item;
383            }
384        }
385    };
386    Box::pin(stream)
387}
388
389/// Decode a record batch to a list of key and value.
390fn decode_record_batch_to_key_and_value(batch: RecordBatch) -> Vec<(String, String)> {
391    let keys = batch.iter_column_as_string(0);
392    let values = batch.iter_column_as_string(1);
393    keys.zip(values)
394        .filter_map(|(k, v)| match (k, v) {
395            (Some(k), Some(v)) => Some((k, v)),
396            (Some(k), None) => Some((k, "".to_string())),
397            (None, _) => None,
398        })
399        .collect::<Vec<_>>()
400}
401
402/// Decode a record batch to a list of key.
403fn decode_record_batch_to_key(batch: RecordBatch) -> Vec<String> {
404    batch.iter_column_as_string(0).flatten().collect::<Vec<_>>()
405}
406
407// simulate to `KvBackend`
408//
409// methods in this block assume the given region id is transformed.
410impl MetadataRegion {
411    fn build_prefix_read_request(prefix: &str, key_only: bool) -> ScanRequest {
412        let filter_expr = col(METADATA_SCHEMA_KEY_COLUMN_NAME).like(lit(prefix));
413
414        let projection = if key_only {
415            vec![METADATA_SCHEMA_KEY_COLUMN_INDEX]
416        } else {
417            vec![
418                METADATA_SCHEMA_KEY_COLUMN_INDEX,
419                METADATA_SCHEMA_VALUE_COLUMN_INDEX,
420            ]
421        };
422        ScanRequest {
423            projection: Some(projection),
424            filters: vec![filter_expr],
425            ..Default::default()
426        }
427    }
428
429    fn build_read_request() -> ScanRequest {
430        let projection = vec![
431            METADATA_SCHEMA_KEY_COLUMN_INDEX,
432            METADATA_SCHEMA_VALUE_COLUMN_INDEX,
433        ];
434        ScanRequest {
435            projection: Some(projection),
436            ..Default::default()
437        }
438    }
439
440    async fn load_all(&self, metadata_region_id: RegionId) -> Result<RegionMetadataCacheEntry> {
441        let scan_req = MetadataRegion::build_read_request();
442        let record_batch_stream = self
443            .mito
444            .scan_to_stream(metadata_region_id, scan_req)
445            .await
446            .context(MitoReadOperationSnafu)?;
447
448        let kv = decode_batch_stream(record_batch_stream, decode_record_batch_to_key_and_value)
449            .try_collect::<BTreeMap<_, _>>()
450            .await?;
451        let mut size = 0;
452        for (k, v) in kv.iter() {
453            size += k.len();
454            size += v.len();
455        }
456        let kv = Arc::new(kv);
457        Ok(RegionMetadataCacheEntry {
458            key_values: kv,
459            size,
460        })
461    }
462
463    /// Acquires the cache access lock lease for `metadata_region_id`.
464    ///
465    /// The lease must be kept alive for as long as any guard taken from it.
466    fn cache_access_lock(&self, metadata_region_id: RegionId) -> CacheAccessLockLease {
467        let mut registry = self
468            .cache_access_locks
469            .lock()
470            .unwrap_or_else(|poisoned| poisoned.into_inner());
471        let lock = match registry.get(&metadata_region_id).and_then(Weak::upgrade) {
472            Some(lock) => lock,
473            None => {
474                let lock = Arc::new(RwLock::new(()));
475                registry.insert(metadata_region_id, Arc::downgrade(&lock));
476                lock
477            }
478        };
479
480        CacheAccessLockLease {
481            registry: Arc::clone(&self.cache_access_locks),
482            region_id: metadata_region_id,
483            lock: Some(lock),
484        }
485    }
486
487    async fn get_all_with_prefix(
488        &self,
489        metadata_region_id: RegionId,
490        prefix: &str,
491    ) -> Result<HashMap<String, String>> {
492        let cache_access_lock = self.cache_access_lock(metadata_region_id);
493        let _cache_guard = cache_access_lock.read().await;
494        let region_metadata = self
495            .cache
496            .try_get_with(metadata_region_id, self.load_all(metadata_region_id))
497            .await
498            .context(CacheGetSnafu)?;
499
500        let mut result = HashMap::new();
501        get_all_with_prefix(&region_metadata, prefix, |k, v| {
502            result.insert(k.to_string(), v.to_string());
503            Ok(())
504        })?;
505        Ok(result)
506    }
507
508    pub async fn get_all_key_with_prefix(
509        &self,
510        region_id: RegionId,
511        prefix: &str,
512    ) -> Result<Vec<String>> {
513        let scan_req = MetadataRegion::build_prefix_read_request(prefix, true);
514        let record_batch_stream = self
515            .mito
516            .scan_to_stream(region_id, scan_req)
517            .await
518            .context(MitoReadOperationSnafu)?;
519
520        decode_batch_stream(record_batch_stream, decode_record_batch_to_key)
521            .try_collect::<Vec<_>>()
522            .await
523    }
524
525    /// Delete the given keys. For performance consideration, this method
526    /// doesn't check if those keys exist or not.
527    async fn delete(&self, metadata_region_id: RegionId, keys: &[String]) -> Result<()> {
528        let delete_request = Self::build_delete_request(keys);
529        self.write_metadata(metadata_region_id, RegionRequest::Delete(delete_request))
530            .await
531    }
532
533    /// Writes metadata and invalidates the corresponding cache entry.
534    async fn write_metadata(
535        &self,
536        metadata_region_id: RegionId,
537        request: RegionRequest,
538    ) -> Result<()> {
539        let cache_access_lock = self.cache_access_lock(metadata_region_id);
540        let _cache_guard = cache_access_lock.write().await;
541        let result = self
542            .mito
543            .handle_request(metadata_region_id, request)
544            .await
545            .context(MitoWriteOperationSnafu);
546        // A failed write may still be applied, e.g. when the region lease expires after the
547        // WAL append.
548        self.cache.invalidate(&metadata_region_id).await;
549        result?;
550
551        Ok(())
552    }
553
554    pub(crate) fn build_put_request_from_iter(
555        kv: impl Iterator<Item = (String, String)>,
556    ) -> RegionPutRequest {
557        let cols = vec![
558            ColumnSchema {
559                column_name: METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME.to_string(),
560                datatype: ColumnDataType::TimestampMillisecond as _,
561                semantic_type: SemanticType::Timestamp as _,
562                ..Default::default()
563            },
564            ColumnSchema {
565                column_name: METADATA_SCHEMA_KEY_COLUMN_NAME.to_string(),
566                datatype: ColumnDataType::String as _,
567                semantic_type: SemanticType::Tag as _,
568                ..Default::default()
569            },
570            ColumnSchema {
571                column_name: METADATA_SCHEMA_VALUE_COLUMN_NAME.to_string(),
572                datatype: ColumnDataType::String as _,
573                semantic_type: SemanticType::Field as _,
574                ..Default::default()
575            },
576        ];
577        let rows = Rows {
578            schema: cols,
579            rows: kv
580                .into_iter()
581                .map(|(key, value)| {
582                    row(vec![
583                        ValueData::TimestampMillisecondValue(0),
584                        ValueData::StringValue(key),
585                        ValueData::StringValue(value),
586                    ])
587                })
588                .collect(),
589        };
590
591        RegionPutRequest {
592            // Metadata must remain recoverable regardless of the user write policy.
593            skip_wal: false,
594            rows,
595            hint: None,
596            partition_expr_version: None,
597        }
598    }
599
600    fn build_delete_request(keys: &[String]) -> RegionDeleteRequest {
601        let cols = vec![
602            ColumnSchema {
603                column_name: METADATA_SCHEMA_TIMESTAMP_COLUMN_NAME.to_string(),
604                datatype: ColumnDataType::TimestampMillisecond as _,
605                semantic_type: SemanticType::Timestamp as _,
606                ..Default::default()
607            },
608            ColumnSchema {
609                column_name: METADATA_SCHEMA_KEY_COLUMN_NAME.to_string(),
610                datatype: ColumnDataType::String as _,
611                semantic_type: SemanticType::Tag as _,
612                ..Default::default()
613            },
614        ];
615        let rows = keys
616            .iter()
617            .map(|key| {
618                row(vec![
619                    ValueData::TimestampMillisecondValue(0),
620                    ValueData::StringValue(key.clone()),
621                ])
622            })
623            .collect();
624        let rows = Rows { schema: cols, rows };
625
626        RegionDeleteRequest {
627            rows,
628            hint: None,
629            partition_expr_version: None,
630        }
631    }
632
633    /// Add logical regions to the metadata region.
634    pub async fn add_logical_regions(
635        &self,
636        physical_region_id: RegionId,
637        write_region_id: bool,
638        logical_regions: impl Iterator<Item = (RegionId, HashMap<&str, &ColumnMetadata>)>,
639    ) -> Result<()> {
640        let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
641        let iter = logical_regions
642            .into_iter()
643            .flat_map(|(logical_region_id, column_metadatas)| {
644                if write_region_id {
645                    Some((
646                        MetadataRegion::concat_region_key(logical_region_id),
647                        String::new(),
648                    ))
649                } else {
650                    None
651                }
652                .into_iter()
653                .chain(column_metadatas.into_iter().map(
654                    move |(name, column_metadata)| {
655                        (
656                            MetadataRegion::concat_column_key(logical_region_id, name),
657                            MetadataRegion::serialize_column_metadata(column_metadata),
658                        )
659                    },
660                ))
661            })
662            .collect::<Vec<_>>();
663
664        let put_request = MetadataRegion::build_put_request_from_iter(iter.into_iter());
665        self.write_metadata(metadata_region_id, RegionRequest::Put(put_request))
666            .await
667    }
668
669    /// Updates logical region metadata so that any entries previously referencing
670    /// `source_region_id` are modified to reference the data region of `physical_region_id`.
671    ///
672    /// This method should be called after copying files from `source_region_id`
673    /// into the target region. It scans the metadata for the target physical
674    /// region, finds logical regions with the same region number as the source,
675    /// and reinserts region and column entries updated to use the target's
676    /// region number.
677    pub async fn transform_logical_region_metadata(
678        &self,
679        physical_region_id: RegionId,
680        source_region_id: RegionId,
681    ) -> Result<()> {
682        let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
683        let data_region_id = utils::to_data_region_id(physical_region_id);
684        let logical_regions = self
685            .logical_regions(data_region_id)
686            .await?
687            .into_iter()
688            .filter(|r| r.region_number() == source_region_id.region_number())
689            .collect::<Vec<_>>();
690        if logical_regions.is_empty() {
691            info!(
692                "No logical regions found from source region {}, physical region id: {}",
693                source_region_id, physical_region_id,
694            );
695            return Ok(());
696        }
697
698        let metadata = self.load_all(metadata_region_id).await?;
699        let mut output = Vec::new();
700        for logical_region_id in &logical_regions {
701            let prefix = MetadataRegion::concat_column_key_prefix(*logical_region_id);
702            get_all_with_prefix(&metadata, &prefix, |k, v| {
703                // Safety: we have checked the prefix
704                let (src_logical_region_id, column_name) = Self::parse_column_key(k)?.unwrap();
705                // Change the region number to the data region number.
706                let new_key = MetadataRegion::concat_column_key(
707                    RegionId::new(
708                        src_logical_region_id.table_id(),
709                        data_region_id.region_number(),
710                    ),
711                    &column_name,
712                );
713                output.push((new_key, v.to_string()));
714                Ok(())
715            })?;
716
717            let new_key = MetadataRegion::concat_region_key(RegionId::new(
718                logical_region_id.table_id(),
719                data_region_id.region_number(),
720            ));
721            output.push((new_key, String::new()));
722        }
723
724        if output.is_empty() {
725            warn!(
726                "No logical regions metadata found from source region {}, physical region id: {}",
727                source_region_id, physical_region_id
728            );
729            return Ok(());
730        }
731
732        debug!(
733            "Transform logical regions metadata to physical region {}, source region: {}, transformed metadata: {}",
734            data_region_id,
735            source_region_id,
736            output.len(),
737        );
738
739        let put_request = MetadataRegion::build_put_request_from_iter(output.into_iter());
740        self.write_metadata(metadata_region_id, RegionRequest::Put(put_request))
741            .await?;
742        info!(
743            "Transformed {} logical regions metadata to physical region {}, source region: {}",
744            logical_regions.len(),
745            data_region_id,
746            source_region_id
747        );
748        Ok(())
749    }
750}
751
752fn get_all_with_prefix(
753    region_metadata: &RegionMetadataCacheEntry,
754    prefix: &str,
755    mut callback: impl FnMut(&str, &str) -> Result<()>,
756) -> Result<()> {
757    let range = region_metadata.key_values.range(prefix.to_string()..);
758    for (k, v) in range {
759        if !k.starts_with(prefix) {
760            break;
761        }
762        callback(k, v)?;
763    }
764    Ok(())
765}
766
767#[cfg(test)]
768impl MetadataRegion {
769    /// Retrieves the value associated with the given key in the specified region.
770    /// Returns `Ok(None)` if the key is not found.
771    pub async fn get(&self, region_id: RegionId, key: &str) -> Result<Option<String>> {
772        use datatypes::arrow::array::{Array, AsArray};
773
774        let filter_expr = datafusion::prelude::col(METADATA_SCHEMA_KEY_COLUMN_NAME)
775            .eq(datafusion::prelude::lit(key));
776
777        let projection = Some(vec![METADATA_SCHEMA_VALUE_COLUMN_INDEX]);
778        let scan_req = ScanRequest {
779            projection,
780            filters: vec![filter_expr],
781            ..Default::default()
782        };
783        let record_batch_stream = self
784            .mito
785            .scan_to_stream(region_id, scan_req)
786            .await
787            .context(MitoReadOperationSnafu)?;
788        let scan_result = common_recordbatch::util::collect(record_batch_stream)
789            .await
790            .context(CollectRecordBatchStreamSnafu)?;
791
792        let Some(first_batch) = scan_result.first() else {
793            return Ok(None);
794        };
795
796        let column = first_batch.column(0);
797        let column = column.as_string::<i32>();
798        let val = column.is_valid(0).then(|| column.value(0).to_string());
799
800        Ok(val)
801    }
802
803    /// Check if the given column exists. Return the semantic type if exists.
804    pub async fn column_semantic_type(
805        &self,
806        physical_region_id: RegionId,
807        logical_region_id: RegionId,
808        column_name: &str,
809    ) -> Result<Option<SemanticType>> {
810        let region_id = utils::to_metadata_region_id(physical_region_id);
811        let column_key = Self::concat_column_key(logical_region_id, column_name);
812        let semantic_type = self.get(region_id, &column_key).await?;
813        semantic_type
814            .map(|s| Self::deserialize_column_metadata(&s).map(|c| c.semantic_type))
815            .transpose()
816    }
817}
818
819#[cfg(test)]
820mod test {
821    use datatypes::data_type::ConcreteDataType;
822    use datatypes::schema::ColumnSchema;
823
824    use super::*;
825    use crate::test_util::TestEnv;
826    use crate::utils::to_metadata_region_id;
827
828    #[test]
829    fn test_metadata_put_always_writes_wal() {
830        // Both metadata put paths use this constructor rather than forwarding
831        // a user insert request, so its WAL policy must always be independent.
832        for entries in [
833            vec![],
834            vec![("region", "")],
835            vec![("region", ""), ("column", "metadata")],
836        ] {
837            let request = MetadataRegion::build_put_request_from_iter(
838                entries
839                    .iter()
840                    .map(|(key, value)| (key.to_string(), value.to_string())),
841            );
842            assert!(!request.skip_wal);
843            assert!(request.hint.is_none());
844            assert!(request.partition_expr_version.is_none());
845            let expected_rows = entries
846                .into_iter()
847                .map(|(key, value)| {
848                    row(vec![
849                        ValueData::TimestampMillisecondValue(0),
850                        ValueData::StringValue(key.to_string()),
851                        ValueData::StringValue(value.to_string()),
852                    ])
853                })
854                .collect::<Vec<_>>();
855            assert_eq!(request.rows.rows, expected_rows);
856        }
857    }
858
859    #[test]
860    fn test_concat_table_key() {
861        let region_id = RegionId::new(1234, 7844);
862        let expected = "__region_5299989651108".to_string();
863        assert_eq!(MetadataRegion::concat_region_key(region_id), expected);
864    }
865
866    #[test]
867    fn test_concat_column_key() {
868        let region_id = RegionId::new(8489, 9184);
869        let column_name = "my_column";
870        let expected = "__column_36459977384928_bXlfY29sdW1u".to_string();
871        assert_eq!(
872            MetadataRegion::concat_column_key(region_id, column_name),
873            expected
874        );
875    }
876
877    #[test]
878    fn test_parse_table_key() {
879        let region_id = RegionId::new(87474, 10607);
880        let encoded = MetadataRegion::concat_column_key(region_id, "my_column");
881        assert_eq!(encoded, "__column_375697969260911_bXlfY29sdW1u");
882
883        let decoded = MetadataRegion::parse_column_key(&encoded).unwrap();
884        assert_eq!(decoded, Some((region_id, "my_column".to_string())));
885    }
886
887    #[test]
888    fn test_parse_valid_column_key() {
889        let region_id = RegionId::new(176, 910);
890        let encoded = MetadataRegion::concat_column_key(region_id, "my_column");
891        assert_eq!(encoded, "__column_755914245006_bXlfY29sdW1u");
892
893        let decoded = MetadataRegion::parse_column_key(&encoded).unwrap();
894        assert_eq!(decoded, Some((region_id, "my_column".to_string())));
895    }
896
897    #[test]
898    fn test_parse_invalid_column_key() {
899        let key = "__column_asdfasd_????";
900        let result = MetadataRegion::parse_column_key(key);
901        assert!(result.is_err());
902    }
903
904    #[test]
905    fn test_serialize_column_metadata() {
906        let semantic_type = SemanticType::Tag;
907        let column_metadata = ColumnMetadata {
908            column_schema: ColumnSchema::new("blabla", ConcreteDataType::string_datatype(), false),
909            semantic_type,
910            column_id: 5,
911        };
912        let old_fmt = "{\"column_schema\":{\"name\":\"blabla\",\"data_type\":{\"String\":null},\"is_nullable\":false,\"is_time_index\":false,\"default_constraint\":null,\"metadata\":{}},\"semantic_type\":\"Tag\",\"column_id\":5}".to_string();
913        let new_fmt = "{\"column_schema\":{\"name\":\"blabla\",\"data_type\":{\"String\":{\"size_type\":\"Utf8\"}},\"is_nullable\":false,\"is_time_index\":false,\"default_constraint\":null,\"metadata\":{}},\"semantic_type\":\"Tag\",\"column_id\":5}".to_string();
914        assert_eq!(
915            MetadataRegion::serialize_column_metadata(&column_metadata),
916            new_fmt
917        );
918        // Ensure both old and new formats can be deserialized.
919        assert_eq!(
920            MetadataRegion::deserialize_column_metadata(&old_fmt).unwrap(),
921            column_metadata
922        );
923        assert_eq!(
924            MetadataRegion::deserialize_column_metadata(&new_fmt).unwrap(),
925            column_metadata
926        );
927
928        let semantic_type = "\"Invalid Column Metadata\"";
929        assert!(MetadataRegion::deserialize_column_metadata(semantic_type).is_err());
930    }
931
932    fn test_column_metadatas() -> HashMap<String, ColumnMetadata> {
933        HashMap::from([
934            (
935                "label1".to_string(),
936                ColumnMetadata {
937                    column_schema: ColumnSchema::new(
938                        "label1".to_string(),
939                        ConcreteDataType::string_datatype(),
940                        false,
941                    ),
942                    semantic_type: SemanticType::Tag,
943                    column_id: 5,
944                },
945            ),
946            (
947                "label2".to_string(),
948                ColumnMetadata {
949                    column_schema: ColumnSchema::new(
950                        "label2".to_string(),
951                        ConcreteDataType::string_datatype(),
952                        false,
953                    ),
954                    semantic_type: SemanticType::Tag,
955                    column_id: 5,
956                },
957            ),
958        ])
959    }
960
961    async fn add_test_logical_region(
962        metadata_region: &MetadataRegion,
963        physical_region_id: RegionId,
964        logical_region_id: RegionId,
965    ) -> Result<()> {
966        let column_metadatas = test_column_metadatas();
967        let logical_regions = std::iter::once((
968            logical_region_id,
969            column_metadatas
970                .iter()
971                .map(|(name, metadata)| (name.as_str(), metadata))
972                .collect::<HashMap<_, _>>(),
973        ));
974        metadata_region
975            .add_logical_regions(physical_region_id, true, logical_regions)
976            .await
977    }
978
979    #[tokio::test]
980    async fn add_logical_regions_to_meta_region() {
981        let env = TestEnv::new().await;
982        env.init_metric_region().await;
983        let metadata_region = env.metadata_region();
984        let physical_region_id = to_metadata_region_id(env.default_physical_region_id());
985        let column_metadatas = test_column_metadatas();
986        let logical_region_id = RegionId::new(1024, 1);
987
988        let iter = vec![(
989            logical_region_id,
990            column_metadatas
991                .iter()
992                .map(|(k, v)| (k.as_str(), v))
993                .collect::<HashMap<_, _>>(),
994        )];
995        metadata_region
996            .add_logical_regions(physical_region_id, true, iter.into_iter())
997            .await
998            .unwrap();
999        // Add logical region again.
1000        let iter = vec![(
1001            logical_region_id,
1002            column_metadatas
1003                .iter()
1004                .map(|(k, v)| (k.as_str(), v))
1005                .collect::<HashMap<_, _>>(),
1006        )];
1007        metadata_region
1008            .add_logical_regions(physical_region_id, true, iter.into_iter())
1009            .await
1010            .unwrap();
1011
1012        // Check if the logical region is added.
1013        let logical_regions = metadata_region
1014            .logical_regions(physical_region_id)
1015            .await
1016            .unwrap();
1017        assert_eq!(logical_regions.len(), 2);
1018
1019        // Check if the logical region columns are added.
1020        let logical_columns = metadata_region
1021            .logical_columns(physical_region_id, logical_region_id)
1022            .await
1023            .unwrap()
1024            .into_iter()
1025            .collect::<HashMap<_, _>>();
1026        assert_eq!(logical_columns.len(), 2);
1027        assert_eq!(column_metadatas, logical_columns);
1028    }
1029
1030    #[tokio::test]
1031    async fn metadata_writes_are_synchronized_per_region() {
1032        let env = TestEnv::new().await;
1033        env.init_metric_region().await;
1034        let other_physical_region_id = RegionId::new(2, 2);
1035        env.create_physical_region(other_physical_region_id, "/test_dir2", vec![])
1036            .await;
1037        let metadata_region = Arc::new(env.metadata_region());
1038        let physical_region_id = env.default_physical_region_id();
1039        let metadata_region_id = to_metadata_region_id(physical_region_id);
1040
1041        let (snapshot_loaded_tx, snapshot_loaded_rx) = tokio::sync::oneshot::channel();
1042        let (release_snapshot_tx, release_snapshot_rx) = tokio::sync::oneshot::channel();
1043        let cache_fill = {
1044            let metadata_region = Arc::clone(&metadata_region);
1045            tokio::spawn(async move {
1046                let cache_access_lock = metadata_region.cache_access_lock(metadata_region_id);
1047                let _cache_guard = cache_access_lock.read().await;
1048                metadata_region
1049                    .cache
1050                    .try_get_with(metadata_region_id, async {
1051                        let snapshot = metadata_region.load_all(metadata_region_id).await?;
1052                        snapshot_loaded_tx.send(()).unwrap();
1053                        release_snapshot_rx.await.unwrap();
1054                        Ok::<_, crate::error::Error>(snapshot)
1055                    })
1056                    .await
1057                    .unwrap();
1058            })
1059        };
1060        snapshot_loaded_rx.await.unwrap();
1061
1062        let logical_region_id = RegionId::new(1024, 1);
1063        let metadata_write = {
1064            let metadata_region = Arc::clone(&metadata_region);
1065            tokio::spawn(async move {
1066                add_test_logical_region(&metadata_region, physical_region_id, logical_region_id)
1067                    .await
1068            })
1069        };
1070        let mut metadata_write = metadata_write;
1071        assert!(
1072            tokio::time::timeout(std::time::Duration::from_millis(50), &mut metadata_write)
1073                .await
1074                .is_err()
1075        );
1076
1077        let other_logical_region_id = RegionId::new(2048, 2);
1078        tokio::time::timeout(
1079            std::time::Duration::from_secs(5),
1080            add_test_logical_region(
1081                &metadata_region,
1082                other_physical_region_id,
1083                other_logical_region_id,
1084            ),
1085        )
1086        .await
1087        .unwrap()
1088        .unwrap();
1089
1090        release_snapshot_tx.send(()).unwrap();
1091        cache_fill.await.unwrap();
1092        metadata_write.await.unwrap().unwrap();
1093
1094        let logical_columns = metadata_region
1095            .logical_columns(physical_region_id, logical_region_id)
1096            .await
1097            .unwrap();
1098        assert_eq!(logical_columns.len(), 2);
1099        assert!(
1100            metadata_region
1101                .cache_access_locks
1102                .lock()
1103                .unwrap()
1104                .is_empty()
1105        );
1106    }
1107
1108    #[tokio::test]
1109    async fn cache_access_locks_self_clean() {
1110        let env = TestEnv::new().await;
1111        env.init_metric_region().await;
1112        let metadata_region = env.metadata_region();
1113        let physical_region_id = env.default_physical_region_id();
1114        let metadata_region_id = to_metadata_region_id(physical_region_id);
1115        let registry_len = || metadata_region.cache_access_locks.lock().unwrap().len();
1116
1117        // Metadata reads and writes leave no lock entries behind.
1118        let logical_region_id = RegionId::new(1024, 1);
1119        add_test_logical_region(&metadata_region, physical_region_id, logical_region_id)
1120            .await
1121            .unwrap();
1122        metadata_region
1123            .logical_columns(physical_region_id, logical_region_id)
1124            .await
1125            .unwrap();
1126        assert_eq!(registry_len(), 0);
1127
1128        // Concurrent leases share one lock; the entry lives until the last lease drops.
1129        let first = metadata_region.cache_access_lock(metadata_region_id);
1130        let second = metadata_region.cache_access_lock(metadata_region_id);
1131        assert!(Arc::ptr_eq(
1132            first.lock.as_ref().unwrap(),
1133            second.lock.as_ref().unwrap()
1134        ));
1135        drop(first);
1136        assert_eq!(registry_len(), 1);
1137        drop(second);
1138        assert_eq!(registry_len(), 0);
1139
1140        // A new acquisition after cleanup mints a fresh lock and entry.
1141        let third = metadata_region.cache_access_lock(metadata_region_id);
1142        assert_eq!(registry_len(), 1);
1143        drop(third);
1144        assert_eq!(registry_len(), 0);
1145    }
1146}