mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2025-12-26 08:00:01 +00:00
feat: Implement reader that returns the last row of each series (#4354)
* feat: last row reader * feat: scan use last row reader * test: test last row selector * chore: update comment
This commit is contained in:
@@ -49,6 +49,8 @@ mod projection_test;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod prune_test;
|
mod prune_test;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
mod row_selector_test;
|
||||||
|
#[cfg(test)]
|
||||||
mod set_readonly_test;
|
mod set_readonly_test;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod truncate_test;
|
mod truncate_test;
|
||||||
|
|||||||
104
src/mito2/src/engine/row_selector_test.rs
Normal file
104
src/mito2/src/engine/row_selector_test.rs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
// Copyright 2023 Greptime Team
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use api::v1::Rows;
|
||||||
|
use common_recordbatch::RecordBatches;
|
||||||
|
use store_api::region_engine::RegionEngine;
|
||||||
|
use store_api::region_request::RegionRequest;
|
||||||
|
use store_api::storage::{RegionId, ScanRequest, TimeSeriesRowSelector};
|
||||||
|
|
||||||
|
use crate::config::MitoConfig;
|
||||||
|
use crate::test_util::batch_util::sort_batches_and_print;
|
||||||
|
use crate::test_util::{
|
||||||
|
build_rows_for_key, flush_region, put_rows, rows_schema, CreateRequestBuilder, TestEnv,
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn test_last_row(append_mode: bool) {
|
||||||
|
let mut env = TestEnv::new();
|
||||||
|
let engine = env.create_engine(MitoConfig::default()).await;
|
||||||
|
let region_id = RegionId::new(1, 1);
|
||||||
|
|
||||||
|
let request = CreateRequestBuilder::new()
|
||||||
|
.insert_option("append_mode", &append_mode.to_string())
|
||||||
|
.build();
|
||||||
|
let column_schemas = rows_schema(&request);
|
||||||
|
engine
|
||||||
|
.handle_request(region_id, RegionRequest::Create(request))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Flush 3 SSTs.
|
||||||
|
// a, field 1, 2
|
||||||
|
let rows = Rows {
|
||||||
|
schema: column_schemas.clone(),
|
||||||
|
rows: build_rows_for_key("a", 1, 3, 1),
|
||||||
|
};
|
||||||
|
put_rows(&engine, region_id, rows).await;
|
||||||
|
flush_region(&engine, region_id, None).await;
|
||||||
|
// a, field 0, 1
|
||||||
|
let rows = Rows {
|
||||||
|
schema: column_schemas.clone(),
|
||||||
|
rows: build_rows_for_key("a", 0, 2, 0),
|
||||||
|
};
|
||||||
|
put_rows(&engine, region_id, rows).await;
|
||||||
|
flush_region(&engine, region_id, None).await;
|
||||||
|
// b, field 0, 1
|
||||||
|
let rows = Rows {
|
||||||
|
schema: column_schemas.clone(),
|
||||||
|
rows: build_rows_for_key("b", 0, 2, 0),
|
||||||
|
};
|
||||||
|
put_rows(&engine, region_id, rows).await;
|
||||||
|
flush_region(&engine, region_id, None).await;
|
||||||
|
|
||||||
|
// Memtable.
|
||||||
|
// a, field 2, 3
|
||||||
|
let rows = Rows {
|
||||||
|
schema: column_schemas,
|
||||||
|
rows: build_rows_for_key("a", 2, 4, 2),
|
||||||
|
};
|
||||||
|
put_rows(&engine, region_id, rows).await;
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
+-------+---------+---------------------+
|
||||||
|
| tag_0 | field_0 | ts |
|
||||||
|
+-------+---------+---------------------+
|
||||||
|
| a | 3.0 | 1970-01-01T00:00:03 |
|
||||||
|
| b | 1.0 | 1970-01-01T00:00:01 |
|
||||||
|
+-------+---------+---------------------+";
|
||||||
|
// Scans in parallel.
|
||||||
|
let scanner = engine
|
||||||
|
.scanner(
|
||||||
|
region_id,
|
||||||
|
ScanRequest {
|
||||||
|
series_row_selector: Some(TimeSeriesRowSelector::LastRow),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(3, scanner.num_files());
|
||||||
|
assert_eq!(1, scanner.num_memtables());
|
||||||
|
let stream = scanner.scan().await.unwrap();
|
||||||
|
let batches = RecordBatches::try_collect(stream).await.unwrap();
|
||||||
|
assert_eq!(expected, sort_batches_and_print(&batches, &["tag_0", "ts"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_last_row_append_mode_disabled() {
|
||||||
|
test_last_row(false).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_last_row_append_mode_enabled() {
|
||||||
|
test_last_row(true).await;
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
pub mod compat;
|
pub mod compat;
|
||||||
pub mod dedup;
|
pub mod dedup;
|
||||||
|
pub(crate) mod last_row;
|
||||||
pub mod merge;
|
pub mod merge;
|
||||||
pub mod projection;
|
pub mod projection;
|
||||||
pub(crate) mod scan_region;
|
pub(crate) mod scan_region;
|
||||||
|
|||||||
153
src/mito2/src/read/last_row.rs
Normal file
153
src/mito2/src/read/last_row.rs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
// Copyright 2023 Greptime Team
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! Utilities to read the last row of each time series.
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::read::{Batch, BatchReader, BoxedBatchReader};
|
||||||
|
|
||||||
|
/// Reader to keep the last row for each time series.
|
||||||
|
/// It assumes that batches from the input reader are
|
||||||
|
/// - sorted
|
||||||
|
/// - all deleted rows has been filtered.
|
||||||
|
/// - not empty
|
||||||
|
///
|
||||||
|
/// This reader is different from the [MergeMode](crate::region::options::MergeMode) as
|
||||||
|
/// it focus on time series (the same key).
|
||||||
|
pub(crate) struct LastRowReader {
|
||||||
|
/// Inner reader.
|
||||||
|
reader: BoxedBatchReader,
|
||||||
|
/// The last batch pending to return.
|
||||||
|
last_batch: Option<Batch>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LastRowReader {
|
||||||
|
/// Creates a new `LastRowReader`.
|
||||||
|
pub(crate) fn new(reader: BoxedBatchReader) -> Self {
|
||||||
|
Self {
|
||||||
|
reader,
|
||||||
|
last_batch: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the last row of the next key.
|
||||||
|
pub(crate) async fn next_last_row(&mut self) -> Result<Option<Batch>> {
|
||||||
|
while let Some(batch) = self.reader.next_batch().await? {
|
||||||
|
if let Some(last) = &self.last_batch {
|
||||||
|
if last.primary_key() == batch.primary_key() {
|
||||||
|
// Same key, update last batch.
|
||||||
|
self.last_batch = Some(batch);
|
||||||
|
} else {
|
||||||
|
// Different key, return the last row in `last` and update `last_batch` by
|
||||||
|
// current batch.
|
||||||
|
debug_assert!(!last.is_empty());
|
||||||
|
let last_row = last.slice(last.num_rows() - 1, 1);
|
||||||
|
self.last_batch = Some(batch);
|
||||||
|
return Ok(Some(last_row));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.last_batch = Some(batch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(last) = self.last_batch.take() {
|
||||||
|
// This is the last key.
|
||||||
|
let last_row = last.slice(last.num_rows() - 1, 1);
|
||||||
|
return Ok(Some(last_row));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl BatchReader for LastRowReader {
|
||||||
|
async fn next_batch(&mut self) -> Result<Option<Batch>> {
|
||||||
|
self.next_last_row().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use api::v1::OpType;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::test_util::{check_reader_result, new_batch, VecBatchReader};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_last_row_one_batch() {
|
||||||
|
let input = [new_batch(
|
||||||
|
b"k1",
|
||||||
|
&[1, 2],
|
||||||
|
&[11, 11],
|
||||||
|
&[OpType::Put, OpType::Put],
|
||||||
|
&[21, 22],
|
||||||
|
)];
|
||||||
|
let reader = VecBatchReader::new(&input);
|
||||||
|
let mut reader = LastRowReader::new(Box::new(reader));
|
||||||
|
check_reader_result(
|
||||||
|
&mut reader,
|
||||||
|
&[new_batch(b"k1", &[2], &[11], &[OpType::Put], &[22])],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Only one row.
|
||||||
|
let input = [new_batch(b"k1", &[1], &[11], &[OpType::Put], &[21])];
|
||||||
|
let reader = VecBatchReader::new(&input);
|
||||||
|
let mut reader = LastRowReader::new(Box::new(reader));
|
||||||
|
check_reader_result(
|
||||||
|
&mut reader,
|
||||||
|
&[new_batch(b"k1", &[1], &[11], &[OpType::Put], &[21])],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_last_row_multi_batch() {
|
||||||
|
let input = [
|
||||||
|
new_batch(
|
||||||
|
b"k1",
|
||||||
|
&[1, 2],
|
||||||
|
&[11, 11],
|
||||||
|
&[OpType::Put, OpType::Put],
|
||||||
|
&[21, 22],
|
||||||
|
),
|
||||||
|
new_batch(
|
||||||
|
b"k1",
|
||||||
|
&[3, 4],
|
||||||
|
&[11, 11],
|
||||||
|
&[OpType::Put, OpType::Put],
|
||||||
|
&[23, 24],
|
||||||
|
),
|
||||||
|
new_batch(
|
||||||
|
b"k2",
|
||||||
|
&[1, 2],
|
||||||
|
&[11, 11],
|
||||||
|
&[OpType::Put, OpType::Put],
|
||||||
|
&[31, 32],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
let reader = VecBatchReader::new(&input);
|
||||||
|
let mut reader = LastRowReader::new(Box::new(reader));
|
||||||
|
check_reader_result(
|
||||||
|
&mut reader,
|
||||||
|
&[
|
||||||
|
new_batch(b"k1", &[4], &[11], &[OpType::Put], &[24]),
|
||||||
|
new_batch(b"k2", &[2], &[11], &[OpType::Put], &[32]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -209,8 +209,8 @@ impl ScanRegion {
|
|||||||
|
|
||||||
/// Returns a [Scanner] to scan the region.
|
/// Returns a [Scanner] to scan the region.
|
||||||
pub(crate) fn scanner(self) -> Result<Scanner> {
|
pub(crate) fn scanner(self) -> Result<Scanner> {
|
||||||
if self.version.options.append_mode {
|
if self.version.options.append_mode && self.request.series_row_selector.is_none() {
|
||||||
// If table uses append mode, we use unordered scan in query.
|
// If table is append only and there is no series row selector, we use unordered scan in query.
|
||||||
// We still use seq scan in compaction.
|
// We still use seq scan in compaction.
|
||||||
self.unordered_scan().map(Scanner::Unordered)
|
self.unordered_scan().map(Scanner::Unordered)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -29,13 +29,14 @@ use datatypes::schema::SchemaRef;
|
|||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
use snafu::ResultExt;
|
use snafu::ResultExt;
|
||||||
use store_api::region_engine::{PartitionRange, RegionScanner, ScannerProperties};
|
use store_api::region_engine::{PartitionRange, RegionScanner, ScannerProperties};
|
||||||
use store_api::storage::ColumnId;
|
use store_api::storage::{ColumnId, TimeSeriesRowSelector};
|
||||||
use table::predicate::Predicate;
|
use table::predicate::Predicate;
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
|
|
||||||
use crate::error::{PartitionOutOfRangeSnafu, Result};
|
use crate::error::{PartitionOutOfRangeSnafu, Result};
|
||||||
use crate::memtable::MemtableRef;
|
use crate::memtable::MemtableRef;
|
||||||
use crate::read::dedup::{DedupReader, LastNonNull, LastRow};
|
use crate::read::dedup::{DedupReader, LastNonNull, LastRow};
|
||||||
|
use crate::read::last_row::LastRowReader;
|
||||||
use crate::read::merge::MergeReaderBuilder;
|
use crate::read::merge::MergeReaderBuilder;
|
||||||
use crate::read::scan_region::{
|
use crate::read::scan_region::{
|
||||||
FileRangeCollector, ScanInput, ScanPart, ScanPartList, StreamContext,
|
FileRangeCollector, ScanInput, ScanPart, ScanPartList, StreamContext,
|
||||||
@@ -210,8 +211,8 @@ impl SeqScan {
|
|||||||
let reader = builder.build().await?;
|
let reader = builder.build().await?;
|
||||||
|
|
||||||
let dedup = !stream_ctx.input.append_mode;
|
let dedup = !stream_ctx.input.append_mode;
|
||||||
if dedup {
|
let reader = if dedup {
|
||||||
let reader = match stream_ctx.input.merge_mode {
|
match stream_ctx.input.merge_mode {
|
||||||
MergeMode::LastRow => Box::new(DedupReader::new(
|
MergeMode::LastRow => Box::new(DedupReader::new(
|
||||||
reader,
|
reader,
|
||||||
LastRow::new(stream_ctx.input.filter_deleted),
|
LastRow::new(stream_ctx.input.filter_deleted),
|
||||||
@@ -220,12 +221,17 @@ impl SeqScan {
|
|||||||
reader,
|
reader,
|
||||||
LastNonNull::new(stream_ctx.input.filter_deleted),
|
LastNonNull::new(stream_ctx.input.filter_deleted),
|
||||||
)) as _,
|
)) as _,
|
||||||
};
|
}
|
||||||
Ok(Some(reader))
|
|
||||||
} else {
|
} else {
|
||||||
let reader = Box::new(reader);
|
Box::new(reader) as _
|
||||||
Ok(Some(reader))
|
};
|
||||||
}
|
|
||||||
|
let reader = match &stream_ctx.input.series_row_selector {
|
||||||
|
Some(TimeSeriesRowSelector::LastRow) => Box::new(LastRowReader::new(reader)) as _,
|
||||||
|
None => reader,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(reader))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scans the given partition when the part list is set properly.
|
/// Scans the given partition when the part list is set properly.
|
||||||
|
|||||||
Reference in New Issue
Block a user