starrocks/be/test/storage/rowset_merger_test.cpp

441 lines
18 KiB
C++

// This file is licensed under the Elastic License 2.0. Copyright 2021-present, StarRocks Limited.
#include "storage/rowset_merger.h"
#include <gtest/gtest.h>
#include "gutil/strings/substitute.h"
#include "runtime/global_dict/types.h"
#include "storage/chunk_helper.h"
#include "storage/empty_iterator.h"
#include "storage/primary_key_encoder.h"
#include "storage/rowset/rowset_factory.h"
#include "storage/rowset/rowset_meta.h"
#include "storage/rowset/rowset_options.h"
#include "storage/rowset/rowset_writer.h"
#include "storage/rowset/rowset_writer_context.h"
#include "storage/storage_engine.h"
#include "storage/tablet_manager.h"
#include "storage/tablet_reader.h"
#include "storage/union_iterator.h"
#include "storage/update_manager.h"
#include "testutil/assert.h"
namespace starrocks::vectorized {
class TestRowsetWriter : public RowsetWriter {
public:
~TestRowsetWriter() = default;
Status init() override { return Status::OK(); }
Status add_chunk(const vectorized::Chunk& chunk) override { return Status::NotSupported(""); }
Status flush_chunk(const vectorized::Chunk& chunk) override { return Status::NotSupported(""); }
Status flush_chunk_with_deletes(const vectorized::Chunk& upserts, const vectorized::Column& deletes) override {
return Status::NotSupported("");
}
Status add_rowset(RowsetSharedPtr rowset) override { return Status::NotSupported(""); }
Status add_rowset_for_linked_schema_change(RowsetSharedPtr rowset, const SchemaMapping& schema_mapping) override {
return Status::NotSupported("");
}
StatusOr<RowsetSharedPtr> build() override { return RowsetSharedPtr(); }
Version version() override { return Version(); }
int64_t num_rows() override { return all_pks->size(); }
int64_t total_data_size() override { return 0; }
RowsetId rowset_id() override { return RowsetId(); }
Status flush() override { return Status::OK(); }
Status flush_columns() override { return Status::OK(); }
Status final_flush() override { return Status::OK(); }
Status add_chunk_with_rssid(const vectorized::Chunk& chunk, const vector<uint32_t>& rssid) {
all_pks->append(*chunk.get_column_by_index(0), 0, chunk.num_rows());
all_rssids.insert(all_rssids.end(), rssid.begin(), rssid.end());
return Status::OK();
}
Status add_columns(const vectorized::Chunk& chunk, const std::vector<uint32_t>& column_indexes, bool is_key) {
if (is_key) {
all_pks->append(*chunk.get_column_by_index(0), 0, chunk.num_rows());
} else {
for (size_t i = 0; i < column_indexes.size(); ++i) {
auto column_index = column_indexes[i];
DCHECK_LT(column_index - 1, non_key_columns.size());
non_key_columns[column_index - 1]->append(*chunk.get_column_by_index(i), 0, chunk.num_rows());
}
}
return Status::OK();
}
Status add_columns_with_rssid(const vectorized::Chunk& chunk, const std::vector<uint32_t>& column_indexes,
const std::vector<uint32_t>& rssid) {
RETURN_IF_ERROR(add_columns(chunk, column_indexes, true));
all_rssids.insert(all_rssids.end(), rssid.begin(), rssid.end());
return Status::OK();
}
const vectorized::DictColumnsValidMap& global_dict_columns_valid_info() const override {
return _global_dict_columns_valid_info;
}
std::unique_ptr<Column> all_pks;
vector<uint32_t> all_rssids;
vector<std::unique_ptr<Column>> non_key_columns;
vectorized::DictColumnsValidMap _global_dict_columns_valid_info;
};
class RowsetMergerTest : public testing::Test {
public:
RowsetSharedPtr create_rowset(const TabletSharedPtr& tablet, const vector<int64_t>& keys,
vectorized::Column* one_delete = nullptr) {
// TODO(cbl): test multi-segment rowsets
RowsetWriterContext writer_context(kDataFormatV2, config::storage_format_version);
RowsetId rowset_id = StorageEngine::instance()->next_rowset_id();
writer_context.rowset_id = rowset_id;
writer_context.tablet_id = tablet->tablet_id();
writer_context.tablet_schema_hash = tablet->schema_hash();
writer_context.partition_id = 0;
writer_context.rowset_path_prefix = tablet->schema_hash_path();
writer_context.rowset_state = COMMITTED;
writer_context.tablet_schema = &tablet->tablet_schema();
writer_context.version.first = 0;
writer_context.version.second = 0;
writer_context.segments_overlap = NONOVERLAPPING;
std::unique_ptr<RowsetWriter> writer;
EXPECT_TRUE(RowsetFactory::create_rowset_writer(writer_context, &writer).ok());
auto schema = vectorized::ChunkHelper::convert_schema_to_format_v2(_tablet->tablet_schema());
auto chunk = vectorized::ChunkHelper::new_chunk(schema, keys.size());
auto& cols = chunk->columns();
for (size_t i = 0; i < keys.size(); i++) {
cols[0]->append_datum(vectorized::Datum(keys[i]));
cols[1]->append_datum(vectorized::Datum((int16_t)(keys[i] % 100 + 1)));
cols[2]->append_datum(vectorized::Datum((int32_t)(keys[i] % 1000 + 2)));
}
if (one_delete == nullptr && !keys.empty()) {
CHECK_OK(writer->flush_chunk(*chunk));
} else if (one_delete == nullptr) {
CHECK_OK(writer->flush());
} else if (one_delete != nullptr) {
CHECK_OK(writer->flush_chunk_with_deletes(*chunk, *one_delete));
}
return *writer->build();
}
void create_tablet(int64_t tablet_id, int32_t schema_hash) {
TCreateTabletReq request;
request.tablet_id = tablet_id;
request.__set_version(1);
request.__set_version_hash(0);
request.tablet_schema.schema_hash = schema_hash;
request.tablet_schema.short_key_column_count = 6;
request.tablet_schema.keys_type = TKeysType::PRIMARY_KEYS;
request.tablet_schema.storage_type = TStorageType::COLUMN;
TColumn k1;
k1.column_name = "pk";
k1.__set_is_key(true);
k1.column_type.type = TPrimitiveType::BIGINT;
request.tablet_schema.columns.push_back(k1);
TColumn k2;
k2.column_name = "v1";
k2.__set_is_key(false);
k2.column_type.type = TPrimitiveType::SMALLINT;
request.tablet_schema.columns.push_back(k2);
TColumn k3;
k3.column_name = "v2";
k3.__set_is_key(false);
k3.column_type.type = TPrimitiveType::INT;
request.tablet_schema.columns.push_back(k3);
auto st = StorageEngine::instance()->create_tablet(request);
ASSERT_TRUE(st.ok()) << st.to_string();
_tablet = StorageEngine::instance()->tablet_manager()->get_tablet(tablet_id);
ASSERT_TRUE(_tablet);
}
void TearDown() override {
if (_tablet) {
StorageEngine::instance()->tablet_manager()->drop_tablet(_tablet->tablet_id());
_tablet.reset();
}
}
protected:
TabletSharedPtr _tablet;
};
static vectorized::ChunkIteratorPtr create_tablet_iterator(vectorized::TabletReader& reader,
vectorized::Schema& schema) {
vectorized::TabletReaderParams params;
if (!reader.prepare().ok()) {
LOG(ERROR) << "reader prepare failed";
return nullptr;
}
std::vector<ChunkIteratorPtr> seg_iters;
if (!reader.get_segment_iterators(params, &seg_iters).ok()) {
LOG(ERROR) << "reader get segment iterators fail";
return nullptr;
}
if (seg_iters.empty()) {
return vectorized::new_empty_iterator(schema, DEFAULT_CHUNK_SIZE);
}
return vectorized::new_union_iterator(seg_iters);
}
static ssize_t read_until_eof(const vectorized::ChunkIteratorPtr& iter) {
auto chunk = vectorized::ChunkHelper::new_chunk(iter->schema(), 100);
size_t count = 0;
while (true) {
auto st = iter->get_next(chunk.get());
if (st.is_end_of_file()) {
break;
} else if (st.ok()) {
count += chunk->num_rows();
chunk->reset();
} else {
return -1;
}
}
return count;
}
static ssize_t read_tablet(const TabletSharedPtr& tablet, int64_t version) {
vectorized::Schema schema = vectorized::ChunkHelper::convert_schema_to_format_v2(tablet->tablet_schema());
vectorized::TabletReader reader(tablet, Version(0, version), schema);
auto iter = create_tablet_iterator(reader, schema);
if (iter == nullptr) {
return -1;
}
return read_until_eof(iter);
}
TEST_F(RowsetMergerTest, horizontal_merge) {
config::vertical_compaction_max_columns_per_group = 5;
srand(GetCurrentTimeMicros());
create_tablet(rand(), rand());
const int max_segments = 8;
const int num_segment = 1 + rand() % max_segments;
const int N = 500000 + rand() % 1000000;
MergeConfig cfg;
cfg.chunk_size = 1000 + rand() % 2000;
LOG(INFO) << "merge test #rowset:" << num_segment << " #row:" << N << " chunk_size:" << cfg.chunk_size;
vector<uint32_t> rssids(N);
vector<vector<int64_t>> segments(num_segment);
for (int i = 0; i < N; i++) {
rssids[i] = rand() % num_segment;
segments[rssids[i]].push_back(i);
}
vector<RowsetSharedPtr> rowsets(num_segment * 2);
for (int i = 0; i < num_segment; i++) {
auto rs = create_rowset(_tablet, segments[i]);
ASSERT_TRUE(_tablet->rowset_commit(i + 2, rs).ok());
rowsets[i] = rs;
}
std::vector<int64_t> pks;
for (int i = 0; i < num_segment; i++) {
vectorized::Int64Column deletes;
deletes.append_numbers(segments[i].data(), sizeof(int64_t) * segments[i].size() / 2);
auto rs = create_rowset(_tablet, {}, &deletes);
ASSERT_TRUE(_tablet->rowset_commit(i + 2 + num_segment, rs).ok());
rowsets[i + num_segment] = rs;
pks.insert(pks.end(), segments[i].begin() + segments[i].size() / 2, segments[i].end());
}
std::sort(pks.begin(), pks.end());
int64_t version = num_segment * 2 + 1;
EXPECT_EQ(pks.size(), read_tablet(_tablet, version));
TestRowsetWriter writer;
Schema schema = ChunkHelper::convert_schema_to_format_v2(_tablet->tablet_schema());
ASSERT_TRUE(PrimaryKeyEncoder::create_column(schema, &writer.all_pks).ok());
ASSERT_TRUE(vectorized::compaction_merge_rowsets(*_tablet, version, rowsets, &writer, cfg).ok());
ASSERT_EQ(pks.size(), writer.all_pks->size());
const int64_t* raw_pk_array = reinterpret_cast<const int64_t*>(writer.all_pks->raw_data());
for (int64_t i = 0; i < pks.size(); i++) {
ASSERT_EQ(pks[i], raw_pk_array[i]);
}
}
TEST_F(RowsetMergerTest, vertical_merge) {
config::vertical_compaction_max_columns_per_group = 1;
srand(GetCurrentTimeMicros());
create_tablet(rand(), rand());
const int max_segments = 8;
const int num_segment = 2 + rand() % max_segments;
const int N = 500000 + rand() % 1000000;
MergeConfig cfg;
cfg.chunk_size = 1000 + rand() % 2000;
cfg.algorithm = VERTICAL_COMPACTION;
vector<uint32_t> rssids(N);
vector<vector<int64_t>> segments(num_segment);
for (int i = 0; i < N; i++) {
rssids[i] = rand() % num_segment;
segments[rssids[i]].push_back(i);
}
vector<RowsetSharedPtr> rowsets(num_segment * 2);
for (int i = 0; i < num_segment; i++) {
auto rs = create_rowset(_tablet, segments[i]);
ASSERT_TRUE(_tablet->rowset_commit(i + 2, rs).ok());
rowsets[i] = rs;
}
std::vector<int64_t> pks;
for (int i = 0; i < num_segment; i++) {
vectorized::Int64Column deletes;
deletes.append_numbers(segments[i].data(), sizeof(int64_t) * segments[i].size() / 2);
auto rs = create_rowset(_tablet, {}, &deletes);
ASSERT_TRUE(_tablet->rowset_commit(i + 2 + num_segment, rs).ok());
rowsets[i + num_segment] = rs;
pks.insert(pks.end(), segments[i].begin() + segments[i].size() / 2, segments[i].end());
}
std::sort(pks.begin(), pks.end());
int64_t version = num_segment * 2 + 1;
EXPECT_EQ(pks.size(), read_tablet(_tablet, version));
TestRowsetWriter writer;
Schema schema = ChunkHelper::convert_schema_to_format_v2(_tablet->tablet_schema());
ASSERT_TRUE(PrimaryKeyEncoder::create_column(schema, &writer.all_pks).ok());
writer.non_key_columns.emplace_back(std::move(vectorized::Int16Column::create_mutable()));
writer.non_key_columns.emplace_back(std::move(vectorized::Int32Column::create_mutable()));
ASSERT_TRUE(vectorized::compaction_merge_rowsets(*_tablet, version, rowsets, &writer, cfg).ok());
ASSERT_EQ(pks.size(), writer.all_pks->size());
ASSERT_EQ(2, writer.non_key_columns.size());
ASSERT_EQ(pks.size(), writer.non_key_columns[0]->size());
ASSERT_EQ(pks.size(), writer.non_key_columns[1]->size());
const int64_t* raw_pk_array = reinterpret_cast<const int64_t*>(writer.all_pks->raw_data());
const int16_t* raw_k2_array = reinterpret_cast<const int16_t*>(writer.non_key_columns[0]->raw_data());
const int32_t* raw_k3_array = reinterpret_cast<const int32_t*>(writer.non_key_columns[1]->raw_data());
for (int64_t i = 0; i < pks.size(); i++) {
ASSERT_EQ(pks[i], raw_pk_array[i]);
ASSERT_EQ(pks[i] % 100 + 1, raw_k2_array[i]);
ASSERT_EQ(pks[i] % 1000 + 2, raw_k3_array[i]);
}
}
TEST_F(RowsetMergerTest, horizontal_merge_seq) {
config::vertical_compaction_max_columns_per_group = 5;
srand(GetCurrentTimeMicros());
create_tablet(rand(), rand());
const int max_segments = 8;
const int num_segment = 1 + rand() % max_segments;
const int N = 500000 + rand() % 1000000;
MergeConfig cfg;
cfg.chunk_size = 100 + rand() % 2000;
// small size test
// const int num_segment = 3;
// const int N = 30;
// MergeConfig cfg;
// cfg.chunk_size = 20;
LOG(INFO) << "seq merge test #rowset:" << num_segment << " #row:" << N << " chunk_size:" << cfg.chunk_size;
vector<uint32_t> rssids(N);
vector<vector<int64_t>> segments(num_segment);
for (int i = 0; i < N; i++) {
rssids[i] = num_segment * i / N;
segments[rssids[i]].push_back(i);
}
vector<RowsetSharedPtr> rowsets(num_segment * 2);
for (int i = 0; i < num_segment; i++) {
auto rs = create_rowset(_tablet, segments[i]);
ASSERT_TRUE(_tablet->rowset_commit(i + 2, rs).ok());
rowsets[i] = rs;
}
std::vector<int64_t> pks;
for (int i = 0; i < num_segment; i++) {
vectorized::Int64Column deletes;
deletes.append_numbers(segments[i].data(), sizeof(int64_t) * segments[i].size() / 2);
auto rs = create_rowset(_tablet, {}, &deletes);
ASSERT_TRUE(_tablet->rowset_commit(i + 2 + num_segment, rs).ok());
rowsets[i + num_segment] = rs;
pks.insert(pks.end(), segments[i].begin() + segments[i].size() / 2, segments[i].end());
}
std::sort(pks.begin(), pks.end());
int64_t version = num_segment * 2 + 1;
EXPECT_EQ(pks.size(), read_tablet(_tablet, version));
TestRowsetWriter writer;
Schema schema = ChunkHelper::convert_schema_to_format_v2(_tablet->tablet_schema());
ASSERT_TRUE(PrimaryKeyEncoder::create_column(schema, &writer.all_pks).ok());
ASSERT_TRUE(vectorized::compaction_merge_rowsets(*_tablet, version, rowsets, &writer, cfg).ok());
ASSERT_EQ(pks.size(), writer.all_pks->size());
const int64_t* raw_pk_array = reinterpret_cast<const int64_t*>(writer.all_pks->raw_data());
for (int64_t i = 0; i < pks.size(); i++) {
ASSERT_EQ(pks[i], raw_pk_array[i]);
}
}
TEST_F(RowsetMergerTest, vertical_merge_seq) {
config::vertical_compaction_max_columns_per_group = 1;
srand(GetCurrentTimeMicros());
create_tablet(rand(), rand());
const int max_segments = 8;
const int num_segment = 2 + rand() % max_segments;
const int N = 500000 + rand() % 1000000;
MergeConfig cfg;
cfg.chunk_size = 100 + rand() % 2000;
cfg.algorithm = VERTICAL_COMPACTION;
vector<uint32_t> rssids(N);
vector<vector<int64_t>> segments(num_segment);
for (int i = 0; i < N; i++) {
rssids[i] = num_segment * i / N;
segments[rssids[i]].push_back(i);
}
vector<RowsetSharedPtr> rowsets(num_segment * 2);
for (int i = 0; i < num_segment; i++) {
auto rs = create_rowset(_tablet, segments[i]);
ASSERT_TRUE(_tablet->rowset_commit(i + 2, rs).ok());
rowsets[i] = rs;
}
std::vector<int64_t> pks;
for (int i = 0; i < num_segment; i++) {
vectorized::Int64Column deletes;
deletes.append_numbers(segments[i].data(), sizeof(int64_t) * segments[i].size() / 2);
auto rs = create_rowset(_tablet, {}, &deletes);
ASSERT_TRUE(_tablet->rowset_commit(i + 2 + num_segment, rs).ok());
rowsets[i + num_segment] = rs;
pks.insert(pks.end(), segments[i].begin() + segments[i].size() / 2, segments[i].end());
}
std::sort(pks.begin(), pks.end());
int64_t version = num_segment * 2 + 1;
EXPECT_EQ(pks.size(), read_tablet(_tablet, version));
TestRowsetWriter writer;
Schema schema = ChunkHelper::convert_schema_to_format_v2(_tablet->tablet_schema());
ASSERT_TRUE(PrimaryKeyEncoder::create_column(schema, &writer.all_pks).ok());
writer.non_key_columns.emplace_back(std::move(vectorized::Int16Column::create_mutable()));
writer.non_key_columns.emplace_back(std::move(vectorized::Int32Column::create_mutable()));
ASSERT_TRUE(vectorized::compaction_merge_rowsets(*_tablet, version, rowsets, &writer, cfg).ok());
ASSERT_EQ(pks.size(), writer.all_pks->size());
ASSERT_EQ(2, writer.non_key_columns.size());
ASSERT_EQ(pks.size(), writer.non_key_columns[0]->size());
ASSERT_EQ(pks.size(), writer.non_key_columns[1]->size());
const int64_t* raw_pk_array = reinterpret_cast<const int64_t*>(writer.all_pks->raw_data());
const int16_t* raw_k2_array = reinterpret_cast<const int16_t*>(writer.non_key_columns[0]->raw_data());
const int32_t* raw_k3_array = reinterpret_cast<const int32_t*>(writer.non_key_columns[1]->raw_data());
for (int64_t i = 0; i < pks.size(); i++) {
ASSERT_EQ(pks[i], raw_pk_array[i]);
ASSERT_EQ(pks[i] % 100 + 1, raw_k2_array[i]);
ASSERT_EQ(pks[i] % 1000 + 2, raw_k3_array[i]);
}
}
} // namespace starrocks::vectorized