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        _sent_at: Instant,
141        _deadline: Instant,
142    ) -> Result<bool, BoxedError> {
143        self.set_region_role(region_id, role).map(|_| true)
144    }
145
146    async fn set_region_role_state_gracefully(
147        &self,
148        region_id: RegionId,
149        _region_role_state: SettableRegionRoleState,
150    ) -> Result<SetRegionRoleStateResponse, BoxedError> {
151        let exists = self.inner.get_region(region_id).await.is_some();
152
153        if exists {
154            Ok(SetRegionRoleStateResponse::success(
155                SetRegionRoleStateSuccess::file(),
156            ))
157        } else {
158            Ok(SetRegionRoleStateResponse::NotFound)
159        }
160    }
161
162    async fn sync_region(
163        &self,
164        _region_id: RegionId,
165        _request: SyncRegionFromRequest,
166    ) -> Result<SyncRegionFromResponse, BoxedError> {
167        // File engine doesn't need to sync region manifest.
168        Ok(SyncRegionFromResponse::NotSupported)
169    }
170
171    async fn remap_manifests(
172        &self,
173        _request: RemapManifestsRequest,
174    ) -> Result<RemapManifestsResponse, BoxedError> {
175        Err(BoxedError::new(
176            UnsupportedSnafu {
177                operation: "remap_manifests",
178            }
179            .build(),
180        ))
181    }
182
183    fn role(&self, region_id: RegionId) -> Option<RegionRole> {
184        self.inner.state(region_id)
185    }
186
187    fn as_any(&self) -> &dyn Any {
188        self
189    }
190}
191
192struct EngineInner {
193    /// All regions opened by the engine.
194    ///
195    /// Writing to `regions` should also hold the `region_mutex`.
196    regions: RwLock<HashMap<RegionId, FileRegionRef>>,
197
198    /// Region mutex is used to protect the operations such as creating/opening/closing
199    /// a region, to avoid things like opening the same region simultaneously.
200    region_mutex: Mutex<()>,
201
202    object_store: ObjectStore,
203
204    local_file_access: LocalFileAccess,
205}
206
207type EngineInnerRef = Arc<EngineInner>;
208
209fn ensure_region_requirements(
210    requirements: RegionRequirements,
211    object_store: &ObjectStore,
212) -> EngineResult<()> {
213    if !requirements.object_storage {
214        return Ok(());
215    }
216
217    ensure!(
218        object_store::util::is_object_storage(object_store),
219        UnsupportedSnafu {
220            operation: "open region with object storage requirement on non-object storage"
221        }
222    );
223
224    Ok(())
225}
226
227impl EngineInner {
228    fn new(object_store: ObjectStore, local_file_access: LocalFileAccess) -> Self {
229        Self {
230            regions: RwLock::new(HashMap::new()),
231            region_mutex: Mutex::new(()),
232            object_store,
233            local_file_access,
234        }
235    }
236
237    async fn handle_request(
238        &self,
239        region_id: RegionId,
240        request: RegionRequest,
241    ) -> EngineResult<RegionResponse> {
242        let result = match request {
243            RegionRequest::Create(req) => self.handle_create(region_id, req).await,
244            RegionRequest::Drop(req) => self.handle_drop(region_id, req).await,
245            RegionRequest::Open(req) => self.handle_open(region_id, req).await,
246            RegionRequest::Close(req) => self.handle_close(region_id, req).await,
247            _ => UnsupportedSnafu {
248                operation: request.to_string(),
249            }
250            .fail(),
251        };
252        result.map(RegionResponse::new)
253    }
254
255    async fn stop(&self) -> EngineResult<()> {
256        let _lock = self.region_mutex.lock().await;
257        self.regions.write().unwrap().clear();
258        Ok(())
259    }
260
261    fn set_region_role(&self, _region_id: RegionId, _region_role: RegionRole) -> EngineResult<()> {
262        // TODO(zhongzc): Improve the semantics and implementation of this API.
263        Ok(())
264    }
265
266    fn state(&self, region_id: RegionId) -> Option<RegionRole> {
267        if self.regions.read().unwrap().get(&region_id).is_some() {
268            Some(RegionRole::Leader)
269        } else {
270            None
271        }
272    }
273}
274
275impl EngineInner {
276    async fn handle_create(
277        &self,
278        region_id: RegionId,
279        request: RegionCreateRequest,
280    ) -> EngineResult<AffectedRows> {
281        ensure!(
282            request.engine == FILE_ENGINE,
283            UnexpectedEngineSnafu {
284                engine: request.engine
285            }
286        );
287
288        if self.exists(region_id).await {
289            return Ok(0);
290        }
291
292        info!("Try to create region, region_id: {}", region_id);
293
294        let _lock = self.region_mutex.lock().await;
295        // Check again after acquiring the lock
296        if self.exists(region_id).await {
297            return Ok(0);
298        }
299
300        ensure_region_requirements(request.requirements, &self.object_store)?;
301
302        let res = FileRegion::create(region_id, request, &self.object_store).await;
303        let region = res.inspect_err(|err| {
304            error!(
305                err;
306                "Failed to create region, region_id: {}",
307                region_id
308            );
309        })?;
310        self.regions.write().unwrap().insert(region_id, region);
311
312        info!("A new region is created, region_id: {}", region_id);
313        Ok(0)
314    }
315
316    async fn handle_open(
317        &self,
318        region_id: RegionId,
319        request: RegionOpenRequest,
320    ) -> EngineResult<AffectedRows> {
321        if self.exists(region_id).await {
322            return Ok(0);
323        }
324
325        info!("Try to open region, region_id: {}", region_id);
326
327        let _lock = self.region_mutex.lock().await;
328        // Check again after acquiring the lock
329        if self.exists(region_id).await {
330            return Ok(0);
331        }
332
333        ensure_region_requirements(request.requirements, &self.object_store)?;
334
335        let res = FileRegion::open(region_id, request, &self.object_store).await;
336        let region = res.inspect_err(|err| {
337            error!(
338                err;
339                "Failed to open region, region_id: {}",
340                region_id
341            );
342        })?;
343        self.regions.write().unwrap().insert(region_id, region);
344
345        info!("Region opened, region_id: {}", region_id);
346        Ok(0)
347    }
348
349    async fn handle_close(
350        &self,
351        region_id: RegionId,
352        _request: RegionCloseRequest,
353    ) -> EngineResult<AffectedRows> {
354        let _lock = self.region_mutex.lock().await;
355
356        let mut regions = self.regions.write().unwrap();
357        if regions.remove(&region_id).is_some() {
358            info!("Region closed, region_id: {}", region_id);
359        }
360
361        Ok(0)
362    }
363
364    async fn handle_drop(
365        &self,
366        region_id: RegionId,
367        _request: RegionDropRequest,
368    ) -> EngineResult<AffectedRows> {
369        if !self.exists(region_id).await {
370            return RegionNotFoundSnafu { region_id }.fail();
371        }
372
373        info!("Try to drop region, region_id: {}", region_id);
374
375        let _lock = self.region_mutex.lock().await;
376
377        let region = self.get_region(region_id).await;
378        if let Some(region) = region {
379            let res = FileRegion::drop(&region, &self.object_store).await;
380            res.inspect_err(|err| {
381                error!(
382                    err;
383                    "Failed to drop region, region_id: {}",
384                    region_id
385                );
386            })?;
387        }
388        let _ = self.regions.write().unwrap().remove(&region_id);
389
390        info!("Region dropped, region_id: {}", region_id);
391        Ok(0)
392    }
393
394    async fn get_region(&self, region_id: RegionId) -> Option<FileRegionRef> {
395        self.regions.read().unwrap().get(&region_id).cloned()
396    }
397
398    async fn exists(&self, region_id: RegionId) -> bool {
399        self.regions.read().unwrap().contains_key(&region_id)
400    }
401}
402
403#[cfg(test)]
404mod tests {
405    use object_store::services::{Fs, S3};
406
407    use super::*;
408    use crate::error::Error;
409
410    fn build_fs_object_store() -> ObjectStore {
411        ObjectStore::new(Fs::default().root("/tmp")).unwrap()
412    }
413
414    fn build_s3_object_store() -> ObjectStore {
415        ObjectStore::new(
416            S3::default()
417                .bucket("test-bucket")
418                .region("us-east-1")
419                .disable_ec2_metadata(),
420        )
421        .unwrap()
422    }
423
424    #[test]
425    fn test_empty_region_requirements_are_supported() {
426        ensure_region_requirements(RegionRequirements::empty(), &build_fs_object_store()).unwrap();
427    }
428
429    #[test]
430    fn test_object_storage_region_requirement_rejects_fs_object_store() {
431        let err = ensure_region_requirements(
432            RegionRequirements::object_storage(),
433            &build_fs_object_store(),
434        )
435        .unwrap_err();
436
437        assert!(matches!(err, Error::Unsupported { .. }));
438    }
439
440    #[test]
441    fn test_object_storage_region_requirement_accepts_s3_object_store() {
442        ensure_region_requirements(
443            RegionRequirements::object_storage(),
444            &build_s3_object_store(),
445        )
446        .unwrap();
447    }
448}