Skip to main content

file_engine/
engine.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::collections::HashMap;
17use std::sync::{Arc, RwLock};
18use std::time::Instant;
19
20use api::region::RegionResponse;
21use async_trait::async_trait;
22use common_catalog::consts::FILE_ENGINE;
23use common_datasource::object_store::LocalFileAccess;
24use common_error::ext::BoxedError;
25use common_recordbatch::SendableRecordBatchStream;
26use common_telemetry::{error, info};
27use object_store::ObjectStore;
28use snafu::{OptionExt, ensure};
29use store_api::metadata::RegionMetadataRef;
30use store_api::region_engine::{
31    RegionEngine, RegionRole, RegionScannerRef, RegionStatistic, RemapManifestsRequest,
32    RemapManifestsResponse, SetRegionRoleStateResponse, SetRegionRoleStateSuccess,
33    SettableRegionRoleState, SinglePartitionScanner, SyncRegionFromRequest, SyncRegionFromResponse,
34};
35use store_api::region_request::{
36    AffectedRows, RegionCloseRequest, RegionCreateRequest, RegionDropRequest, RegionOpenRequest,
37    RegionRequest, RegionRequirements,
38};
39use store_api::storage::{RegionId, ScanRequest, SequenceNumber};
40use tokio::sync::Mutex;
41
42use crate::config::EngineConfig;
43use crate::error::{
44    RegionNotFoundSnafu, Result as EngineResult, UnexpectedEngineSnafu, UnsupportedSnafu,
45};
46use crate::region::{FileRegion, FileRegionRef};
47
48pub struct FileRegionEngine {
49    inner: EngineInnerRef,
50}
51
52impl FileRegionEngine {
53    pub fn new(
54        _config: EngineConfig,
55        object_store: ObjectStore,
56        local_file_access: LocalFileAccess,
57    ) -> Self {
58        Self {
59            inner: Arc::new(EngineInner::new(object_store, local_file_access)),
60        }
61    }
62
63    async fn handle_query(
64        &self,
65        region_id: RegionId,
66        request: ScanRequest,
67    ) -> Result<SendableRecordBatchStream, BoxedError> {
68        self.inner
69            .get_region(region_id)
70            .await
71            .context(RegionNotFoundSnafu { region_id })
72            .map_err(BoxedError::new)?
73            .query(request, &self.inner.local_file_access)
74            .await
75            .map_err(BoxedError::new)
76    }
77}
78
79#[async_trait]
80impl RegionEngine for FileRegionEngine {
81    fn name(&self) -> &str {
82        FILE_ENGINE
83    }
84
85    async fn handle_request(
86        &self,
87        region_id: RegionId,
88        request: RegionRequest,
89    ) -> Result<RegionResponse, BoxedError> {
90        self.inner
91            .handle_request(region_id, request)
92            .await
93            .map_err(BoxedError::new)
94    }
95
96    async fn handle_query(
97        &self,
98        region_id: RegionId,
99        request: ScanRequest,
100    ) -> Result<RegionScannerRef, BoxedError> {
101        let stream = self.handle_query(region_id, request).await?;
102        let metadata = self.get_metadata(region_id).await?;
103        // We don't support enabling append mode for file engine.
104        let scanner = Box::new(SinglePartitionScanner::new(stream, false, metadata, None));
105        Ok(scanner)
106    }
107
108    async fn get_metadata(&self, region_id: RegionId) -> Result<RegionMetadataRef, BoxedError> {
109        self.inner
110            .get_region(region_id)
111            .await
112            .map(|r| r.metadata())
113            .context(RegionNotFoundSnafu { region_id })
114            .map_err(BoxedError::new)
115    }
116
117    async fn stop(&self) -> Result<(), BoxedError> {
118        self.inner.stop().await.map_err(BoxedError::new)
119    }
120
121    fn region_statistic(&self, _: RegionId) -> Option<RegionStatistic> {
122        None
123    }
124
125    async fn get_committed_sequence(&self, _: RegionId) -> Result<SequenceNumber, BoxedError> {
126        Ok(Default::default())
127    }
128
129    fn set_region_role(&self, region_id: RegionId, role: RegionRole) -> Result<(), BoxedError> {
130        self.inner
131            .set_region_role(region_id, role)
132            .map_err(BoxedError::new)
133    }
134
135    /// Regions of this engine take no writes, so the lease is not needed.
136    fn set_region_role_with_lease(
137        &self,
138        region_id: RegionId,
139        role: RegionRole,
140        _deadline: Instant,
141    ) -> Result<(), BoxedError> {
142        self.set_region_role(region_id, role)
143    }
144
145    async fn set_region_role_state_gracefully(
146        &self,
147        region_id: RegionId,
148        _region_role_state: SettableRegionRoleState,
149    ) -> Result<SetRegionRoleStateResponse, BoxedError> {
150        let exists = self.inner.get_region(region_id).await.is_some();
151
152        if exists {
153            Ok(SetRegionRoleStateResponse::success(
154                SetRegionRoleStateSuccess::file(),
155            ))
156        } else {
157            Ok(SetRegionRoleStateResponse::NotFound)
158        }
159    }
160
161    async fn sync_region(
162        &self,
163        _region_id: RegionId,
164        _request: SyncRegionFromRequest,
165    ) -> Result<SyncRegionFromResponse, BoxedError> {
166        // File engine doesn't need to sync region manifest.
167        Ok(SyncRegionFromResponse::NotSupported)
168    }
169
170    async fn remap_manifests(
171        &self,
172        _request: RemapManifestsRequest,
173    ) -> Result<RemapManifestsResponse, BoxedError> {
174        Err(BoxedError::new(
175            UnsupportedSnafu {
176                operation: "remap_manifests",
177            }
178            .build(),
179        ))
180    }
181
182    fn role(&self, region_id: RegionId) -> Option<RegionRole> {
183        self.inner.state(region_id)
184    }
185
186    fn as_any(&self) -> &dyn Any {
187        self
188    }
189}
190
191struct EngineInner {
192    /// All regions opened by the engine.
193    ///
194    /// Writing to `regions` should also hold the `region_mutex`.
195    regions: RwLock<HashMap<RegionId, FileRegionRef>>,
196
197    /// Region mutex is used to protect the operations such as creating/opening/closing
198    /// a region, to avoid things like opening the same region simultaneously.
199    region_mutex: Mutex<()>,
200
201    object_store: ObjectStore,
202
203    local_file_access: LocalFileAccess,
204}
205
206type EngineInnerRef = Arc<EngineInner>;
207
208fn ensure_region_requirements(
209    requirements: RegionRequirements,
210    object_store: &ObjectStore,
211) -> EngineResult<()> {
212    if !requirements.object_storage {
213        return Ok(());
214    }
215
216    ensure!(
217        object_store::util::is_object_storage(object_store),
218        UnsupportedSnafu {
219            operation: "open region with object storage requirement on non-object storage"
220        }
221    );
222
223    Ok(())
224}
225
226impl EngineInner {
227    fn new(object_store: ObjectStore, local_file_access: LocalFileAccess) -> Self {
228        Self {
229            regions: RwLock::new(HashMap::new()),
230            region_mutex: Mutex::new(()),
231            object_store,
232            local_file_access,
233        }
234    }
235
236    async fn handle_request(
237        &self,
238        region_id: RegionId,
239        request: RegionRequest,
240    ) -> EngineResult<RegionResponse> {
241        let result = match request {
242            RegionRequest::Create(req) => self.handle_create(region_id, req).await,
243            RegionRequest::Drop(req) => self.handle_drop(region_id, req).await,
244            RegionRequest::Open(req) => self.handle_open(region_id, req).await,
245            RegionRequest::Close(req) => self.handle_close(region_id, req).await,
246            _ => UnsupportedSnafu {
247                operation: request.to_string(),
248            }
249            .fail(),
250        };
251        result.map(RegionResponse::new)
252    }
253
254    async fn stop(&self) -> EngineResult<()> {
255        let _lock = self.region_mutex.lock().await;
256        self.regions.write().unwrap().clear();
257        Ok(())
258    }
259
260    fn set_region_role(&self, _region_id: RegionId, _region_role: RegionRole) -> EngineResult<()> {
261        // TODO(zhongzc): Improve the semantics and implementation of this API.
262        Ok(())
263    }
264
265    fn state(&self, region_id: RegionId) -> Option<RegionRole> {
266        if self.regions.read().unwrap().get(&region_id).is_some() {
267            Some(RegionRole::Leader)
268        } else {
269            None
270        }
271    }
272}
273
274impl EngineInner {
275    async fn handle_create(
276        &self,
277        region_id: RegionId,
278        request: RegionCreateRequest,
279    ) -> EngineResult<AffectedRows> {
280        ensure!(
281            request.engine == FILE_ENGINE,
282            UnexpectedEngineSnafu {
283                engine: request.engine
284            }
285        );
286
287        if self.exists(region_id).await {
288            return Ok(0);
289        }
290
291        info!("Try to create region, region_id: {}", region_id);
292
293        let _lock = self.region_mutex.lock().await;
294        // Check again after acquiring the lock
295        if self.exists(region_id).await {
296            return Ok(0);
297        }
298
299        ensure_region_requirements(request.requirements, &self.object_store)?;
300
301        let res = FileRegion::create(region_id, request, &self.object_store).await;
302        let region = res.inspect_err(|err| {
303            error!(
304                err;
305                "Failed to create region, region_id: {}",
306                region_id
307            );
308        })?;
309        self.regions.write().unwrap().insert(region_id, region);
310
311        info!("A new region is created, region_id: {}", region_id);
312        Ok(0)
313    }
314
315    async fn handle_open(
316        &self,
317        region_id: RegionId,
318        request: RegionOpenRequest,
319    ) -> EngineResult<AffectedRows> {
320        if self.exists(region_id).await {
321            return Ok(0);
322        }
323
324        info!("Try to open region, region_id: {}", region_id);
325
326        let _lock = self.region_mutex.lock().await;
327        // Check again after acquiring the lock
328        if self.exists(region_id).await {
329            return Ok(0);
330        }
331
332        ensure_region_requirements(request.requirements, &self.object_store)?;
333
334        let res = FileRegion::open(region_id, request, &self.object_store).await;
335        let region = res.inspect_err(|err| {
336            error!(
337                err;
338                "Failed to open region, region_id: {}",
339                region_id
340            );
341        })?;
342        self.regions.write().unwrap().insert(region_id, region);
343
344        info!("Region opened, region_id: {}", region_id);
345        Ok(0)
346    }
347
348    async fn handle_close(
349        &self,
350        region_id: RegionId,
351        _request: RegionCloseRequest,
352    ) -> EngineResult<AffectedRows> {
353        let _lock = self.region_mutex.lock().await;
354
355        let mut regions = self.regions.write().unwrap();
356        if regions.remove(&region_id).is_some() {
357            info!("Region closed, region_id: {}", region_id);
358        }
359
360        Ok(0)
361    }
362
363    async fn handle_drop(
364        &self,
365        region_id: RegionId,
366        _request: RegionDropRequest,
367    ) -> EngineResult<AffectedRows> {
368        if !self.exists(region_id).await {
369            return RegionNotFoundSnafu { region_id }.fail();
370        }
371
372        info!("Try to drop region, region_id: {}", region_id);
373
374        let _lock = self.region_mutex.lock().await;
375
376        let region = self.get_region(region_id).await;
377        if let Some(region) = region {
378            let res = FileRegion::drop(&region, &self.object_store).await;
379            res.inspect_err(|err| {
380                error!(
381                    err;
382                    "Failed to drop region, region_id: {}",
383                    region_id
384                );
385            })?;
386        }
387        let _ = self.regions.write().unwrap().remove(&region_id);
388
389        info!("Region dropped, region_id: {}", region_id);
390        Ok(0)
391    }
392
393    async fn get_region(&self, region_id: RegionId) -> Option<FileRegionRef> {
394        self.regions.read().unwrap().get(&region_id).cloned()
395    }
396
397    async fn exists(&self, region_id: RegionId) -> bool {
398        self.regions.read().unwrap().contains_key(&region_id)
399    }
400}
401
402#[cfg(test)]
403mod tests {
404    use object_store::services::{Fs, S3};
405
406    use super::*;
407    use crate::error::Error;
408
409    fn build_fs_object_store() -> ObjectStore {
410        ObjectStore::new(Fs::default().root("/tmp")).unwrap()
411    }
412
413    fn build_s3_object_store() -> ObjectStore {
414        ObjectStore::new(
415            S3::default()
416                .bucket("test-bucket")
417                .region("us-east-1")
418                .disable_ec2_metadata(),
419        )
420        .unwrap()
421    }
422
423    #[test]
424    fn test_empty_region_requirements_are_supported() {
425        ensure_region_requirements(RegionRequirements::empty(), &build_fs_object_store()).unwrap();
426    }
427
428    #[test]
429    fn test_object_storage_region_requirement_rejects_fs_object_store() {
430        let err = ensure_region_requirements(
431            RegionRequirements::object_storage(),
432            &build_fs_object_store(),
433        )
434        .unwrap_err();
435
436        assert!(matches!(err, Error::Unsupported { .. }));
437    }
438
439    #[test]
440    fn test_object_storage_region_requirement_accepts_s3_object_store() {
441        ensure_region_requirements(
442            RegionRequirements::object_storage(),
443            &build_s3_object_store(),
444        )
445        .unwrap();
446    }
447}