Skip to main content

catalog/kvbackend/
client.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::any::Any;
16use std::fmt::Debug;
17use std::sync::atomic::{AtomicUsize, Ordering};
18use std::sync::{Arc, Mutex};
19use std::time::Duration;
20
21use common_error::ext::BoxedError;
22use common_meta::cache_invalidator::KvCacheInvalidator;
23use common_meta::error::Error::CacheNotGet;
24use common_meta::error::{
25    CacheNotGetSnafu, Error, ExternalSnafu, GetKvCacheSnafu, Result, UnsupportedSnafu,
26};
27use common_meta::kv_backend::read_only::ReadOnlyKvBackend;
28use common_meta::kv_backend::txn::{Txn, TxnResponse};
29use common_meta::kv_backend::{KvBackend, KvBackendRef, TxnService};
30use common_meta::rpc::KeyValue;
31use common_meta::rpc::store::{
32    BatchDeleteRequest, BatchDeleteResponse, BatchGetRequest, BatchGetResponse, BatchPutRequest,
33    BatchPutResponse, CompareAndPutRequest, CompareAndPutResponse, DeleteRangeRequest,
34    DeleteRangeResponse, PutRequest, PutResponse, RangeRequest, RangeResponse,
35};
36use common_telemetry::debug;
37use meta_client::client::MetaClient;
38use moka::future::{Cache, CacheBuilder};
39use snafu::{OptionExt, ResultExt};
40
41use crate::metrics::{
42    METRIC_CATALOG_KV_BATCH_GET, METRIC_CATALOG_KV_GET, METRIC_CATALOG_KV_REMOTE_GET,
43};
44
45const DEFAULT_CACHE_MAX_CAPACITY: u64 = 10000;
46const DEFAULT_CACHE_TTL: Duration = Duration::from_secs(10 * 60);
47const DEFAULT_CACHE_TTI: Duration = Duration::from_secs(5 * 60);
48
49pub struct CachedKvBackendBuilder {
50    cache_max_capacity: Option<u64>,
51    cache_ttl: Option<Duration>,
52    cache_tti: Option<Duration>,
53    inner: KvBackendRef,
54}
55
56impl CachedKvBackendBuilder {
57    pub fn new(inner: KvBackendRef) -> Self {
58        Self {
59            cache_max_capacity: None,
60            cache_ttl: None,
61            cache_tti: None,
62            inner,
63        }
64    }
65
66    pub fn cache_max_capacity(mut self, cache_max_capacity: u64) -> Self {
67        self.cache_max_capacity.replace(cache_max_capacity);
68        self
69    }
70
71    pub fn cache_ttl(mut self, cache_ttl: Duration) -> Self {
72        self.cache_ttl.replace(cache_ttl);
73        self
74    }
75
76    pub fn cache_tti(mut self, cache_tti: Duration) -> Self {
77        self.cache_tti.replace(cache_tti);
78        self
79    }
80
81    pub fn build(self) -> CachedKvBackend {
82        let cache_max_capacity = self
83            .cache_max_capacity
84            .unwrap_or(DEFAULT_CACHE_MAX_CAPACITY);
85        let cache_ttl = self.cache_ttl.unwrap_or(DEFAULT_CACHE_TTL);
86        let cache_tti = self.cache_tti.unwrap_or(DEFAULT_CACHE_TTI);
87
88        let cache = CacheBuilder::new(cache_max_capacity)
89            .time_to_live(cache_ttl)
90            .time_to_idle(cache_tti)
91            .build();
92        let kv_backend = self.inner;
93        let name = format!("CachedKvBackend({})", kv_backend.name());
94        let version = AtomicUsize::new(0);
95
96        CachedKvBackend {
97            kv_backend,
98            cache,
99            name,
100            version,
101        }
102    }
103}
104
105pub type CacheBackend = Cache<Vec<u8>, KeyValue>;
106
107/// A wrapper of `MetaKvBackend` with cache support.
108///
109/// CachedMetaKvBackend is mainly used to read metadata information from Metasrv, and provides
110/// cache for get and batch_get. One way to trigger cache invalidation of CachedMetaKvBackend:
111/// when metadata information changes, Metasrv will broadcast a metadata invalidation request.
112///
113/// Therefore, it is recommended to use CachedMetaKvBackend to only read metadata related
114/// information. Note: If you read other information, you may read expired data, which depends on
115/// TTL and TTI for cache.
116pub struct CachedKvBackend {
117    kv_backend: KvBackendRef,
118    cache: CacheBackend,
119    name: String,
120    version: AtomicUsize,
121}
122
123#[async_trait::async_trait]
124impl TxnService for CachedKvBackend {
125    type Error = Error;
126
127    async fn txn(&self, txn: Txn) -> std::result::Result<TxnResponse, Self::Error> {
128        // TODO(hl): txn of CachedKvBackend simply pass through to inner backend without invalidating caches.
129        self.kv_backend.txn(txn).await
130    }
131
132    fn max_txn_ops(&self) -> usize {
133        self.kv_backend.max_txn_ops()
134    }
135}
136
137#[async_trait::async_trait]
138impl KvBackend for CachedKvBackend {
139    fn name(&self) -> &str {
140        &self.name
141    }
142
143    fn as_any(&self) -> &dyn Any {
144        self
145    }
146
147    async fn range(&self, req: RangeRequest) -> Result<RangeResponse> {
148        self.kv_backend.range(req).await
149    }
150
151    async fn put(&self, req: PutRequest) -> Result<PutResponse> {
152        let key = &req.key.clone();
153
154        let ret = self.kv_backend.put(req).await;
155
156        if ret.is_ok() {
157            self.invalidate_key(key).await;
158        }
159
160        ret
161    }
162
163    async fn batch_put(&self, req: BatchPutRequest) -> Result<BatchPutResponse> {
164        let keys = req
165            .kvs
166            .iter()
167            .map(|kv| kv.key().to_vec())
168            .collect::<Vec<_>>();
169
170        let resp = self.kv_backend.batch_put(req).await;
171
172        if resp.is_ok() {
173            for key in keys {
174                self.invalidate_key(&key).await;
175            }
176        }
177
178        resp
179    }
180
181    async fn batch_get(&self, req: BatchGetRequest) -> Result<BatchGetResponse> {
182        let _timer = METRIC_CATALOG_KV_BATCH_GET.start_timer();
183
184        let mut kvs = Vec::with_capacity(req.keys.len());
185        let mut miss_keys = Vec::with_capacity(req.keys.len());
186
187        for key in req.keys {
188            if let Some(val) = self.cache.get(&key).await {
189                kvs.push(val);
190            } else {
191                miss_keys.push(key);
192            }
193        }
194
195        if miss_keys.is_empty() {
196            return Ok(BatchGetResponse { kvs });
197        }
198
199        let batch_get_req = BatchGetRequest::new().with_keys(miss_keys.clone());
200
201        let pre_version = self.version();
202
203        let unhit_kvs = self.kv_backend.batch_get(batch_get_req).await?.kvs;
204
205        for kv in unhit_kvs.iter() {
206            self.cache.insert(kv.key().to_vec(), kv.clone()).await;
207        }
208
209        if !self.validate_version(pre_version) {
210            for key in miss_keys.iter() {
211                self.cache.invalidate(key).await;
212            }
213        }
214
215        kvs.extend(unhit_kvs);
216
217        Ok(BatchGetResponse { kvs })
218    }
219
220    async fn compare_and_put(&self, req: CompareAndPutRequest) -> Result<CompareAndPutResponse> {
221        let key = &req.key.clone();
222
223        let ret = self.kv_backend.compare_and_put(req).await;
224
225        if ret.is_ok() {
226            self.invalidate_key(key).await;
227        }
228
229        ret
230    }
231
232    async fn delete_range(&self, mut req: DeleteRangeRequest) -> Result<DeleteRangeResponse> {
233        let prev_kv = req.prev_kv;
234
235        req.prev_kv = true;
236        let resp = self.kv_backend.delete_range(req).await;
237        match resp {
238            Ok(mut resp) => {
239                for prev_kv in resp.prev_kvs.iter() {
240                    self.invalidate_key(prev_kv.key()).await;
241                }
242
243                if !prev_kv {
244                    resp.prev_kvs = vec![];
245                }
246                Ok(resp)
247            }
248            Err(e) => Err(e),
249        }
250    }
251
252    async fn batch_delete(&self, mut req: BatchDeleteRequest) -> Result<BatchDeleteResponse> {
253        let prev_kv = req.prev_kv;
254
255        req.prev_kv = true;
256        let resp = self.kv_backend.batch_delete(req).await;
257        match resp {
258            Ok(mut resp) => {
259                for prev_kv in resp.prev_kvs.iter() {
260                    self.invalidate_key(prev_kv.key()).await;
261                }
262
263                if !prev_kv {
264                    resp.prev_kvs = vec![];
265                }
266                Ok(resp)
267            }
268            Err(e) => Err(e),
269        }
270    }
271
272    async fn get(&self, key: &[u8]) -> Result<Option<KeyValue>> {
273        let _timer = METRIC_CATALOG_KV_GET.start_timer();
274
275        let pre_version = Arc::new(Mutex::new(None));
276
277        let init = async {
278            let version_clone = pre_version.clone();
279            let _timer = METRIC_CATALOG_KV_REMOTE_GET.start_timer();
280
281            version_clone.lock().unwrap().replace(self.version());
282
283            self.kv_backend.get(key).await.map(|val| {
284                val.with_context(|| CacheNotGetSnafu {
285                    key: String::from_utf8_lossy(key),
286                })
287            })?
288        };
289
290        // currently moka doesn't have `optionally_try_get_with_by_ref`
291        // TODO(fys): change to moka method when available
292        // https://github.com/moka-rs/moka/issues/254
293        let ret = match self.cache.try_get_with_by_ref(key, init).await {
294            Ok(val) => Ok(Some(val)),
295            Err(e) => match e.as_ref() {
296                CacheNotGet { .. } => Ok(None),
297                _ => Err(e),
298            },
299        }
300        .map_err(|e| {
301            GetKvCacheSnafu {
302                err_msg: e.to_string(),
303            }
304            .build()
305        });
306
307        // "cache.invalidate_key" and "cache.try_get_with_by_ref" are not mutually exclusive. So we need
308        // to use the version mechanism to prevent expired data from being put into the cache.
309        if pre_version
310            .lock()
311            .unwrap()
312            .as_ref()
313            .is_some_and(|v| !self.validate_version(*v))
314        {
315            self.cache.invalidate(key).await;
316        }
317
318        ret
319    }
320}
321
322#[async_trait::async_trait]
323impl KvCacheInvalidator for CachedKvBackend {
324    async fn invalidate_key(&self, key: &[u8]) {
325        self.create_new_version();
326        self.cache.invalidate(key).await;
327        debug!("invalidated cache key: {}", String::from_utf8_lossy(key));
328    }
329}
330
331impl CachedKvBackend {
332    // only for test
333    #[cfg(test)]
334    fn wrap(kv_backend: KvBackendRef) -> Self {
335        let cache = CacheBuilder::new(DEFAULT_CACHE_MAX_CAPACITY)
336            .time_to_live(DEFAULT_CACHE_TTL)
337            .time_to_idle(DEFAULT_CACHE_TTI)
338            .build();
339
340        let name = format!("CachedKvBackend({})", kv_backend.name());
341        Self {
342            kv_backend,
343            cache,
344            name,
345            version: AtomicUsize::new(0),
346        }
347    }
348
349    pub fn cache(&self) -> &CacheBackend {
350        &self.cache
351    }
352
353    fn version(&self) -> usize {
354        self.version.load(Ordering::Relaxed)
355    }
356
357    fn validate_version(&self, pre_version: usize) -> bool {
358        self.version() == pre_version
359    }
360
361    fn create_new_version(&self) -> usize {
362        self.version.fetch_add(1, Ordering::Relaxed) + 1
363    }
364}
365
366#[derive(Debug)]
367pub(crate) struct MetaKvBackend {
368    client: Arc<MetaClient>,
369}
370
371impl MetaKvBackend {
372    /// Constructs a [MetaKvBackend].
373    fn new(client: Arc<MetaClient>) -> MetaKvBackend {
374        MetaKvBackend { client }
375    }
376}
377
378pub fn new_read_only_meta_kv_backend(client: Arc<MetaClient>) -> KvBackendRef {
379    Arc::new(ReadOnlyKvBackend::new(Arc::new(MetaKvBackend::new(client))))
380}
381
382#[async_trait::async_trait]
383impl TxnService for MetaKvBackend {
384    type Error = Error;
385
386    async fn txn(&self, _txn: Txn) -> Result<TxnResponse> {
387        UnsupportedSnafu {
388            operation: "MetaKvBackend txn",
389        }
390        .fail()
391    }
392
393    fn max_txn_ops(&self) -> usize {
394        usize::MAX
395    }
396}
397
398/// Implement `KvBackend` trait for `MetaKvBackend` instead of opendal's `Accessor` since
399/// `MetaClient`'s range method can return both keys and values, which can reduce IO overhead
400/// comparing to `Accessor`'s list and get method.
401#[async_trait::async_trait]
402impl KvBackend for MetaKvBackend {
403    fn name(&self) -> &str {
404        "MetaKvBackend"
405    }
406
407    fn as_any(&self) -> &dyn Any {
408        self
409    }
410
411    async fn range(&self, req: RangeRequest) -> Result<RangeResponse> {
412        self.client
413            .range(req)
414            .await
415            .map_err(BoxedError::new)
416            .context(ExternalSnafu)
417    }
418
419    async fn put(&self, req: PutRequest) -> Result<PutResponse> {
420        self.client
421            .put(req)
422            .await
423            .map_err(BoxedError::new)
424            .context(ExternalSnafu)
425    }
426
427    async fn batch_put(&self, req: BatchPutRequest) -> Result<BatchPutResponse> {
428        self.client
429            .batch_put(req)
430            .await
431            .map_err(BoxedError::new)
432            .context(ExternalSnafu)
433    }
434
435    async fn batch_get(&self, req: BatchGetRequest) -> Result<BatchGetResponse> {
436        self.client
437            .batch_get(req)
438            .await
439            .map_err(BoxedError::new)
440            .context(ExternalSnafu)
441    }
442
443    async fn compare_and_put(
444        &self,
445        request: CompareAndPutRequest,
446    ) -> Result<CompareAndPutResponse> {
447        self.client
448            .compare_and_put(request)
449            .await
450            .map_err(BoxedError::new)
451            .context(ExternalSnafu)
452    }
453
454    async fn delete_range(&self, req: DeleteRangeRequest) -> Result<DeleteRangeResponse> {
455        self.client
456            .delete_range(req)
457            .await
458            .map_err(BoxedError::new)
459            .context(ExternalSnafu)
460    }
461
462    async fn batch_delete(&self, req: BatchDeleteRequest) -> Result<BatchDeleteResponse> {
463        self.client
464            .batch_delete(req)
465            .await
466            .map_err(BoxedError::new)
467            .context(ExternalSnafu)
468    }
469
470    async fn get(&self, key: &[u8]) -> Result<Option<KeyValue>> {
471        let mut response = self
472            .client
473            .range(RangeRequest::new().with_key(key))
474            .await
475            .map_err(BoxedError::new)
476            .context(ExternalSnafu)?;
477        Ok(response.take_kvs().get_mut(0).map(|kv| KeyValue {
478            key: kv.take_key(),
479            value: kv.take_value(),
480        }))
481    }
482}
483
484#[cfg(test)]
485mod tests {
486    use std::any::Any;
487    use std::sync::Arc;
488    use std::sync::atomic::{AtomicU32, Ordering};
489
490    use async_trait::async_trait;
491    use common_meta::kv_backend::memory::MemoryKvBackend;
492    use common_meta::kv_backend::read_only::ReadOnlyKvBackend;
493    use common_meta::kv_backend::txn::{Txn, TxnOp};
494    use common_meta::kv_backend::{KvBackend, TxnService};
495    use common_meta::rpc::KeyValue;
496    use common_meta::rpc::store::{
497        BatchDeleteRequest, BatchDeleteResponse, BatchGetRequest, BatchGetResponse,
498        BatchPutRequest, BatchPutResponse, DeleteRangeRequest, DeleteRangeResponse, PutRequest,
499        PutResponse, RangeRequest, RangeResponse,
500    };
501    use dashmap::DashMap;
502    use meta_client::client::MetaClientBuilder;
503
504    use super::{CachedKvBackend, new_read_only_meta_kv_backend};
505
506    #[derive(Default)]
507    pub struct SimpleKvBackend {
508        inner_map: DashMap<Vec<u8>, Vec<u8>>,
509        get_execute_times: Arc<AtomicU32>,
510        batch_get_execute_times: Arc<AtomicU32>,
511    }
512
513    impl TxnService for SimpleKvBackend {
514        type Error = common_meta::error::Error;
515    }
516
517    #[async_trait]
518    impl KvBackend for SimpleKvBackend {
519        fn name(&self) -> &str {
520            "SimpleKvBackend"
521        }
522
523        fn as_any(&self) -> &dyn Any {
524            self
525        }
526
527        async fn batch_get(&self, req: BatchGetRequest) -> Result<BatchGetResponse, Self::Error> {
528            self.batch_get_execute_times.fetch_add(1, Ordering::SeqCst);
529            let mut kvs = Vec::with_capacity(req.keys.len());
530            for key in req.keys.iter() {
531                if let Some(kv) = self.get(key).await? {
532                    kvs.push(kv);
533                }
534            }
535            Ok(BatchGetResponse { kvs })
536        }
537
538        async fn put(&self, req: PutRequest) -> Result<PutResponse, Self::Error> {
539            self.inner_map.insert(req.key, req.value);
540            // always return None as prev_kv, since we don't use it in this test.
541            Ok(PutResponse { prev_kv: None })
542        }
543
544        async fn get(&self, key: &[u8]) -> Result<Option<KeyValue>, Self::Error> {
545            self.get_execute_times
546                .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
547            Ok(self.inner_map.get(key).map(|v| KeyValue {
548                key: key.to_vec(),
549                value: v.value().clone(),
550            }))
551        }
552
553        async fn range(&self, _req: RangeRequest) -> Result<RangeResponse, Self::Error> {
554            unimplemented!()
555        }
556
557        async fn batch_put(&self, _req: BatchPutRequest) -> Result<BatchPutResponse, Self::Error> {
558            unimplemented!()
559        }
560
561        async fn delete_range(
562            &self,
563            _req: DeleteRangeRequest,
564        ) -> Result<DeleteRangeResponse, Self::Error> {
565            unimplemented!()
566        }
567
568        async fn batch_delete(
569            &self,
570            _req: BatchDeleteRequest,
571        ) -> Result<BatchDeleteResponse, Self::Error> {
572            unimplemented!()
573        }
574    }
575
576    #[tokio::test]
577    async fn test_cached_kv_backend() {
578        let simple_kv = Arc::new(SimpleKvBackend::default());
579        let get_execute_times = simple_kv.get_execute_times.clone();
580        let batch_get_execute_times = simple_kv.batch_get_execute_times.clone();
581        let cached_kv = CachedKvBackend::wrap(simple_kv);
582
583        add_some_vals(&cached_kv).await;
584
585        let batch_get_req = BatchGetRequest {
586            keys: vec![b"k1".to_vec(), b"k2".to_vec()],
587        };
588
589        assert_eq!(get_execute_times.load(Ordering::SeqCst), 0);
590
591        for _ in 0..10 {
592            let batch_get_resp = cached_kv.batch_get(batch_get_req.clone()).await.unwrap();
593
594            assert_eq!(batch_get_resp.kvs.len(), 2);
595            assert_eq!(get_execute_times.load(Ordering::SeqCst), 2);
596            assert_eq!(batch_get_execute_times.load(Ordering::SeqCst), 1);
597        }
598
599        let batch_get_req = BatchGetRequest {
600            keys: vec![b"k1".to_vec(), b"k2".to_vec(), b"k3".to_vec()],
601        };
602
603        let _batch_get_resp = cached_kv.batch_get(batch_get_req.clone()).await.unwrap();
604
605        assert_eq!(get_execute_times.load(Ordering::SeqCst), 3);
606        assert_eq!(batch_get_execute_times.load(Ordering::SeqCst), 2);
607
608        for _ in 0..10 {
609            let batch_get_resp = cached_kv.batch_get(batch_get_req.clone()).await.unwrap();
610
611            assert_eq!(batch_get_resp.kvs.len(), 3);
612            assert_eq!(get_execute_times.load(Ordering::SeqCst), 3);
613            assert_eq!(batch_get_execute_times.load(Ordering::SeqCst), 2);
614        }
615    }
616
617    #[tokio::test]
618    async fn test_cached_kv_backend_empty_batch_get() {
619        let simple_kv = Arc::new(SimpleKvBackend::default());
620        let cached_kv = CachedKvBackend::wrap(simple_kv.clone());
621
622        let response = cached_kv.batch_get(BatchGetRequest::new()).await.unwrap();
623
624        assert!(response.kvs.is_empty());
625        assert_eq!(simple_kv.batch_get_execute_times.load(Ordering::SeqCst), 0);
626        assert_eq!(simple_kv.get_execute_times.load(Ordering::SeqCst), 0);
627    }
628
629    #[tokio::test]
630    async fn test_cached_kv_backend_rejects_writes_with_read_only_inner() {
631        let inner = Arc::new(MemoryKvBackend::<common_meta::error::Error>::new());
632        let cached_kv = CachedKvBackend::wrap(Arc::new(ReadOnlyKvBackend::new(inner)));
633
634        let err = cached_kv
635            .put(PutRequest {
636                key: b"k1".to_vec(),
637                value: b"v1".to_vec(),
638                prev_kv: false,
639            })
640            .await
641            .unwrap_err();
642
643        assert!(matches!(
644            err,
645            common_meta::error::Error::ReadOnlyKvBackend { .. }
646        ));
647    }
648
649    #[tokio::test]
650    async fn test_read_only_meta_kv_backend_rejects_writes() {
651        let meta_client = Arc::new(MetaClientBuilder::frontend_default_options().build());
652        let backend = new_read_only_meta_kv_backend(meta_client);
653
654        let err = backend
655            .put(PutRequest {
656                key: b"k1".to_vec(),
657                value: b"v1".to_vec(),
658                prev_kv: false,
659            })
660            .await
661            .unwrap_err();
662
663        assert!(matches!(
664            err,
665            common_meta::error::Error::ReadOnlyKvBackend { .. }
666        ));
667    }
668
669    #[tokio::test]
670    async fn test_read_only_meta_kv_backend_does_not_emulate_txn() {
671        let meta_client = Arc::new(MetaClientBuilder::frontend_default_options().build());
672        let backend = new_read_only_meta_kv_backend(meta_client);
673
674        let result = backend
675            .txn(Txn::new().and_then(vec![TxnOp::Get(b"k1".to_vec())]))
676            .await;
677        let err = match result {
678            Ok(_) => panic!("expected unsupported txn error"),
679            Err(err) => err,
680        };
681
682        assert!(matches!(err, common_meta::error::Error::Unsupported { .. }));
683    }
684
685    async fn add_some_vals(kv_backend: &impl KvBackend) {
686        kv_backend
687            .put(PutRequest {
688                key: b"k1".to_vec(),
689                value: b"v1".to_vec(),
690                prev_kv: false,
691            })
692            .await
693            .unwrap();
694
695        kv_backend
696            .put(PutRequest {
697                key: b"k2".to_vec(),
698                value: b"v2".to_vec(),
699                prev_kv: false,
700            })
701            .await
702            .unwrap();
703
704        kv_backend
705            .put(PutRequest {
706                key: b"k3".to_vec(),
707                value: b"v3".to_vec(),
708                prev_kv: false,
709            })
710            .await
711            .unwrap();
712    }
713}