From 8781b0fcb3394ebd7817fd1ddab713021076d242 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Mon, 9 May 2022 20:12:34 +0800 Subject: [PATCH 001/127] Add a metric to statistics expansion or reused. (#4619) ref pingcap/tiflash#3594 --- dbms/src/Common/ProfileEvents.cpp | 2 + dbms/src/Storages/Page/V3/BlobStore.cpp | 6 +- dbms/src/Storages/Page/V3/spacemap/SpaceMap.h | 7 +- .../Page/V3/spacemap/SpaceMapRBTree.cpp | 33 +++-- .../Page/V3/spacemap/SpaceMapRBTree.h | 2 +- .../Page/V3/spacemap/SpaceMapSTDMap.h | 25 ++-- .../Storages/Page/V3/tests/gtest_free_map.cpp | 46 ++++++- metrics/grafana/tiflash_summary.json | 121 ++++++++++++++++++ 8 files changed, 214 insertions(+), 28 deletions(-) diff --git a/dbms/src/Common/ProfileEvents.cpp b/dbms/src/Common/ProfileEvents.cpp index 1b9b62dd2c6..0ec1ce438a6 100644 --- a/dbms/src/Common/ProfileEvents.cpp +++ b/dbms/src/Common/ProfileEvents.cpp @@ -110,6 +110,8 @@ \ M(PSMWritePages) \ M(PSMWriteIOCalls) \ + M(PSV3MBlobExpansion) \ + M(PSV3MBlobReused) \ M(PSMWriteBytes) \ M(PSMBackgroundWriteBytes) \ M(PSMReadPages) \ diff --git a/dbms/src/Storages/Page/V3/BlobStore.cpp b/dbms/src/Storages/Page/V3/BlobStore.cpp index b95a4521af7..2c568d8e85b 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.cpp +++ b/dbms/src/Storages/Page/V3/BlobStore.cpp @@ -36,6 +36,8 @@ namespace ProfileEvents { extern const Event PSMWritePages; extern const Event PSMReadPages; +extern const Event PSV3MBlobExpansion; +extern const Event PSV3MBlobReused; } // namespace ProfileEvents namespace DB @@ -1243,8 +1245,10 @@ BlobFileOffset BlobStore::BlobStats::BlobStat::getPosFromStat(size_t buf_size, c { BlobFileOffset offset = 0; UInt64 max_cap = 0; + bool expansion = true; - std::tie(offset, max_cap) = smap->searchInsertOffset(buf_size); + std::tie(offset, max_cap, expansion) = smap->searchInsertOffset(buf_size); + ProfileEvents::increment(expansion ? ProfileEvents::PSV3MBlobExpansion : ProfileEvents::PSV3MBlobReused); /** * Whatever `searchInsertOffset` success or failed, diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h b/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h index e4af33c5a81..4a0c035cd3f 100644 --- a/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h @@ -86,10 +86,11 @@ class SpaceMap * It will mark that span to be used and also return a hint of the max capacity available in this SpaceMap. * * return value is : - * insert_offset : start offset for the inserted space - * max_cap : A hint of the largest available space this SpaceMap can hold. + * insert_offset: start offset for the inserted space + * max_cap: A hint of the largest available space this SpaceMap can hold. + * is_expansion: Whether it is an expansion span */ - virtual std::pair searchInsertOffset(size_t size) = 0; + virtual std::tuple searchInsertOffset(size_t size) = 0; /** * Get the offset of the last free block. `[margin_offset, +∞)` is not used at all. diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.cpp b/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.cpp index 3b4c6a28099..54275574060 100644 --- a/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.cpp +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.cpp @@ -304,7 +304,7 @@ static bool rb_remove_entry(UInt64 start, UInt64 count, struct RbPrivate * priva // Root node have not been init if (private_data->root.rb_node == nullptr) { - assert(false); + LOG_ERROR(log, "Current spacemap is invalid."); } while (*n) @@ -500,7 +500,7 @@ bool RBTreeSpaceMap::isMarkUnused(UInt64 offset, size_t length) if (length == 0 || rb_tree->root.rb_node == nullptr) { - assert(0); + LOG_ERROR(log, "Current spacemap is invalid."); } while (*n) @@ -543,12 +543,12 @@ bool RBTreeSpaceMap::isMarkUnused(UInt64 offset, size_t length) return retval; } -std::pair RBTreeSpaceMap::searchInsertOffset(size_t size) +std::tuple RBTreeSpaceMap::searchInsertOffset(size_t size) { - UInt64 offset = UINT64_MAX; + UInt64 offset = UINT64_MAX, last_offset = UINT64_MAX; UInt64 max_cap = 0; - struct rb_node * node = nullptr; - struct SmapRbEntry * entry; + struct rb_node *node = nullptr, *last_node = nullptr; + struct SmapRbEntry *entry, *last_entry; UInt64 scan_biggest_cap = 0; UInt64 scan_biggest_offset = 0; @@ -558,7 +558,18 @@ std::pair RBTreeSpaceMap::searchInsertOffset(size_t size) { LOG_ERROR(log, "Current spacemap is full."); biggest_cap = 0; - return std::make_pair(offset, biggest_cap); + return std::make_tuple(offset, biggest_cap, false); + } + + last_node = rb_tree_last(&rb_tree->root); + if (last_node != nullptr) + { + last_entry = node_to_entry(last_node); + last_offset = (last_entry->start + last_entry->count == end) ? last_entry->start : UINT64_MAX; + } + else + { + LOG_ERROR(log, "Current spacemap is invalid."); } for (; node != nullptr; node = rb_tree_next(node)) @@ -592,7 +603,7 @@ std::pair RBTreeSpaceMap::searchInsertOffset(size_t size) biggest_range = scan_biggest_offset; biggest_cap = scan_biggest_cap; - return std::make_pair(offset, biggest_cap); + return std::make_tuple(offset, biggest_cap, false); } // Update return start @@ -614,7 +625,7 @@ std::pair RBTreeSpaceMap::searchInsertOffset(size_t size) rb_node_remove(node, &rb_tree->root); rb_free_entry(rb_tree, entry); max_cap = biggest_cap; - return std::make_pair(offset, max_cap); + return std::make_tuple(offset, max_cap, offset == last_offset); } } else // must be entry->count > size @@ -637,7 +648,7 @@ std::pair RBTreeSpaceMap::searchInsertOffset(size_t size) else // It not champion, just return { max_cap = biggest_cap; - return std::make_pair(offset, max_cap); + return std::make_tuple(offset, max_cap, offset == last_offset); } } @@ -653,7 +664,7 @@ std::pair RBTreeSpaceMap::searchInsertOffset(size_t size) biggest_range = scan_biggest_offset; biggest_cap = scan_biggest_cap; max_cap = biggest_cap; - return std::make_pair(offset, max_cap); + return std::make_tuple(offset, max_cap, offset == last_offset); } UInt64 RBTreeSpaceMap::updateAccurateMaxCapacity() diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.h b/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.h index 8c53724be7d..0393fda081b 100644 --- a/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.h +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.h @@ -40,7 +40,7 @@ class RBTreeSpaceMap static std::shared_ptr create(UInt64, UInt64 end); - std::pair searchInsertOffset(size_t size) override; + std::tuple searchInsertOffset(size_t size) override; UInt64 updateAccurateMaxCapacity() override; diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h b/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h index b691d0b1d81..92c08deb555 100644 --- a/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h @@ -181,9 +181,9 @@ class STDMapSpaceMap return true; } - std::pair searchInsertOffset(size_t size) override + std::tuple searchInsertOffset(size_t size) override { - UInt64 offset = UINT64_MAX; + UInt64 offset = UINT64_MAX, last_offset = UINT64_MAX; UInt64 max_cap = 0; // The biggest free block capacity and its start offset UInt64 scan_biggest_cap = 0; @@ -193,9 +193,12 @@ class STDMapSpaceMap { LOG_FMT_ERROR(log, "Current space map is full"); hint_biggest_cap = 0; - return std::make_pair(offset, hint_biggest_cap); + return std::make_tuple(offset, hint_biggest_cap, false); } + auto r_it = free_map.rbegin(); + last_offset = (r_it->first + r_it->second == end) ? r_it->first : UINT64_MAX; + auto it = free_map.begin(); for (; it != free_map.end(); it++) { @@ -214,11 +217,17 @@ class STDMapSpaceMap // No enough space for insert if (it == free_map.end()) { - LOG_FMT_ERROR(log, "Not sure why can't found any place to insert. [size={}] [old biggest_offset={}] [old biggest_cap={}] [new biggest_offset={}] [new biggest_cap={}]", size, hint_biggest_offset, hint_biggest_cap, scan_biggest_offset, scan_biggest_cap); + LOG_FMT_ERROR(log, "Not sure why can't found any place to insert." + "[size={}] [old biggest_offset={}] [old biggest_cap={}] [new biggest_offset={}] [new biggest_cap={}]", // + size, + hint_biggest_offset, + hint_biggest_cap, + scan_biggest_offset, + scan_biggest_cap); hint_biggest_offset = scan_biggest_offset; hint_biggest_cap = scan_biggest_cap; - return std::make_pair(offset, hint_biggest_cap); + return std::make_tuple(offset, hint_biggest_cap, false); } // Update return start @@ -231,7 +240,7 @@ class STDMapSpaceMap { free_map.erase(it); max_cap = hint_biggest_cap; - return std::make_pair(offset, max_cap); + return std::make_tuple(offset, max_cap, last_offset == offset); } // It is champion, need to update `scan_biggest_cap`, `scan_biggest_offset` @@ -251,7 +260,7 @@ class STDMapSpaceMap if (k - size != hint_biggest_offset) { max_cap = hint_biggest_cap; - return std::make_pair(offset, max_cap); + return std::make_tuple(offset, max_cap, last_offset == offset); } // It is champion, need to update `scan_biggest_cap`, `scan_biggest_offset` @@ -274,7 +283,7 @@ class STDMapSpaceMap hint_biggest_offset = scan_biggest_offset; hint_biggest_cap = scan_biggest_cap; - return std::make_pair(offset, hint_biggest_cap); + return std::make_tuple(offset, hint_biggest_cap, last_offset == offset); } UInt64 updateAccurateMaxCapacity() override diff --git a/dbms/src/Storages/Page/V3/tests/gtest_free_map.cpp b/dbms/src/Storages/Page/V3/tests/gtest_free_map.cpp index 85a94ec0ac3..f7120f000b2 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_free_map.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_free_map.cpp @@ -283,14 +283,18 @@ TEST_P(SpaceMapTest, TestSearch) auto smap = SpaceMap::createSpaceMap(test_type, 0, 100); UInt64 offset; UInt64 max_cap; + bool expansion = true; + Range ranges[] = {{.start = 0, .end = 100}}; ASSERT_TRUE(smap->check(genChecker(ranges, 1), 1)); ASSERT_TRUE(smap->markUsed(50, 10)); - std::tie(offset, max_cap) = smap->searchInsertOffset(20); + std::tie(offset, max_cap, expansion) = smap->searchInsertOffset(20); + ASSERT_EQ(offset, 0); ASSERT_EQ(max_cap, 40); + ASSERT_EQ(expansion, false); Range ranges1[] = {{.start = 20, .end = 50}, @@ -304,9 +308,10 @@ TEST_P(SpaceMapTest, TestSearch) smap = SpaceMap::createSpaceMap(test_type, 0, 100); ASSERT_TRUE(smap->markUsed(50, 10)); - std::tie(offset, max_cap) = smap->searchInsertOffset(5); + std::tie(offset, max_cap, expansion) = smap->searchInsertOffset(5); ASSERT_EQ(offset, 0); ASSERT_EQ(max_cap, 45); + ASSERT_EQ(expansion, false); Range ranges2[] = {{.start = 5, .end = 50}, @@ -317,9 +322,10 @@ TEST_P(SpaceMapTest, TestSearch) // Test margin smap = SpaceMap::createSpaceMap(test_type, 0, 100); ASSERT_TRUE(smap->markUsed(50, 10)); - std::tie(offset, max_cap) = smap->searchInsertOffset(50); + std::tie(offset, max_cap, expansion) = smap->searchInsertOffset(50); ASSERT_EQ(offset, 0); ASSERT_EQ(max_cap, 40); + ASSERT_EQ(expansion, false); Range ranges3[] = {{.start = 60, .end = 100}}; @@ -328,9 +334,10 @@ TEST_P(SpaceMapTest, TestSearch) // Test invalid Size smap = SpaceMap::createSpaceMap(test_type, 0, 100); ASSERT_TRUE(smap->markUsed(50, 10)); - std::tie(offset, max_cap) = smap->searchInsertOffset(100); + std::tie(offset, max_cap, expansion) = smap->searchInsertOffset(100); ASSERT_EQ(offset, UINT64_MAX); ASSERT_EQ(max_cap, 50); + ASSERT_EQ(expansion, false); // No changed Range ranges4[] = {{.start = 0, @@ -338,6 +345,37 @@ TEST_P(SpaceMapTest, TestSearch) {.start = 60, .end = 100}}; ASSERT_TRUE(smap->check(genChecker(ranges4, 2), 2)); + + // Test expansion + smap = SpaceMap::createSpaceMap(test_type, 0, 100); + std::tie(offset, max_cap, expansion) = smap->searchInsertOffset(10); + ASSERT_EQ(offset, 0); + ASSERT_EQ(max_cap, 90); + ASSERT_EQ(expansion, true); + + std::tie(offset, max_cap, expansion) = smap->searchInsertOffset(10); + ASSERT_EQ(offset, 10); + ASSERT_EQ(max_cap, 80); + ASSERT_EQ(expansion, true); +} + +TEST_P(SpaceMapTest, TestSearchIsExpansion) +{ + auto smap = SpaceMap::createSpaceMap(test_type, 0, 100); + UInt64 offset; + UInt64 max_cap; + bool expansion = true; + + std::tie(offset, max_cap, expansion) = smap->searchInsertOffset(20); + ASSERT_EQ(offset, 0); + ASSERT_EQ(max_cap, 80); + ASSERT_EQ(expansion, true); + + ASSERT_TRUE(smap->markUsed(90, 10)); + std::tie(offset, max_cap, expansion) = smap->searchInsertOffset(20); + ASSERT_EQ(expansion, false); + std::tie(offset, max_cap, expansion) = smap->searchInsertOffset(20); + ASSERT_EQ(expansion, false); } diff --git a/metrics/grafana/tiflash_summary.json b/metrics/grafana/tiflash_summary.json index 364216a28db..a1c75e7c04e 100644 --- a/metrics/grafana/tiflash_summary.json +++ b/metrics/grafana/tiflash_summary.json @@ -5157,6 +5157,127 @@ "align": false, "alignLevel": null } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_TEST-CLUSTER}", + "description": "The states of BlobStore (an internal component of storage engine)", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 69 + }, + "hiddenSeries": false, + "id": 85, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.11", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "BlobAllocated", + "yaxis": 1 + }, + { + "alias": "BlobReusedRate", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(tiflash_system_profile_event_PSV3MBlobExpansion{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m]) + rate(tiflash_system_profile_event_PSV3MBlobReused{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m])) by (instance)", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "BlobAllocated", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum(rate(tiflash_system_profile_event_PSV3MBlobReused{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m]) / (rate(tiflash_system_profile_event_PSV3MBlobExpansion{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m]) + rate(tiflash_system_profile_event_PSV3MBlobReused{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m]))) by (instance)", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "BlobReusedRate", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "The BlobStore Status", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "percentunit", + "label": null, + "logBase": 1, + "max": "1", + "min": "0", + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } } ], "repeat": null, From 0e2b7f7cb1b4bec6df21a7b8469ca8929c54e4c3 Mon Sep 17 00:00:00 2001 From: hehechen Date: Tue, 10 May 2022 11:26:34 +0800 Subject: [PATCH 002/127] MinMax Index Supports Nullable DataType (#4792) close pingcap/tiflash#4787 --- dbms/src/DataTypes/IDataType.h | 1 - .../Storages/DeltaMerge/File/DMFileWriter.cpp | 5 +- .../Storages/DeltaMerge/Index/MinMaxIndex.cpp | 229 ++++++++++++++++-- .../Storages/DeltaMerge/Index/MinMaxIndex.h | 5 +- .../tests/gtest_dm_minmax_index.cpp | 82 ++++++- 5 files changed, 284 insertions(+), 38 deletions(-) diff --git a/dbms/src/DataTypes/IDataType.h b/dbms/src/DataTypes/IDataType.h index 120d0b1ba30..71fda0615e4 100644 --- a/dbms/src/DataTypes/IDataType.h +++ b/dbms/src/DataTypes/IDataType.h @@ -471,7 +471,6 @@ class IDataType : private boost::noncopyable virtual bool isEnum() const { return false; }; virtual bool isNullable() const { return false; } - /** Is this type can represent only NULL value? (It also implies isNullable) */ virtual bool onlyNull() const { return false; } diff --git a/dbms/src/Storages/DeltaMerge/File/DMFileWriter.cpp b/dbms/src/Storages/DeltaMerge/File/DMFileWriter.cpp index 3bff05ef19f..424e3f1b0c1 100644 --- a/dbms/src/Storages/DeltaMerge/File/DMFileWriter.cpp +++ b/dbms/src/Storages/DeltaMerge/File/DMFileWriter.cpp @@ -72,10 +72,9 @@ DMFileWriter::DMFileWriter(const DMFilePtr & dmfile_, for (auto & cd : write_columns) { // TODO: currently we only generate index for Integers, Date, DateTime types, and this should be configurable by user. - // TODO: If column type is nullable, we won't generate index for it /// for handle column always generate index - bool do_index = cd.id == EXTRA_HANDLE_COLUMN_ID || cd.type->isInteger() || cd.type->isDateOrDateTime(); - + auto type = removeNullable(cd.type); + bool do_index = cd.id == EXTRA_HANDLE_COLUMN_ID || type->isInteger() || type->isDateOrDateTime(); if (options.flags.isSingleFile()) { if (do_index) diff --git a/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.cpp b/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.cpp index 21c53647cfc..ef42a036e1a 100644 --- a/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.cpp +++ b/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.cpp @@ -61,7 +61,6 @@ inline std::pair minmax(const IColumn & column, const ColumnVect void MinMaxIndex::addPack(const IColumn & column, const ColumnVector * del_mark) { - const IColumn * column_ptr = &column; auto size = column.size(); bool has_null = false; if (column.isColumnNullable()) @@ -70,7 +69,6 @@ void MinMaxIndex::addPack(const IColumn & column, const ColumnVector * de const auto & nullable_column = static_cast(column); const auto & null_mark_data = nullable_column.getNullMapColumn().getData(); - column_ptr = &nullable_column.getNestedColumn(); for (size_t i = 0; i < size; ++i) { @@ -82,14 +80,13 @@ void MinMaxIndex::addPack(const IColumn & column, const ColumnVector * de } } - const IColumn & updated_column = *column_ptr; - auto [min_index, max_index] = details::minmax(updated_column, del_mark, 0, updated_column.size()); + auto [min_index, max_index] = details::minmax(column, del_mark, 0, column.size()); if (min_index != NONE_EXIST) { has_null_marks->push_back(has_null); has_value_marks->push_back(1); - minmaxes->insertFrom(updated_column, min_index); - minmaxes->insertFrom(updated_column, max_index); + minmaxes->insertFrom(column, min_index); + minmaxes->insertFrom(column, max_index); } else { @@ -158,6 +155,64 @@ std::pair MinMaxIndex::getUInt64MinMax(size_t pack_index) return {minmaxes->get64(pack_index * 2), minmaxes->get64(pack_index * 2 + 1)}; } +RSResult MinMaxIndex::checkNullableEqual(size_t pack_index, const Field & value, const DataTypePtr & type) +{ + const ColumnNullable & column_nullable = static_cast(*minmaxes); + + const auto * raw_type = type.get(); + + // if minmaxes_data has null value, the value of minmaxes_data[i] is meaningless and maybe just some random value. + // But in checkEqual, we have checked the has_null_marks and ensured that there is no null value in MinMax Indexes. +#define DISPATCH(TYPE) \ + if (typeid_cast(raw_type)) \ + { \ + auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); \ + auto min = minmaxes_data[pack_index * 2]; \ + auto max = minmaxes_data[pack_index * 2 + 1]; \ + return RoughCheck::checkEqual(value, type, min, max); \ + } + FOR_NUMERIC_TYPES(DISPATCH) +#undef DISPATCH + if (typeid_cast(raw_type)) + { + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkEqual(value, type, min, max); + } + if (typeid_cast(raw_type)) + { + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkEqual(value, type, min, max); + } + if (typeid_cast(raw_type) || typeid_cast(raw_type)) + { + // For DataTypeMyDateTime / DataTypeMyDate, simply compare them as comparing UInt64 is OK. + // Check `struct MyTimeBase` for more details. + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkEqual(value, type, min, max); + } + if (typeid_cast(raw_type)) + { + const auto * string_column = checkAndGetColumn(column_nullable.getNestedColumnPtr().get()); + const auto & chars = string_column->getChars(); + const auto & offsets = string_column->getOffsets(); + size_t pos = pack_index * 2; + size_t prev_offset = pos == 0 ? 0 : offsets[pos - 1]; + // todo use StringRef instead of String + auto min = String(chars[prev_offset], offsets[pos] - prev_offset - 1); + pos = pack_index * 2 + 1; + prev_offset = offsets[pos - 1]; + auto max = String(chars[prev_offset], offsets[pos] - prev_offset - 1); + return RoughCheck::checkEqual(value, type, min, max); + } + return RSResult::Some; +} + RSResult MinMaxIndex::checkEqual(size_t pack_index, const Field & value, const DataTypePtr & type) { if ((*has_null_marks)[pack_index] || value.isNull()) @@ -166,6 +221,10 @@ RSResult MinMaxIndex::checkEqual(size_t pack_index, const Field & value, const D return RSResult::None; const auto * raw_type = type.get(); + if (typeid_cast(raw_type)) + { + return checkNullableEqual(pack_index, value, removeNullable(type)); + } #define DISPATCH(TYPE) \ if (typeid_cast(raw_type)) \ { \ @@ -178,14 +237,14 @@ RSResult MinMaxIndex::checkEqual(size_t pack_index, const Field & value, const D #undef DISPATCH if (typeid_cast(raw_type)) { - auto & minmaxes_data = toColumnVectorData(minmaxes); + const auto & minmaxes_data = toColumnVectorData(minmaxes); auto min = minmaxes_data[pack_index * 2]; auto max = minmaxes_data[pack_index * 2 + 1]; return RoughCheck::checkEqual(value, type, min, max); } if (typeid_cast(raw_type)) { - auto & minmaxes_data = toColumnVectorData(minmaxes); + const auto & minmaxes_data = toColumnVectorData(minmaxes); auto min = minmaxes_data[pack_index * 2]; auto max = minmaxes_data[pack_index * 2 + 1]; return RoughCheck::checkEqual(value, type, min, max); @@ -194,16 +253,16 @@ RSResult MinMaxIndex::checkEqual(size_t pack_index, const Field & value, const D { // For DataTypeMyDateTime / DataTypeMyDate, simply compare them as comparing UInt64 is OK. // Check `struct MyTimeBase` for more details. - auto & minmaxes_data = toColumnVectorData(minmaxes); + const auto & minmaxes_data = toColumnVectorData(minmaxes); auto min = minmaxes_data[pack_index * 2]; auto max = minmaxes_data[pack_index * 2 + 1]; return RoughCheck::checkEqual(value, type, min, max); } if (typeid_cast(raw_type)) { - auto * string_column = checkAndGetColumn(minmaxes.get()); - auto & chars = string_column->getChars(); - auto & offsets = string_column->getOffsets(); + const auto * string_column = checkAndGetColumn(minmaxes.get()); + const auto & chars = string_column->getChars(); + const auto & offsets = string_column->getOffsets(); size_t pos = pack_index * 2; size_t prev_offset = pos == 0 ? 0 : offsets[pos - 1]; // todo use StringRef instead of String @@ -215,6 +274,62 @@ RSResult MinMaxIndex::checkEqual(size_t pack_index, const Field & value, const D } return RSResult::Some; } + +RSResult MinMaxIndex::checkNullableGreater(size_t pack_index, const Field & value, const DataTypePtr & type) +{ + const ColumnNullable & column_nullable = static_cast(*minmaxes); + const auto * raw_type = type.get(); + +#define DISPATCH(TYPE) \ + if (typeid_cast(raw_type)) \ + { \ + auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); \ + auto min = minmaxes_data[pack_index * 2]; \ + auto max = minmaxes_data[pack_index * 2 + 1]; \ + return RoughCheck::checkGreater(value, type, min, max); \ + } + FOR_NUMERIC_TYPES(DISPATCH) +#undef DISPATCH + if (typeid_cast(raw_type)) + { + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkGreater(value, type, min, max); + } + if (typeid_cast(raw_type)) + { + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkGreater(value, type, min, max); + } + if (typeid_cast(raw_type) || typeid_cast(raw_type)) + { + // For DataTypeMyDateTime / DataTypeMyDate, simply compare them as comparing UInt64 is OK. + // Check `struct MyTimeBase` for more details. + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkGreater(value, type, min, max); + } + if (typeid_cast(raw_type)) + { + const auto * string_column = checkAndGetColumn(column_nullable.getNestedColumnPtr().get()); + const auto & chars = string_column->getChars(); + const auto & offsets = string_column->getOffsets(); + size_t pos = pack_index * 2; + size_t prev_offset = pos == 0 ? 0 : offsets[pos - 1]; + // todo use StringRef instead of String + auto min = String(chars[prev_offset], offsets[pos] - prev_offset - 1); + pos = pack_index * 2 + 1; + prev_offset = offsets[pos - 1]; + auto max = String(chars[prev_offset], offsets[pos] - prev_offset - 1); + return RoughCheck::checkGreater(value, type, min, max); + } + return RSResult::Some; +} + RSResult MinMaxIndex::checkGreater(size_t pack_index, const Field & value, const DataTypePtr & type, int /*nan_direction_hint*/) { if ((*has_null_marks)[pack_index] || value.isNull()) @@ -223,6 +338,10 @@ RSResult MinMaxIndex::checkGreater(size_t pack_index, const Field & value, const return RSResult::None; const auto * raw_type = type.get(); + if (typeid_cast(raw_type)) + { + return checkNullableGreater(pack_index, value, removeNullable(type)); + } #define DISPATCH(TYPE) \ if (typeid_cast(raw_type)) \ { \ @@ -235,14 +354,14 @@ RSResult MinMaxIndex::checkGreater(size_t pack_index, const Field & value, const #undef DISPATCH if (typeid_cast(raw_type)) { - auto & minmaxes_data = toColumnVectorData(minmaxes); + const auto & minmaxes_data = toColumnVectorData(minmaxes); auto min = minmaxes_data[pack_index * 2]; auto max = minmaxes_data[pack_index * 2 + 1]; return RoughCheck::checkGreater(value, type, min, max); } if (typeid_cast(raw_type)) { - auto & minmaxes_data = toColumnVectorData(minmaxes); + const auto & minmaxes_data = toColumnVectorData(minmaxes); auto min = minmaxes_data[pack_index * 2]; auto max = minmaxes_data[pack_index * 2 + 1]; return RoughCheck::checkGreater(value, type, min, max); @@ -251,16 +370,16 @@ RSResult MinMaxIndex::checkGreater(size_t pack_index, const Field & value, const { // For DataTypeMyDateTime / DataTypeMyDate, simply compare them as comparing UInt64 is OK. // Check `struct MyTimeBase` for more details. - auto & minmaxes_data = toColumnVectorData(minmaxes); + const auto & minmaxes_data = toColumnVectorData(minmaxes); auto min = minmaxes_data[pack_index * 2]; auto max = minmaxes_data[pack_index * 2 + 1]; return RoughCheck::checkGreater(value, type, min, max); } if (typeid_cast(raw_type)) { - auto * string_column = checkAndGetColumn(minmaxes.get()); - auto & chars = string_column->getChars(); - auto & offsets = string_column->getOffsets(); + const auto * string_column = checkAndGetColumn(minmaxes.get()); + const auto & chars = string_column->getChars(); + const auto & offsets = string_column->getOffsets(); size_t pos = pack_index * 2; size_t prev_offset = pos == 0 ? 0 : offsets[pos - 1]; // todo use StringRef instead of String @@ -272,6 +391,62 @@ RSResult MinMaxIndex::checkGreater(size_t pack_index, const Field & value, const } return RSResult::Some; } + +RSResult MinMaxIndex::checkNullableGreaterEqual(size_t pack_index, const Field & value, const DataTypePtr & type) +{ + const ColumnNullable & column_nullable = static_cast(*minmaxes); + + const auto * raw_type = type.get(); +#define DISPATCH(TYPE) \ + if (typeid_cast(raw_type)) \ + { \ + auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); \ + auto min = minmaxes_data[pack_index * 2]; \ + auto max = minmaxes_data[pack_index * 2 + 1]; \ + return RoughCheck::checkGreaterEqual(value, type, min, max); \ + } + FOR_NUMERIC_TYPES(DISPATCH) +#undef DISPATCH + if (typeid_cast(raw_type)) + { + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkGreaterEqual(value, type, min, max); + } + if (typeid_cast(raw_type)) + { + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkGreaterEqual(value, type, min, max); + } + if (typeid_cast(raw_type) || typeid_cast(raw_type)) + { + // For DataTypeMyDateTime / DataTypeMyDate, simply compare them as comparing UInt64 is OK. + // Check `struct MyTimeBase` for more details. + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkGreaterEqual(value, type, min, max); + } + if (typeid_cast(raw_type)) + { + const auto * string_column = checkAndGetColumn(column_nullable.getNestedColumnPtr().get()); + const auto & chars = string_column->getChars(); + const auto & offsets = string_column->getOffsets(); + size_t pos = pack_index * 2; + size_t prev_offset = pos == 0 ? 0 : offsets[pos - 1]; + // todo use StringRef instead of String + auto min = String(reinterpret_cast(&chars[prev_offset]), offsets[pos] - prev_offset - 1); + pos = pack_index * 2 + 1; + prev_offset = offsets[pos - 1]; + auto max = String(reinterpret_cast(&chars[prev_offset]), offsets[pos] - prev_offset - 1); + return RoughCheck::checkGreaterEqual(value, type, min, max); + } + return RSResult::Some; +} + RSResult MinMaxIndex::checkGreaterEqual(size_t pack_index, const Field & value, const DataTypePtr & type, int /*nan_direction_hint*/) { if ((*has_null_marks)[pack_index] || value.isNull()) @@ -280,6 +455,10 @@ RSResult MinMaxIndex::checkGreaterEqual(size_t pack_index, const Field & value, return RSResult::None; const auto * raw_type = type.get(); + if (typeid_cast(raw_type)) + { + return checkNullableGreaterEqual(pack_index, value, removeNullable(type)); + } #define DISPATCH(TYPE) \ if (typeid_cast(raw_type)) \ { \ @@ -292,14 +471,14 @@ RSResult MinMaxIndex::checkGreaterEqual(size_t pack_index, const Field & value, #undef DISPATCH if (typeid_cast(raw_type)) { - auto & minmaxes_data = toColumnVectorData(minmaxes); + const auto & minmaxes_data = toColumnVectorData(minmaxes); auto min = minmaxes_data[pack_index * 2]; auto max = minmaxes_data[pack_index * 2 + 1]; return RoughCheck::checkGreaterEqual(value, type, min, max); } if (typeid_cast(raw_type)) { - auto & minmaxes_data = toColumnVectorData(minmaxes); + const auto & minmaxes_data = toColumnVectorData(minmaxes); auto min = minmaxes_data[pack_index * 2]; auto max = minmaxes_data[pack_index * 2 + 1]; return RoughCheck::checkGreaterEqual(value, type, min, max); @@ -308,16 +487,16 @@ RSResult MinMaxIndex::checkGreaterEqual(size_t pack_index, const Field & value, { // For DataTypeMyDateTime / DataTypeMyDate, simply compare them as comparing UInt64 is OK. // Check `struct MyTimeBase` for more details. - auto & minmaxes_data = toColumnVectorData(minmaxes); + const auto & minmaxes_data = toColumnVectorData(minmaxes); auto min = minmaxes_data[pack_index * 2]; auto max = minmaxes_data[pack_index * 2 + 1]; return RoughCheck::checkGreaterEqual(value, type, min, max); } if (typeid_cast(raw_type)) { - auto * string_column = checkAndGetColumn(minmaxes.get()); - auto & chars = string_column->getChars(); - auto & offsets = string_column->getOffsets(); + const auto * string_column = checkAndGetColumn(minmaxes.get()); + const auto & chars = string_column->getChars(); + const auto & offsets = string_column->getOffsets(); size_t pos = pack_index * 2; size_t prev_offset = pos == 0 ? 0 : offsets[pos - 1]; // todo use StringRef instead of String @@ -330,7 +509,7 @@ RSResult MinMaxIndex::checkGreaterEqual(size_t pack_index, const Field & value, return RSResult::Some; } -String MinMaxIndex::toString() const +String MinMaxIndex::toString() { return ""; } diff --git a/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.h b/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.h index 34e69b056ce..73284333c73 100644 --- a/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.h +++ b/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.h @@ -80,7 +80,10 @@ class MinMaxIndex RSResult checkGreater(size_t pack_index, const Field & value, const DataTypePtr & type, int nan_direction); RSResult checkGreaterEqual(size_t pack_index, const Field & value, const DataTypePtr & type, int nan_direction); - String toString() const; + static String toString(); + RSResult checkNullableEqual(size_t pack_index, const Field & value, const DataTypePtr & type); + RSResult checkNullableGreater(size_t pack_index, const Field & value, const DataTypePtr & type); + RSResult checkNullableGreaterEqual(size_t pack_index, const Field & value, const DataTypePtr & type); }; diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp index 31fd99faf01..460d42828d5 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp @@ -214,14 +214,6 @@ try ASSERT_EQ(true, checkMatch(case_name, *context, "MyDateTime", "2020-09-27", createLessEqual(attr("MyDateTime"), parseMyDateTime("2020-09-27"), 0))); ASSERT_EQ(false, checkMatch(case_name, *context, "MyDateTime", "2020-09-27", createLessEqual(attr("MyDateTime"), parseMyDateTime("2020-09-26"), 0))); - /// Currently we don't do filtering for null values. i.e. if a pack contains any null values, then the pack will pass the filter. - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createEqual(attr("Nullable(Int64)"), Field((Int64)101)))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createIn(attr("Nullable(Int64)"), {Field((Int64)101)}))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createGreater(attr("Nullable(Int64)"), Field((Int64)100), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)101), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createLess(attr("Nullable(Int64)"), Field((Int64)100), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createLessEqual(attr("Nullable(Int64)"), Field((Int64)99), 0))); - ASSERT_EQ(false, checkDelMatch(case_name, *context, "Int64", "100", createEqual(attr("Int64"), Field((Int64)100)))); ASSERT_EQ(true, checkPkMatch(case_name, *context, "Int64", "100", createEqual(pkAttr(), Field((Int64)100)), true)); ASSERT_EQ(true, checkPkMatch(case_name, *context, "Int64", "100", createGreater(pkAttr(), Field((Int64)99), 0), true)); @@ -236,6 +228,80 @@ try } CATCH +TEST_F(DMMinMaxIndexTest, NullableToNullable) +try +{ + const auto * case_name = ::testing::UnitTest::GetInstance()->current_test_info()->name(); + // clang-format off + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createEqual(attr("Nullable(Int64)"), Field((Int64)101)))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createEqual(attr("Nullable(Int64)"), Field((Int64)100)))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createIn(attr("Nullable(Int64)"), {Field((Int64)100)}))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createIn(attr("Nullable(Int64)"), {Field((Int64)101)}))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createGreater(attr("Nullable(Int64)"), Field((Int64)99), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createGreater(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)101), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createLess(attr("Nullable(Int64)"), Field((Int64)101), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createLess(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createLessEqual(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createLessEqual(attr("Nullable(Int64)"), Field((Int64)99), 0))); + + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createEqual(attr("Nullable(Date)"), Field((String) "2020-09-27")))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createEqual(attr("Nullable(Date)"), Field((String) "2020-09-28")))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createIn(attr("Nullable(Date)"), {Field((String) "2020-09-27")}))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createIn(attr("Nullable(Date)"), {Field((String) "2020-09-28")}))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createGreater(attr("Nullable(Date)"), Field((String) "2020-09-26"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createGreater(attr("Nullable(Date)"), Field((String) "2020-09-27"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createGreaterEqual(attr("Nullable(Date)"), Field((String) "2020-09-27"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createGreaterEqual(attr("Nullable(Date)"), Field((String) "2020-09-28"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createLess(attr("Nullable(Date)"), Field((String) "2020-09-28"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createLess(attr("Nullable(Date)"), Field((String) "2020-09-27"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createLessEqual(attr("Nullable(Date)"), Field((String) "2020-09-27"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createLessEqual(attr("Nullable(Date)"), Field((String) "2020-09-26"), 0))); + + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01")))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:02")))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createIn(attr("Nullable(DateTime)"), {Field((String) "2020-01-01 05:00:01")}))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createIn(attr("Nullable(DateTime)"), {Field((String) "2020-01-01 05:00:02")}))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createGreater(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:00"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createGreater(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createGreaterEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createGreaterEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:02"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createLess(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:02"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createLess(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createLessEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createLessEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:00"), 0))); + + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27")))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-28")))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createIn(attr("Nullable(MyDateTime)"), {parseMyDateTime("2020-09-27")}))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createIn(attr("Nullable(MyDateTime)"), {parseMyDateTime("2020-09-28")}))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createGreater(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-26"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createGreater(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createGreaterEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createGreaterEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-28"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createLess(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-28"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createLess(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createLessEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createLessEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-26"), 0))); + + // has null + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createEqual(attr("Nullable(Int64)"), Field((Int64)101)))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createIn(attr("Nullable(Int64)"), {Field((Int64)101)}))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createGreater(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)101), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createLess(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createLessEqual(attr("Nullable(Int64)"), Field((Int64)99), 0))); + + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createEqual(attr("Nullable(Int64)"), Field((Int64)101)))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createIn(attr("Nullable(Int64)"), {Field((Int64)101)}))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createGreater(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)101), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createLess(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createLessEqual(attr("Nullable(Int64)"), Field((Int64)99), 0))); +} +CATCH + TEST_F(DMMinMaxIndexTest, Logical) try { From f601de130d9f74141f654c2f63813d0aae1830d4 Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Tue, 10 May 2022 17:58:34 +0800 Subject: [PATCH 003/127] Interpreter: Run interpreter test without exchange executors (#4788) ref pingcap/tiflash#4609 --- .../MockTableScanBlockInputStream.cpp | 4 +- .../MockTableScanBlockInputStream.h | 2 +- dbms/src/Flash/Coprocessor/DAGContext.h | 27 ++ .../Coprocessor/DAGQueryBlockInterpreter.cpp | 28 +- .../Coprocessor/DAGQueryBlockInterpreter.h | 2 + .../Flash/Coprocessor/GenSchemaAndColumn.cpp | 56 ++++ .../Flash/Coprocessor/GenSchemaAndColumn.h | 26 ++ dbms/src/Flash/Coprocessor/InterpreterDAG.cpp | 5 +- dbms/src/Flash/Coprocessor/TiDBTableScan.h | 2 - dbms/src/Flash/tests/gtest_interpreter.cpp | 257 ++++++++++++++++++ dbms/src/TestUtils/InterpreterTestUtils.cpp | 35 ++- dbms/src/TestUtils/InterpreterTestUtils.h | 15 +- dbms/src/TestUtils/mockExecutor.cpp | 2 + .../TestUtils/tests/gtest_mock_executors.cpp | 13 +- .../tests/gtest_window_functions.cpp | 2 +- 15 files changed, 445 insertions(+), 31 deletions(-) rename dbms/src/{TestUtils => DataStreams}/MockTableScanBlockInputStream.cpp (95%) rename dbms/src/{TestUtils => DataStreams}/MockTableScanBlockInputStream.h (95%) create mode 100644 dbms/src/Flash/Coprocessor/GenSchemaAndColumn.cpp create mode 100644 dbms/src/Flash/Coprocessor/GenSchemaAndColumn.h create mode 100644 dbms/src/Flash/tests/gtest_interpreter.cpp diff --git a/dbms/src/TestUtils/MockTableScanBlockInputStream.cpp b/dbms/src/DataStreams/MockTableScanBlockInputStream.cpp similarity index 95% rename from dbms/src/TestUtils/MockTableScanBlockInputStream.cpp rename to dbms/src/DataStreams/MockTableScanBlockInputStream.cpp index 316c7487a63..0405e8082db 100644 --- a/dbms/src/TestUtils/MockTableScanBlockInputStream.cpp +++ b/dbms/src/DataStreams/MockTableScanBlockInputStream.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include namespace DB { @@ -32,7 +32,7 @@ MockTableScanBlockInputStream::MockTableScanBlockInputStream(ColumnsWithTypeAndN } } -ColumnPtr MockTableScanBlockInputStream::makeColumn(ColumnWithTypeAndName elem) +ColumnPtr MockTableScanBlockInputStream::makeColumn(ColumnWithTypeAndName elem) const { auto column = elem.type->createColumn(); size_t row_count = 0; diff --git a/dbms/src/TestUtils/MockTableScanBlockInputStream.h b/dbms/src/DataStreams/MockTableScanBlockInputStream.h similarity index 95% rename from dbms/src/TestUtils/MockTableScanBlockInputStream.h rename to dbms/src/DataStreams/MockTableScanBlockInputStream.h index d148d7f3ac1..624afc195ee 100644 --- a/dbms/src/TestUtils/MockTableScanBlockInputStream.h +++ b/dbms/src/DataStreams/MockTableScanBlockInputStream.h @@ -34,7 +34,7 @@ class MockTableScanBlockInputStream : public IProfilingBlockInputStream protected: Block readImpl() override; - ColumnPtr makeColumn(ColumnWithTypeAndName elem); + ColumnPtr makeColumn(ColumnWithTypeAndName elem) const; }; } // namespace DB diff --git a/dbms/src/Flash/Coprocessor/DAGContext.h b/dbms/src/Flash/Coprocessor/DAGContext.h index 18ad73ec207..d031ff103ff 100644 --- a/dbms/src/Flash/Coprocessor/DAGContext.h +++ b/dbms/src/Flash/Coprocessor/DAGContext.h @@ -167,8 +167,30 @@ class DAGContext , max_recorded_error_count(max_error_count_) , warnings(max_recorded_error_count) , warning_count(0) + , is_test(true) {} + // for tests need to run query tasks. + explicit DAGContext(const tipb::DAGRequest & dag_request_, String log_identifier, size_t concurrency) + : dag_request(&dag_request_) + , initialize_concurrency(concurrency) + , is_mpp_task(false) + , is_root_mpp_task(false) + , tunnel_set(nullptr) + , log(Logger::get(log_identifier)) + , flags(dag_request->flags()) + , sql_mode(dag_request->sql_mode()) + , max_recorded_error_count(getMaxErrorCount(*dag_request)) + , warnings(max_recorded_error_count) + , warning_count(0) + , is_test(true) + { + assert(dag_request->has_root_executor() || dag_request->executors_size() > 0); + return_executor_id = dag_request->root_executor().has_executor_id() || dag_request->executors(0).has_executor_id(); + + initOutputInfo(); + } + void attachBlockIO(const BlockIO & io_); std::unordered_map & getProfileStreamsMap(); @@ -275,6 +297,8 @@ class DAGContext return sql_mode & f; } + bool isTest() const { return is_test; } + void cancelAllExchangeReceiver(); void initExchangeReceiverIfMPP(Context & context, size_t max_streams); @@ -287,6 +311,7 @@ class DAGContext const tipb::DAGRequest * dag_request; Int64 compile_time_ns = 0; size_t final_concurrency = 1; + size_t initialize_concurrency = 1; bool has_read_wait_index = false; Clock::time_point read_wait_index_start_timestamp{Clock::duration::zero()}; Clock::time_point read_wait_index_end_timestamp{Clock::duration::zero()}; @@ -345,6 +370,8 @@ class DAGContext /// vector of SubqueriesForSets(such as join build subquery). /// The order of the vector is also the order of the subquery. std::vector subqueries; + + bool is_test = false; /// switch for test, do not use it in production. }; } // namespace DB diff --git a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp index 45a3c1e9471..c11c5bd75a2 100644 --- a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp +++ b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +48,7 @@ #include #include #include +#include #include namespace DB @@ -96,7 +99,8 @@ AnalysisResult analyzeExpressions( AnalysisResult res; ExpressionActionsChain chain; // selection on table scan had been executed in handleTableScan - if (query_block.selection && !query_block.isTableScanSource()) + // In test mode, filter is not pushed down to table scan + if (query_block.selection && (!query_block.isTableScanSource() || context.getDAGContext()->isTest())) { std::vector where_conditions; for (const auto & c : query_block.selection->selection().conditions()) @@ -153,6 +157,19 @@ AnalysisResult analyzeExpressions( } } // namespace +// for tests, we need to mock tableScan blockInputStream as the source stream. +void DAGQueryBlockInterpreter::handleMockTableScan(const TiDBTableScan & table_scan, DAGPipeline & pipeline) +{ + auto names_and_types = genNamesAndTypes(table_scan); + auto columns_with_type_and_name = getColumnWithTypeAndName(names_and_types); + analyzer = std::make_unique(std::move(names_and_types), context); + for (size_t i = 0; i < max_streams; ++i) + { + auto mock_table_scan_stream = std::make_shared(columns_with_type_and_name, context.getSettingsRef().max_block_size); + pipeline.streams.emplace_back(mock_table_scan_stream); + } +} + void DAGQueryBlockInterpreter::handleTableScan(const TiDBTableScan & table_scan, DAGPipeline & pipeline) { const auto push_down_filter = PushDownFilter::toPushDownFilter(query_block.selection); @@ -752,7 +769,10 @@ void DAGQueryBlockInterpreter::executeImpl(DAGPipeline & pipeline) else if (query_block.isTableScanSource()) { TiDBTableScan table_scan(query_block.source, query_block.source_name, dagContext()); - handleTableScan(table_scan, pipeline); + if (dagContext().isTest()) + handleMockTableScan(table_scan, pipeline); + else + handleTableScan(table_scan, pipeline); dagContext().table_scan_executor_id = query_block.source_name; } else if (query_block.source->tp() == tipb::ExecType::TypeWindow) @@ -799,14 +819,12 @@ void DAGQueryBlockInterpreter::executeImpl(DAGPipeline & pipeline) // execute aggregation executeAggregation(pipeline, res.before_aggregation, res.aggregation_keys, res.aggregation_collators, res.aggregate_descriptions, res.is_final_agg); } - if (res.before_having) { // execute having executeWhere(pipeline, res.before_having, res.having_column_name); recordProfileStreams(pipeline, query_block.having_name); } - if (res.before_order_and_select) { executeExpression(pipeline, res.before_order_and_select); @@ -821,14 +839,12 @@ void DAGQueryBlockInterpreter::executeImpl(DAGPipeline & pipeline) // execute final project action executeProject(pipeline, final_project); - // execute limit if (query_block.limit_or_topn && query_block.limit_or_topn->tp() == tipb::TypeLimit) { executeLimit(pipeline); recordProfileStreams(pipeline, query_block.limit_or_topn_name); } - restorePipelineConcurrency(pipeline); // execute exchange_sender diff --git a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h index 84253afbc45..f5a8d2b5ce5 100644 --- a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h +++ b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,7 @@ class DAGQueryBlockInterpreter private: #endif void executeImpl(DAGPipeline & pipeline); + void handleMockTableScan(const TiDBTableScan & table_scan, DAGPipeline & pipeline); void handleTableScan(const TiDBTableScan & table_scan, DAGPipeline & pipeline); void handleJoin(const tipb::Join & join, DAGPipeline & pipeline, SubqueryForSet & right_query); void prepareJoin( diff --git a/dbms/src/Flash/Coprocessor/GenSchemaAndColumn.cpp b/dbms/src/Flash/Coprocessor/GenSchemaAndColumn.cpp new file mode 100644 index 00000000000..e7964021709 --- /dev/null +++ b/dbms/src/Flash/Coprocessor/GenSchemaAndColumn.cpp @@ -0,0 +1,56 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. +#include +#include + +namespace DB +{ +NamesAndTypes genNamesAndTypes(const TiDBTableScan & table_scan) +{ + NamesAndTypes names_and_types; + names_and_types.reserve(table_scan.getColumnSize()); + for (Int32 i = 0; i < table_scan.getColumnSize(); ++i) + { + TiDB::ColumnInfo column_info; + const auto & ci = table_scan.getColumns()[i]; + column_info.tp = static_cast(ci.tp()); + column_info.id = ci.column_id(); + + switch (column_info.id) + { + case TiDBPkColumnID: + // TODO: need to check if the type of pk_handle_columns matches the type that used in delta merge tree. + names_and_types.emplace_back(MutableSupport::tidb_pk_column_name, getDataTypeByColumnInfoForComputingLayer(column_info)); + break; + case ExtraTableIDColumnID: + names_and_types.emplace_back(MutableSupport::extra_table_id_column_name, MutableSupport::extra_table_id_column_type); + break; + default: + names_and_types.emplace_back(fmt::format("mock_table_scan_{}", i), getDataTypeByColumnInfoForComputingLayer(column_info)); + } + } + return names_and_types; +} + +ColumnsWithTypeAndName getColumnWithTypeAndName(const NamesAndTypes & names_and_types) +{ + std::vector column_with_type_and_names; + column_with_type_and_names.reserve(names_and_types.size()); + for (const auto & col : names_and_types) + { + column_with_type_and_names.push_back(DB::ColumnWithTypeAndName(col.type, col.name)); + } + return column_with_type_and_names; +} +} // namespace DB \ No newline at end of file diff --git a/dbms/src/Flash/Coprocessor/GenSchemaAndColumn.h b/dbms/src/Flash/Coprocessor/GenSchemaAndColumn.h new file mode 100644 index 00000000000..617f69de925 --- /dev/null +++ b/dbms/src/Flash/Coprocessor/GenSchemaAndColumn.h @@ -0,0 +1,26 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +#include +#include +#include +#include + +namespace DB +{ +NamesAndTypes genNamesAndTypes(const TiDBTableScan & table_scan); +ColumnsWithTypeAndName getColumnWithTypeAndName(const NamesAndTypes & names_and_types); +} // namespace DB \ No newline at end of file diff --git a/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp b/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp index 6b118f1dd40..b7c75c06e67 100644 --- a/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp +++ b/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp @@ -28,8 +28,11 @@ InterpreterDAG::InterpreterDAG(Context & context_, const DAGQuerySource & dag_) const Settings & settings = context.getSettingsRef(); if (dagContext().isBatchCop() || dagContext().isMPPTask()) max_streams = settings.max_threads; + else if (dagContext().isTest()) + max_streams = dagContext().initialize_concurrency; else max_streams = 1; + if (max_streams > 1) { max_streams *= settings.max_streams_to_max_threads_ratio; @@ -79,7 +82,6 @@ BlockIO InterpreterDAG::execute() BlockInputStreams streams = executeQueryBlock(*dag.getRootQueryBlock()); DAGPipeline pipeline; pipeline.streams = streams; - /// add union to run in parallel if needed if (dagContext().isMPPTask()) /// MPPTask do not need the returned blocks. @@ -95,7 +97,6 @@ BlockIO InterpreterDAG::execute() SizeLimits(settings.max_rows_to_transfer, settings.max_bytes_to_transfer, settings.transfer_overflow_mode), dagContext().log->identifier()); } - BlockIO res; res.in = pipeline.firstStream(); return res; diff --git a/dbms/src/Flash/Coprocessor/TiDBTableScan.h b/dbms/src/Flash/Coprocessor/TiDBTableScan.h index 934ee2c7769..6ac07d326f6 100644 --- a/dbms/src/Flash/Coprocessor/TiDBTableScan.h +++ b/dbms/src/Flash/Coprocessor/TiDBTableScan.h @@ -16,8 +16,6 @@ #include -#include - namespace DB { /// TiDBTableScan is a wrap to hide the difference of `TableScan` and `PartitionTableScan` diff --git a/dbms/src/Flash/tests/gtest_interpreter.cpp b/dbms/src/Flash/tests/gtest_interpreter.cpp new file mode 100644 index 00000000000..961ab525be8 --- /dev/null +++ b/dbms/src/Flash/tests/gtest_interpreter.cpp @@ -0,0 +1,257 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include + +namespace DB +{ +namespace tests +{ +class InterpreterExecuteTest : public DB::tests::InterpreterTest +{ +public: + void initializeContext() override + { + InterpreterTest::initializeContext(); + + context.addMockTable({"test_db", "test_table"}, {{"s1", TiDB::TP::TypeString}, {"s2", TiDB::TP::TypeString}}); + context.addMockTable({"test_db", "test_table_1"}, {{"s1", TiDB::TP::TypeString}, {"s2", TiDB::TP::TypeString}, {"s3", TiDB::TP::TypeString}}); + context.addMockTable({"test_db", "r_table"}, {{"r_a", TiDB::TP::TypeLong}, {"r_b", TiDB::TP::TypeString}, {"join_c", TiDB::TP::TypeString}}); + context.addMockTable({"test_db", "l_table"}, {{"l_a", TiDB::TP::TypeLong}, {"l_b", TiDB::TP::TypeString}, {"join_c", TiDB::TP::TypeString}}); + } +}; + +TEST_F(InterpreterExecuteTest, SingleQueryBlock) +try +{ + auto request = context.scan("test_db", "test_table_1") + .filter(eq(col("s2"), col("s3"))) + .aggregation({Max(col("s1"))}, {col("s2"), col("s3")}) + .filter(eq(col("s2"), col("s3"))) + .topN("s2", false, 10) + .build(context); + { + String expected = R"( +Union + SharedQuery x 10 + Expression + MergeSorting + Union + PartialSorting x 10 + Expression + Filter + SharedQuery + ParallelAggregating + Expression x 10 + Filter + MockTableScan)"; + ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); + } + + request = context.scan("test_db", "test_table_1") + .filter(eq(col("s2"), col("s3"))) + .aggregation({Max(col("s1"))}, {col("s2"), col("s3")}) + .filter(eq(col("s2"), col("s3"))) + .limit(10) + .build(context); + + { + String expected = R"( +Union + SharedQuery x 10 + Limit + Union + Limit x 10 + Expression + Expression + Filter + SharedQuery + ParallelAggregating + Expression x 10 + Filter + MockTableScan)"; + ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); + } +} +CATCH + +TEST_F(InterpreterExecuteTest, MultipleQueryBlockWithSource) +try +{ + auto request = context.scan("test_db", "test_table_1") + .project({"s1", "s2", "s3"}) + .project({"s1", "s2"}) + .project("s1") + .build(context); + { + String expected = R"( +Union + Expression x 10 + Expression + Expression + Expression + Expression + Expression + Expression + Expression + Expression + Expression + MockTableScan)"; + ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); + } + + request = context.scan("test_db", "test_table_1") + .project({"s1", "s2", "s3"}) + .topN({{"s1", true}, {"s2", false}}, 10) + .project({"s1", "s2"}) + .build(context); + { + String expected = R"( +Union + Expression x 10 + Expression + Expression + SharedQuery + Expression + MergeSorting + Union + PartialSorting x 10 + Expression + Expression + Expression + MockTableScan)"; + ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); + } + + request = context.scan("test_db", "test_table_1") + .project({"s1", "s2", "s3"}) + .topN({{"s1", true}, {"s2", false}}, 10) + .project({"s1", "s2"}) + .aggregation({Max(col("s1"))}, {col("s1"), col("s2")}) + .project({"max(s1)", "s1", "s2"}) + .build(context); + { + String expected = R"( +Union + Expression x 10 + Expression + Expression + Expression + SharedQuery + ParallelAggregating + Expression x 10 + Expression + Expression + SharedQuery + Expression + MergeSorting + Union + PartialSorting x 10 + Expression + Expression + Expression + MockTableScan)"; + ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); + } + + request = context.scan("test_db", "test_table_1") + .project({"s1", "s2", "s3"}) + .topN({{"s1", true}, {"s2", false}}, 10) + .project({"s1", "s2"}) + .aggregation({Max(col("s1"))}, {col("s1"), col("s2")}) + .project({"max(s1)", "s1", "s2"}) + .filter(eq(col("s1"), col("s2"))) + .project({"max(s1)", "s1"}) + .limit(10) + .build(context); + { + String expected = R"( +Union + SharedQuery x 10 + Limit + Union + Limit x 10 + Expression + Expression + Expression + Expression + Expression + Filter + Expression + Expression + Expression + SharedQuery + ParallelAggregating + Expression x 10 + Expression + Expression + SharedQuery + Expression + MergeSorting + Union + PartialSorting x 10 + Expression + Expression + Expression + MockTableScan)"; + ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); + } + + // Join Source. + DAGRequestBuilder table1 = context.scan("test_db", "r_table"); + DAGRequestBuilder table2 = context.scan("test_db", "l_table"); + DAGRequestBuilder table3 = context.scan("test_db", "r_table"); + DAGRequestBuilder table4 = context.scan("test_db", "l_table"); + + request = table1.join( + table2.join( + table3.join(table4, + {col("join_c")}, + ASTTableJoin::Kind::Left), + {col("join_c")}, + ASTTableJoin::Kind::Left), + {col("join_c")}, + ASTTableJoin::Kind::Left) + .build(context); + { + String expected = R"( +CreatingSets + Union + HashJoinBuildBlockInputStream x 10 + Expression + Expression + MockTableScan + Union x 2 + HashJoinBuildBlockInputStream x 10 + Expression + Expression + Expression + HashJoinProbe + Expression + MockTableScan + Union + Expression x 10 + Expression + HashJoinProbe + Expression + MockTableScan)"; + ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); + } +} +CATCH + + +} // namespace tests +} // namespace DB \ No newline at end of file diff --git a/dbms/src/TestUtils/InterpreterTestUtils.cpp b/dbms/src/TestUtils/InterpreterTestUtils.cpp index 737978a8bc4..2cc096d4095 100644 --- a/dbms/src/TestUtils/InterpreterTestUtils.cpp +++ b/dbms/src/TestUtils/InterpreterTestUtils.cpp @@ -13,24 +13,26 @@ // limitations under the License. #include +#include +#include #include #include - namespace DB::tests { -DAGContext & MockExecutorTest::getDAGContext() +DAGContext & InterpreterTest::getDAGContext() { assert(dag_context_ptr != nullptr); return *dag_context_ptr; } -void MockExecutorTest::initializeContext() +void InterpreterTest::initializeContext() { dag_context_ptr = std::make_unique(1024); context = MockDAGRequestContext(TiFlashTestEnv::getContext()); + dag_context_ptr->log = Logger::get("interpreterTest"); } -void MockExecutorTest::SetUpTestCase() +void InterpreterTest::SetUpTestCase() { try { @@ -43,8 +45,29 @@ void MockExecutorTest::SetUpTestCase() } } -void MockExecutorTest::dagRequestEqual(String & expected_string, const std::shared_ptr & actual) +void InterpreterTest::initializeClientInfo() +{ + context.context.setCurrentQueryId("test"); + ClientInfo & client_info = context.context.getClientInfo(); + client_info.query_kind = ClientInfo::QueryKind::INITIAL_QUERY; + client_info.interface = ClientInfo::Interface::GRPC; +} + +void InterpreterTest::executeInterpreter(const String & expected_string, const std::shared_ptr & request, size_t concurrency) { - ASSERT_EQ(Poco::trimInPlace(expected_string), Poco::trim(ExecutorSerializer().serialize(actual.get()))); + DAGContext dag_context(*request, "interpreter_test", concurrency); + context.context.setDAGContext(&dag_context); + // Currently, don't care about regions information in interpreter tests. + DAGQuerySource dag(context.context); + auto res = executeQuery(dag, context.context, false, QueryProcessingStage::Complete); + FmtBuffer fb; + res.in->dumpTree(fb); + ASSERT_EQ(Poco::trim(expected_string), Poco::trim(fb.toString())); } + +void InterpreterTest::dagRequestEqual(const String & expected_string, const std::shared_ptr & actual) +{ + ASSERT_EQ(Poco::trim(expected_string), Poco::trim(ExecutorSerializer().serialize(actual.get()))); +} + } // namespace DB::tests diff --git a/dbms/src/TestUtils/InterpreterTestUtils.h b/dbms/src/TestUtils/InterpreterTestUtils.h index 074c65da6f0..28d44d3a5f2 100644 --- a/dbms/src/TestUtils/InterpreterTestUtils.h +++ b/dbms/src/TestUtils/InterpreterTestUtils.h @@ -26,30 +26,37 @@ #include namespace DB::tests { -class MockExecutorTest : public ::testing::Test +void executeInterpreter(const std::shared_ptr & request, Context & context); +class InterpreterTest : public ::testing::Test { protected: void SetUp() override { initializeContext(); + initializeClientInfo(); } public: - MockExecutorTest() + InterpreterTest() : context(TiFlashTestEnv::getContext()) {} static void SetUpTestCase(); virtual void initializeContext(); + void initializeClientInfo(); + DAGContext & getDAGContext(); - static void dagRequestEqual(String & expected_string, const std::shared_ptr & actual); + static void dagRequestEqual(const String & expected_string, const std::shared_ptr & actual); + + void executeInterpreter(const String & expected_string, const std::shared_ptr & request, size_t concurrency); protected: MockDAGRequestContext context; std::unique_ptr dag_context_ptr; }; -#define ASSERT_DAGREQUEST_EQAUL(str, request) dagRequestEqual(str, request); +#define ASSERT_DAGREQUEST_EQAUL(str, request) dagRequestEqual((str), (request)); +#define ASSERT_BLOCKINPUTSTREAM_EQAUL(str, request, concurrency) executeInterpreter((str), (request), (concurrency)) } // namespace DB::tests \ No newline at end of file diff --git a/dbms/src/TestUtils/mockExecutor.cpp b/dbms/src/TestUtils/mockExecutor.cpp index c862c7deec8..3313aae6a93 100644 --- a/dbms/src/TestUtils/mockExecutor.cpp +++ b/dbms/src/TestUtils/mockExecutor.cpp @@ -88,11 +88,13 @@ DAGRequestBuilder & DAGRequestBuilder::mockTable(const String & db, const String assert(!columns.empty()); TableInfo table_info; table_info.name = db + "." + table; + int i = 0; for (const auto & column : columns) { TiDB::ColumnInfo ret; ret.tp = column.second; ret.name = column.first; + ret.id = i++; table_info.columns.push_back(std::move(ret)); } String empty_alias; diff --git a/dbms/src/TestUtils/tests/gtest_mock_executors.cpp b/dbms/src/TestUtils/tests/gtest_mock_executors.cpp index 5c7d77c399a..6dbf791669f 100644 --- a/dbms/src/TestUtils/tests/gtest_mock_executors.cpp +++ b/dbms/src/TestUtils/tests/gtest_mock_executors.cpp @@ -12,22 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include #include #include -#include namespace DB { namespace tests { -class MockDAGRequestTest : public DB::tests::MockExecutorTest +class MockDAGRequestTest : public DB::tests::InterpreterTest { public: void initializeContext() override { - dag_context_ptr = std::make_unique(1024); - context = MockDAGRequestContext(TiFlashTestEnv::getContext()); + InterpreterTest::initializeContext(); context.addMockTable({"test_db", "test_table"}, {{"s1", TiDB::TP::TypeString}, {"s2", TiDB::TP::TypeString}}); context.addMockTable({"test_db", "test_table_1"}, {{"s1", TiDB::TP::TypeLong}, {"s2", TiDB::TP::TypeString}, {"s3", TiDB::TP::TypeString}}); @@ -45,6 +42,8 @@ try String expected = "table_scan_0 | {<0, String>, <1, String>}\n"; ASSERT_DAGREQUEST_EQAUL(expected, request); } + + request = context.scan("test_db", "test_table_1").build(context); { String expected = "table_scan_0 | {<0, Long>, <1, String>, <2, String>}\n"; @@ -63,10 +62,10 @@ try ASSERT_DAGREQUEST_EQAUL(expected, request); } request = context.scan("test_db", "test_table_1") - .filter(And(eq(col("s1"), col("s2")), lt(col("s2"), lt(col("s1"), col("s2"))))) + .filter(And(eq(col("s1"), col("s2")), lt(col("s2"), col("s2")))) // type in lt must be same .build(context); { - String expected = "selection_1 | equals(<0, Long>, <1, String>) and less(<1, String>, less(<0, Long>, <1, String>))}\n" + String expected = "selection_1 | equals(<0, Long>, <1, String>) and less(<1, String>, <1, String>)}\n" " table_scan_0 | {<0, Long>, <1, String>, <2, String>}\n"; ASSERT_DAGREQUEST_EQAUL(expected, request); } diff --git a/dbms/src/WindowFunctions/tests/gtest_window_functions.cpp b/dbms/src/WindowFunctions/tests/gtest_window_functions.cpp index f94a20c1a65..e4205f6f938 100644 --- a/dbms/src/WindowFunctions/tests/gtest_window_functions.cpp +++ b/dbms/src/WindowFunctions/tests/gtest_window_functions.cpp @@ -14,10 +14,10 @@ #include #include +#include #include #include #include -#include #include #include From cf0dd8605ae207c49a23a71808e7e06b6e5ad2b1 Mon Sep 17 00:00:00 2001 From: lidezhu <47731263+lidezhu@users.noreply.github.com> Date: Wed, 11 May 2022 12:06:36 +0800 Subject: [PATCH 004/127] clear storage data before drop its metadata (#4673) ref pingcap/tiflash#3594 --- dbms/src/Common/FailPoint.cpp | 4 +- dbms/src/Debug/DBGInvoker.cpp | 1 + dbms/src/Debug/dbgFuncMisc.cpp | 12 ++ dbms/src/Debug/dbgFuncMisc.h | 7 + .../src/Interpreters/InterpreterDropQuery.cpp | 4 + dbms/src/Server/StorageConfigParser.cpp | 1 - .../Storages/DeltaMerge/DeltaMergeStore.cpp | 61 +++++-- .../src/Storages/DeltaMerge/DeltaMergeStore.h | 7 + dbms/src/Storages/DeltaMerge/Segment.cpp | 7 +- dbms/src/Storages/DeltaMerge/Segment.h | 2 +- .../tests/gtest_dm_storage_delta_merge.cpp | 152 ++++++++++++++++++ dbms/src/Storages/IStorage.h | 40 +++-- dbms/src/Storages/StorageDeltaMerge.cpp | 15 +- dbms/src/Storages/StorageDeltaMerge.h | 2 + .../raft/schema/partition_table_restart.test | 2 + 15 files changed, 286 insertions(+), 31 deletions(-) diff --git a/dbms/src/Common/FailPoint.cpp b/dbms/src/Common/FailPoint.cpp index 8e8b6117def..2c641858e76 100644 --- a/dbms/src/Common/FailPoint.cpp +++ b/dbms/src/Common/FailPoint.cpp @@ -63,7 +63,9 @@ std::unordered_map> FailPointHelper::f M(force_legacy_or_checkpoint_page_file_exists) \ M(exception_in_creating_set_input_stream) \ M(exception_when_read_from_log) \ - M(exception_mpp_hash_build) + M(exception_mpp_hash_build) \ + M(exception_before_drop_segment) \ + M(exception_after_drop_segment) #define APPLY_FOR_FAILPOINTS(M) \ M(force_set_page_file_write_errno) \ diff --git a/dbms/src/Debug/DBGInvoker.cpp b/dbms/src/Debug/DBGInvoker.cpp index 6ae0f9ebd53..3f633c08e67 100644 --- a/dbms/src/Debug/DBGInvoker.cpp +++ b/dbms/src/Debug/DBGInvoker.cpp @@ -121,6 +121,7 @@ DBGInvoker::DBGInvoker() regSchemalessFunc("search_log_for_key", dbgFuncSearchLogForKey); regSchemalessFunc("tidb_dag", dbgFuncTiDBQueryFromNaturalDag); + regSchemalessFunc("gc_global_storage_pool", dbgFuncTriggerGlobalPageStorageGC); regSchemalessFunc("read_index_stress_test", ReadIndexStressTest::dbgFuncStressTest); } diff --git a/dbms/src/Debug/dbgFuncMisc.cpp b/dbms/src/Debug/dbgFuncMisc.cpp index 8563aaf7433..b9f62317189 100644 --- a/dbms/src/Debug/dbgFuncMisc.cpp +++ b/dbms/src/Debug/dbgFuncMisc.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -97,4 +98,15 @@ void dbgFuncSearchLogForKey(Context & context, const ASTs & args, DBGInvoker::Pr else output("Invalid"); } + +void dbgFuncTriggerGlobalPageStorageGC(Context & context, const ASTs & /*args*/, DBGInvoker::Printer /*output*/) +{ + auto global_storage_pool = context.getGlobalStoragePool(); + if (global_storage_pool) + { + global_storage_pool->meta()->gc(); + global_storage_pool->log()->gc(); + global_storage_pool->data()->gc(); + } +} } // namespace DB diff --git a/dbms/src/Debug/dbgFuncMisc.h b/dbms/src/Debug/dbgFuncMisc.h index c863a6cd8fc..c256e6b41c0 100644 --- a/dbms/src/Debug/dbgFuncMisc.h +++ b/dbms/src/Debug/dbgFuncMisc.h @@ -26,4 +26,11 @@ class Context; // ./storage-client.sh "DBGInvoke search_log_for_key(key)" void dbgFuncSearchLogForKey(Context & context, const ASTs & args, DBGInvoker::Printer output); +// Trigger the gc process of global storage pool. Used to remove obsolete entries left by the previous dropped table +// Note that it is ok for now since we don't store external/ref on GlobalStoragePool.meta, or it may run into same +// problem as https://github.com/pingcap/tiflash/pull/4850 +// Usage: +// ./storage-client.sh "DBGInvoke trigger_global_storage_pool_gc()" +void dbgFuncTriggerGlobalPageStorageGC(Context & context, const ASTs & args, DBGInvoker::Printer output); + } // namespace DB diff --git a/dbms/src/Interpreters/InterpreterDropQuery.cpp b/dbms/src/Interpreters/InterpreterDropQuery.cpp index a6060064f74..fe1391cab50 100644 --- a/dbms/src/Interpreters/InterpreterDropQuery.cpp +++ b/dbms/src/Interpreters/InterpreterDropQuery.cpp @@ -159,6 +159,10 @@ BlockIO InterpreterDropQuery::execute() } else { + /// Clear storage data first, and if tiflash crash in the middle of `clearData`, + /// this table can still be restored, and can call `clearData` again. + table.first->clearData(); + /// Delete table metdata and table itself from memory database->removeTable(context, current_table_name); diff --git a/dbms/src/Server/StorageConfigParser.cpp b/dbms/src/Server/StorageConfigParser.cpp index 653a4eb947f..270390ac1fa 100644 --- a/dbms/src/Server/StorageConfigParser.cpp +++ b/dbms/src/Server/StorageConfigParser.cpp @@ -216,7 +216,6 @@ void TiFlashStorageConfig::parseMisc(const String & storage_section, Poco::Logge // config for experimental feature, may remove later enable_ps_v3 = get_bool_config_or_default("enable_ps_v3", enable_ps_v3); - LOG_FMT_INFO(log, "format_version {} lazily_init_store {} enable_ps_v3 {}", format_version, lazily_init_store, enable_ps_v3); } diff --git a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp index 36063e2bd83..b27c94305aa 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp +++ b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp @@ -95,6 +95,8 @@ extern const char force_set_segment_ingest_packs_fail[]; extern const char segment_merge_after_ingest_packs[]; extern const char random_exception_after_dt_write_done[]; extern const char force_slow_page_storage_snapshot_release[]; +extern const char exception_before_drop_segment[]; +extern const char exception_after_drop_segment[]; } // namespace FailPoints namespace DM @@ -369,12 +371,8 @@ void DeltaMergeStore::rename(String /*new_path*/, bool clean_rename, String new_ db_name.swap(new_database_name); } -void DeltaMergeStore::drop() +void DeltaMergeStore::dropAllSegments(bool keep_first_segment) { - // Remove all background task first - shutdown(); - - LOG_FMT_INFO(log, "Drop DeltaMerge removing data from filesystem [{}.{}]", db_name, table_name); auto dm_context = newDMContext(global_context, global_context.getSettingsRef()); { std::unique_lock lock(read_write_mutex); @@ -390,31 +388,76 @@ void DeltaMergeStore::drop() while (!segment_ids.empty()) { auto segment_id_to_drop = segment_ids.top(); + if (keep_first_segment && (segment_id_to_drop == DELTA_MERGE_FIRST_SEGMENT_ID)) + { + // This must be the last segment to drop + assert(segment_ids.size() == 1); + break; + } auto segment_to_drop = id_to_segment[segment_id_to_drop]; segment_ids.pop(); + SegmentPtr previous_segment; + SegmentPtr new_previous_segment; if (!segment_ids.empty()) { // This is not the last segment, so we need to set previous segment's next_segment_id to 0 to indicate that this segment has been dropped auto previous_segment_id = segment_ids.top(); - auto previous_segment = id_to_segment[previous_segment_id]; + previous_segment = id_to_segment[previous_segment_id]; assert(previous_segment->nextSegmentId() == segment_id_to_drop); auto previous_lock = previous_segment->mustGetUpdateLock(); - auto new_previous_segment = previous_segment->dropNextSegment(wbs); + + FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::exception_before_drop_segment); // No need to abandon previous_segment, because it's delta and stable is managed by the new_previous_segment. // Abandon previous_segment will actually abandon new_previous_segment - segments.emplace(new_previous_segment->getRowKeyRange().getEnd(), new_previous_segment); - id_to_segment.emplace(previous_segment_id, new_previous_segment); + // + // And we need to use the previous_segment to manage the dropped segment's range, + // because if tiflash crash in the middle of the drop table process, and when restoring this table at restart, + // there are some possibilities that this table will trigger some background tasks, + // and in these background tasks, it may check that all ranges of this table should be managed by some segment. + new_previous_segment = previous_segment->dropNextSegment(wbs, segment_to_drop->getRowKeyRange()); + FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::exception_after_drop_segment); } // The order to drop the meta and data of this segment doesn't matter, // Because there is no segment pointing to this segment, // so it won't be restored again even the drop process was interrupted by restart segments.erase(segment_to_drop->getRowKeyRange().getEnd()); id_to_segment.erase(segment_id_to_drop); + if (previous_segment) + { + assert(new_previous_segment); + assert(previous_segment->segmentId() == new_previous_segment->segmentId()); + segments.erase(previous_segment->getRowKeyRange().getEnd()); + segments.emplace(new_previous_segment->getRowKeyRange().getEnd(), new_previous_segment); + id_to_segment.erase(previous_segment->segmentId()); + id_to_segment.emplace(new_previous_segment->segmentId(), new_previous_segment); + } auto drop_lock = segment_to_drop->mustGetUpdateLock(); segment_to_drop->abandon(*dm_context); segment_to_drop->drop(global_context.getFileProvider(), wbs); } } +} + +void DeltaMergeStore::clearData() +{ + // Remove all background task first + shutdown(); + LOG_FMT_INFO(log, "Clear DeltaMerge segments data [{}.{}]", db_name, table_name); + // We don't drop the first segment in clearData, because if we drop it and tiflash crashes before drop the table's metadata, + // when restart the table will try to restore the first segment but failed to do it which cause tiflash crash again. + // The reason this happens is that even we delete all data in a PageStorage instance, + // the call to PageStorage::getMaxId is still not 0 so tiflash treat it as an old table and will try to restore it's first segment. + dropAllSegments(true); + LOG_FMT_INFO(log, "Clear DeltaMerge segments data done [{}.{}]", db_name, table_name); +} + +void DeltaMergeStore::drop() +{ + // Remove all background task first + shutdown(); + + LOG_FMT_INFO(log, "Drop DeltaMerge removing data from filesystem [{}.{}]", db_name, table_name); + dropAllSegments(false); storage_pool->drop(); // Drop data in storage path pool diff --git a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h index de56a622978..9b09b6f37c5 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h +++ b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h @@ -311,6 +311,8 @@ class DeltaMergeStore : private boost::noncopyable void rename(String new_path, bool clean_rename, String new_database_name, String new_table_name); + void clearData(); + void drop(); // Stop all background tasks. @@ -457,8 +459,13 @@ class DeltaMergeStore : private boost::noncopyable size_t expected_tasks_count = 1, const SegmentIdSet & read_segments = {}); +private: + void dropAllSegments(bool keep_first_segment); + #ifndef DBMS_PUBLIC_GTEST private: +#else +public: #endif Context & global_context; diff --git a/dbms/src/Storages/DeltaMerge/Segment.cpp b/dbms/src/Storages/DeltaMerge/Segment.cpp index ac192ff6082..eccc06a8257 100644 --- a/dbms/src/Storages/DeltaMerge/Segment.cpp +++ b/dbms/src/Storages/DeltaMerge/Segment.cpp @@ -1262,10 +1262,13 @@ SegmentPtr Segment::applyMerge(DMContext & dm_context, // return merged; } -SegmentPtr Segment::dropNextSegment(WriteBatches & wbs) +SegmentPtr Segment::dropNextSegment(WriteBatches & wbs, const RowKeyRange & next_segment_range) { + assert(rowkey_range.end == next_segment_range.start); + // merge the rowkey range of the next segment to this segment + auto new_rowkey_range = RowKeyRange(rowkey_range.start, next_segment_range.end, rowkey_range.is_common_handle, rowkey_range.rowkey_column_size); auto new_segment = std::make_shared(epoch + 1, // - rowkey_range, + new_rowkey_range, segment_id, 0, delta, diff --git a/dbms/src/Storages/DeltaMerge/Segment.h b/dbms/src/Storages/DeltaMerge/Segment.h index a6328d24128..0d048011e18 100644 --- a/dbms/src/Storages/DeltaMerge/Segment.h +++ b/dbms/src/Storages/DeltaMerge/Segment.h @@ -231,7 +231,7 @@ class Segment : private boost::noncopyable WriteBatches & wbs, const StableValueSpacePtr & new_stable) const; - SegmentPtr dropNextSegment(WriteBatches & wbs); + SegmentPtr dropNextSegment(WriteBatches & wbs, const RowKeyRange & next_segment_range); /// Flush delta's cache packs. bool flushCache(DMContext & dm_context); diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp index 75eed3ab964..a26471cfe01 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp @@ -44,6 +44,11 @@ namespace DB { +namespace FailPoints +{ +extern const char exception_before_drop_segment[]; +extern const char exception_after_drop_segment[]; +} // namespace FailPoints namespace DM { namespace tests @@ -699,6 +704,153 @@ try } CATCH +TEST(StorageDeltaMergeTest, RestoreAfterClearData) +try +{ + Context ctx = DMTestEnv::getContext(); + auto & settings = ctx.getSettingsRef(); + settings.dt_segment_limit_rows = 11; + settings.dt_segment_limit_size = 20; + settings.dt_segment_delta_limit_rows = 7; + settings.dt_segment_delta_limit_size = 20; + settings.dt_segment_force_split_size = 100; + settings.dt_segment_delta_cache_limit_size = 20; + std::shared_ptr storage; + DataTypes data_types; + Names column_names; + // create table + auto create_table = [&]() { + NamesAndTypesList names_and_types_list{ + {"col1", std::make_shared()}, + {"col2", std::make_shared()}, + }; + for (const auto & name_type : names_and_types_list) + { + data_types.push_back(name_type.type); + column_names.push_back(name_type.name); + } + + const String path_name = DB::tests::TiFlashTestEnv::getTemporaryPath("StorageDeltaMerge_RestoreAfterClearData"); + if (Poco::File path(path_name); path.exists()) + path.remove(true); + + // primary_expr_ast + const String table_name = "t_1233"; + ASTPtr astptr(new ASTIdentifier(table_name, ASTIdentifier::Kind::Table)); + astptr->children.emplace_back(new ASTIdentifier("col1")); + + // table_info.id is used as the ns_id + TiDB::TableInfo table_info; + table_info.id = 1233; + table_info.is_common_handle = false; + table_info.pk_is_handle = false; + + storage = StorageDeltaMerge::create("TiFlash", + /* db_name= */ "default", + table_name, + table_info, + ColumnsDescription{names_and_types_list}, + astptr, + 0, + ctx); + storage->startup(); + }; + auto write_data = [&](Int64 start, Int64 limit) { + ASTPtr insertptr(new ASTInsertQuery()); + BlockOutputStreamPtr output = storage->write(insertptr, ctx.getSettingsRef()); + // prepare block data + Block sample; + sample.insert(DB::tests::createColumn( + createNumbers(start, start + limit), + "col1")); + sample.insert(DB::tests::createColumn( + Strings(limit, "a"), + "col2")); + + output->writePrefix(); + output->write(sample); + output->writeSuffix(); + }; + auto read_data = [&]() { + QueryProcessingStage::Enum stage2; + SelectQueryInfo query_info; + query_info.query = std::make_shared(); + query_info.mvcc_query_info = std::make_unique(ctx.getSettingsRef().resolve_locks, std::numeric_limits::max()); + Names read_columns = {"col1", EXTRA_TABLE_ID_COLUMN_NAME, "col2"}; + BlockInputStreams ins = storage->read(read_columns, query_info, ctx, stage2, 8192, 1); + BlockInputStreamPtr in = ins[0]; + in->readPrefix(); + size_t num_rows_read = 0; + while (Block block = in->read()) + { + num_rows_read += block.rows(); + } + in->readSuffix(); + return num_rows_read; + }; + + // create table + create_table(); + size_t num_rows_write = 0; + // write until split and use a big enough finite for loop to make sure the test won't hang forever + for (size_t i = 0; i < 100000; i++) + { + write_data(num_rows_write, 1000); + num_rows_write += 1000; + if (storage->getStore()->getSegmentStats().size() > 1) + break; + } + { + ASSERT_GT(storage->getStore()->getSegmentStats().size(), 1); + ASSERT_EQ(read_data(), num_rows_write); + } + storage->flushCache(ctx); + // throw exception before drop first segment + DB::FailPointHelper::enableFailPoint(DB::FailPoints::exception_before_drop_segment); + ASSERT_ANY_THROW(storage->clearData()); + storage->removeFromTMTContext(); + + // restore the table and make sure no data has been dropped + create_table(); + { + ASSERT_EQ(read_data(), num_rows_write); + } + // write more data make sure segments more than 1 + for (size_t i = 0; i < 100000; i++) + { + if (storage->getStore()->getSegmentStats().size() > 1) + break; + write_data(num_rows_write, 1000); + num_rows_write += 1000; + } + { + ASSERT_GT(storage->getStore()->getSegmentStats().size(), 1); + ASSERT_EQ(read_data(), num_rows_write); + } + storage->flushCache(ctx); + // throw exception after drop first segment + DB::FailPointHelper::enableFailPoint(DB::FailPoints::exception_after_drop_segment); + ASSERT_ANY_THROW(storage->clearData()); + storage->removeFromTMTContext(); + + // restore the table and make sure some data has been dropped + create_table(); + { + ASSERT_LT(read_data(), num_rows_write); + } + storage->clearData(); + storage->removeFromTMTContext(); + + // restore the table and make sure there is just one segment left + create_table(); + { + ASSERT_EQ(storage->getStore()->getSegmentStats().size(), 1); + ASSERT_LT(read_data(), num_rows_write); + } + storage->drop(); +} +CATCH + } // namespace tests } // namespace DM } // namespace DB diff --git a/dbms/src/Storages/IStorage.h b/dbms/src/Storages/IStorage.h index 9867f088953..1d3016e2853 100644 --- a/dbms/src/Storages/IStorage.h +++ b/dbms/src/Storages/IStorage.h @@ -29,7 +29,6 @@ namespace DB { - class Context; class IBlockInputStream; class IBlockOutputStream; @@ -57,7 +56,9 @@ class AlterCommands; * - data storage structure (compression, etc.) * - concurrent access to data (locks, etc.) */ -class IStorage : public std::enable_shared_from_this, private boost::noncopyable, public ITableDeclaration +class IStorage : public std::enable_shared_from_this + , private boost::noncopyable + , public ITableDeclaration { public: /// The main name of the table type (for example, StorageMergeTree). @@ -100,7 +101,8 @@ class IStorage : public std::enable_shared_from_this, private boost::n /// won't be changed by this lock. /// After decoding done, we can release alter lock but keep drop lock for writing data. TableStructureLockHolder lockStructureForShare( - const String & query_id, const std::chrono::milliseconds & acquire_timeout = std::chrono::milliseconds(0)); + const String & query_id, + const std::chrono::milliseconds & acquire_timeout = std::chrono::milliseconds(0)); /// Lock table exclusively. This lock must be acquired if you want to be /// sure, that no other thread (SELECT, merge, ALTER, etc.) doing something @@ -110,7 +112,8 @@ class IStorage : public std::enable_shared_from_this, private boost::n /// NOTE: You have to be 100% sure that you need this lock. It's extremely /// heavyweight and makes table irresponsive. TableExclusiveLockHolder lockExclusively( - const String & query_id, const std::chrono::milliseconds & acquire_timeout = std::chrono::milliseconds(0)); + const String & query_id, + const std::chrono::milliseconds & acquire_timeout = std::chrono::milliseconds(0)); /** Read a set of columns from the table. * Accepts a list of columns to read, as well as a description of the query, @@ -133,11 +136,11 @@ class IStorage : public std::enable_shared_from_this, private boost::n * is guaranteed to be immutable once the input streams are returned. */ virtual BlockInputStreams read(const Names & /*column_names*/, - const SelectQueryInfo & /*query_info*/, - const Context & /*context*/, - QueryProcessingStage::Enum & /*processed_stage*/, - size_t /*max_block_size*/, - unsigned /*num_streams*/) + const SelectQueryInfo & /*query_info*/, + const Context & /*context*/, + QueryProcessingStage::Enum & /*processed_stage*/, + size_t /*max_block_size*/, + unsigned /*num_streams*/) { throw Exception("Method read is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); } @@ -153,6 +156,11 @@ class IStorage : public std::enable_shared_from_this, private boost::n throw Exception("Method write is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); } + /** Clear the table data. Called before drop the metadata and data of this storage. + * The difference with `drop` is that after calling `clearData`, the storage must still be able to be restored. + */ + virtual void clearData() {} + /** Delete the table data. Called before deleting the directory with the data. * If you do not need any action other than deleting the directory with data, you can leave this method blank. */ @@ -172,8 +180,7 @@ class IStorage : public std::enable_shared_from_this, private boost::n * This method must fully execute the ALTER query, taking care of the locks itself. * To update the table metadata on disk, this method should call InterpreterAlterQuery::updateMetadata. */ - virtual void alter(const TableLockHolder &, const AlterCommands & /*params*/, const String & /*database_name*/, - const String & /*table_name*/, const Context & /*context*/) + virtual void alter(const TableLockHolder &, const AlterCommands & /*params*/, const String & /*database_name*/, const String & /*table_name*/, const Context & /*context*/) { throw Exception("Method alter is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); } @@ -223,7 +230,11 @@ class IStorage : public std::enable_shared_from_this, private boost::n * Returns whether any work has been done. */ virtual bool optimize( - const ASTPtr & /*query*/, const ASTPtr & /*partition*/, bool /*final*/, bool /*deduplicate*/, const Context & /*context*/) + const ASTPtr & /*query*/, + const ASTPtr & /*partition*/, + bool /*final*/, + bool /*deduplicate*/, + const Context & /*context*/) { throw Exception("Method optimize is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); } @@ -273,7 +284,10 @@ class IStorage : public std::enable_shared_from_this, private boost::n private: RWLock::LockHolder tryLockTimed( - const RWLockPtr & rwlock, RWLock::Type type, const String & query_id, const std::chrono::milliseconds & acquire_timeout) const; + const RWLockPtr & rwlock, + RWLock::Type type, + const String & query_id, + const std::chrono::milliseconds & acquire_timeout) const; /// You always need to take the next two locks in this order. diff --git a/dbms/src/Storages/StorageDeltaMerge.cpp b/dbms/src/Storages/StorageDeltaMerge.cpp index 1c6853afeef..f2aa227d29c 100644 --- a/dbms/src/Storages/StorageDeltaMerge.cpp +++ b/dbms/src/Storages/StorageDeltaMerge.cpp @@ -287,13 +287,20 @@ void StorageDeltaMerge::updateTableColumnInfo() rowkey_column_size = rowkey_column_defines.size(); } +void StorageDeltaMerge::clearData() +{ + shutdown(); + // init the store so it can clear data + auto & store = getAndMaybeInitStore(); + store->clearData(); +} + void StorageDeltaMerge::drop() { shutdown(); - if (storeInited()) - { - _store->drop(); - } + // init the store so it can do the drop work + auto & store = getAndMaybeInitStore(); + store->drop(); } Block StorageDeltaMerge::buildInsertBlock(bool is_import, bool is_delete, const Block & old_block) diff --git a/dbms/src/Storages/StorageDeltaMerge.h b/dbms/src/Storages/StorageDeltaMerge.h index a6e61f3bebe..560f365e747 100644 --- a/dbms/src/Storages/StorageDeltaMerge.h +++ b/dbms/src/Storages/StorageDeltaMerge.h @@ -52,6 +52,8 @@ class StorageDeltaMerge String getTableName() const override; String getDatabaseName() const override; + void clearData() override; + void drop() override; BlockInputStreams read( diff --git a/tests/delta-merge-test/raft/schema/partition_table_restart.test b/tests/delta-merge-test/raft/schema/partition_table_restart.test index 2de5cb92436..bd0facdc153 100644 --- a/tests/delta-merge-test/raft/schema/partition_table_restart.test +++ b/tests/delta-merge-test/raft/schema/partition_table_restart.test @@ -35,6 +35,8 @@ # schema syncer guarantees logical table creation at last, so there won't be cases that logical table exists whereas physical table not. => drop table default.test => DBGInvoke __reset_schemas() +# remove obsolete entry left by previous dropped table +=> DBGInvoke __gc_global_storage_pool() => DBGInvoke __add_column_to_tidb_table(default, test, 'col_3 Nullable(Int8)') => DBGInvoke __rename_tidb_table(default, test, test1) From ec4d0588f9b94be0b3f1311521f0a84fd1b0bfec Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Wed, 11 May 2022 13:44:35 +0800 Subject: [PATCH 005/127] Fix wal may dump write diff type in same page_id (#4850) close pingcap/tiflash#4849 --- dbms/src/Storages/DeltaMerge/StoragePool.cpp | 55 ++++++++++++++++--- dbms/src/Storages/DeltaMerge/StoragePool.h | 24 ++++++++ dbms/src/Storages/Page/PageStorage.h | 15 +++-- dbms/src/Storages/Page/V2/PageStorage.cpp | 18 +++--- dbms/src/Storages/Page/V2/PageStorage.h | 4 +- .../Page/V2/tests/gtest_page_storage.cpp | 28 ++++++++++ .../Storages/Page/V3/PageDirectoryFactory.cpp | 9 +++ .../Storages/Page/V3/PageDirectoryFactory.h | 6 ++ dbms/src/Storages/Page/V3/PageStorageImpl.cpp | 9 +-- dbms/src/Storages/Page/V3/PageStorageImpl.h | 3 +- .../Page/V3/tests/gtest_page_storage.cpp | 46 ++++++++++++++++ tests/delta-merge-test/ddl/alter.test | 23 ++++---- .../raft/txn_mock/partition_table.test | 27 ++++----- 13 files changed, 203 insertions(+), 64 deletions(-) diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.cpp b/dbms/src/Storages/DeltaMerge/StoragePool.cpp index bee6b174b55..8c4468ae2ed 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.cpp +++ b/dbms/src/Storages/DeltaMerge/StoragePool.cpp @@ -114,9 +114,9 @@ GlobalStoragePool::GlobalStoragePool(const PathPool & path_pool, Context & globa void GlobalStoragePool::restore() { - log_storage->restore(); - data_storage->restore(); - meta_storage->restore(); + log_max_ids = log_storage->restore(); + data_max_ids = data_storage->restore(); + meta_max_ids = meta_storage->restore(); gc_handle = global_context.getBackgroundPool().addTask( [this] { @@ -187,6 +187,9 @@ StoragePool::StoragePool(NamespaceId ns_id_, const GlobalStoragePool & global_st , data_storage_reader(ns_id, data_storage, nullptr) , meta_storage_reader(ns_id, meta_storage, nullptr) , global_context(global_ctx) + , v3_log_max_ids(global_storage_pool.getLogMaxIds()) + , v3_data_max_ids(global_storage_pool.getDataMaxIds()) + , v3_meta_max_ids(global_storage_pool.getMetaMaxIds()) {} void StoragePool::restore() @@ -194,14 +197,48 @@ void StoragePool::restore() // If the storage instances is not global, we need to initialize it by ourselves and add a gc task. if (owned_storage) { - log_storage->restore(); - data_storage->restore(); - meta_storage->restore(); + auto log_max_ids = log_storage->restore(); + auto data_max_ids = data_storage->restore(); + auto meta_max_ids = meta_storage->restore(); + + assert(log_max_ids.size() == 1); + assert(data_max_ids.size() == 1); + assert(meta_max_ids.size() == 1); + + max_log_page_id = log_max_ids[0]; + max_data_page_id = data_max_ids[0]; + max_meta_page_id = meta_max_ids[0]; } + else + { + if (const auto & it = v3_log_max_ids.find(ns_id); it == v3_log_max_ids.end()) + { + max_log_page_id = 0; + } + else + { + max_log_page_id = it->second; + } - max_log_page_id = log_storage->getMaxId(ns_id); - max_data_page_id = data_storage->getMaxId(ns_id); - max_meta_page_id = meta_storage->getMaxId(ns_id); + if (const auto & it = v3_data_max_ids.find(ns_id); it == v3_data_max_ids.end()) + { + max_data_page_id = 0; + } + else + { + max_data_page_id = it->second; + } + + if (const auto & it = v3_meta_max_ids.find(ns_id); it == v3_meta_max_ids.end()) + { + max_meta_page_id = 0; + } + else + { + max_meta_page_id = it->second; + } + } + // TODO: add a log to show max_*_page_id after mix mode pr ready. } StoragePool::~StoragePool() diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.h b/dbms/src/Storages/DeltaMerge/StoragePool.h index 859be76dbe5..3a02232902b 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.h +++ b/dbms/src/Storages/DeltaMerge/StoragePool.h @@ -53,6 +53,21 @@ class GlobalStoragePool : private boost::noncopyable PageStoragePtr data() const { return data_storage; } PageStoragePtr meta() const { return meta_storage; } + std::map getLogMaxIds() const + { + return log_max_ids; + } + + std::map getDataMaxIds() const + { + return data_max_ids; + } + + std::map getMetaMaxIds() const + { + return meta_max_ids; + } + private: // TODO: maybe more frequent gc for GlobalStoragePool? bool gc(const Settings & settings, const Seconds & try_gc_period = DELTA_MERGE_GC_PERIOD); @@ -62,6 +77,10 @@ class GlobalStoragePool : private boost::noncopyable PageStoragePtr data_storage; PageStoragePtr meta_storage; + std::map log_max_ids; + std::map data_max_ids; + std::map meta_max_ids; + std::atomic last_try_gc_time = Clock::now(); std::mutex mutex; @@ -143,6 +162,11 @@ class StoragePool : private boost::noncopyable Context & global_context; + // TBD: Will be replaced GlobalPathPoolPtr after mix mode ptr ready + std::map v3_log_max_ids; + std::map v3_data_max_ids; + std::map v3_meta_max_ids; + std::atomic max_log_page_id = 0; std::atomic max_data_page_id = 0; std::atomic max_meta_page_id = 0; diff --git a/dbms/src/Storages/Page/PageStorage.h b/dbms/src/Storages/Page/PageStorage.h index 29fea2d30e3..34d91dbd1ad 100644 --- a/dbms/src/Storages/Page/PageStorage.h +++ b/dbms/src/Storages/Page/PageStorage.h @@ -211,12 +211,16 @@ class PageStorage : private boost::noncopyable virtual ~PageStorage() = default; - virtual void restore() = 0; + // Return the map[ns_id, max_page_id] + // The caller should ensure that it only allocate new id that is larger than `max_page_id`. Reusing the + // same ID for different kind of write (put/ref/put_external) would make PageStorage run into unexpected error. + // + // Note that for V2, we always return a map with only one element: cause V2 have no + // idea about ns_id. + virtual std::map restore() = 0; virtual void drop() = 0; - virtual PageId getMaxId(NamespaceId ns_id) = 0; - virtual SnapshotPtr getSnapshot(const String & tracing_id) = 0; // Get some statistics of all living snapshots and the oldest living snapshot. @@ -362,11 +366,6 @@ class PageReader : private boost::noncopyable return storage->read(ns_id, page_fields, read_limiter, snap); } - PageId getMaxId() const - { - return storage->getMaxId(ns_id); - } - PageId getNormalPageId(PageId page_id) const { return storage->getNormalPageId(ns_id, page_id, snap); diff --git a/dbms/src/Storages/Page/V2/PageStorage.cpp b/dbms/src/Storages/Page/V2/PageStorage.cpp index 32367c4e708..4c47645e7d7 100644 --- a/dbms/src/Storages/Page/V2/PageStorage.cpp +++ b/dbms/src/Storages/Page/V2/PageStorage.cpp @@ -196,7 +196,7 @@ toConcreteSnapshot(const DB::PageStorage::SnapshotPtr & ptr) return assert_cast(ptr.get()); } -void PageStorage::restore() +std::map PageStorage::restore() { LOG_FMT_INFO(log, "{} begin to restore data from disk. [path={}] [num_writers={}]", storage_name, delegator->defaultPath(), write_files.size()); @@ -349,17 +349,13 @@ void PageStorage::restore() #endif statistics = restore_info; - { - auto snapshot = getConcreteSnapshot(); - size_t num_pages = snapshot->version()->numPages(); - LOG_FMT_INFO(log, "{} restore {} pages, write batch sequence: {}, {}", storage_name, num_pages, write_batch_seq, statistics.toString()); - } -} -PageId PageStorage::getMaxId(NamespaceId /*ns_id*/) -{ - std::lock_guard write_lock(write_mutex); - return versioned_page_entries.getSnapshot("")->version()->maxId(); + auto snapshot = getConcreteSnapshot(); + size_t num_pages = snapshot->version()->numPages(); + LOG_FMT_INFO(log, "{} restore {} pages, write batch sequence: {}, {}", storage_name, num_pages, write_batch_seq, statistics.toString()); + + // Fixed namespace id 0 + return {{0, snapshot->version()->maxId()}}; } PageId PageStorage::getNormalPageIdImpl(NamespaceId /*ns_id*/, PageId page_id, SnapshotPtr snapshot, bool throw_on_not_exist) diff --git a/dbms/src/Storages/Page/V2/PageStorage.h b/dbms/src/Storages/Page/V2/PageStorage.h index 6cefb6407d2..1e32de40dd0 100644 --- a/dbms/src/Storages/Page/V2/PageStorage.h +++ b/dbms/src/Storages/Page/V2/PageStorage.h @@ -91,12 +91,10 @@ class PageStorage : public DB::PageStorage const FileProviderPtr & file_provider_); ~PageStorage() = default; - void restore() override; + std::map restore() override; void drop() override; - PageId getMaxId(NamespaceId ns_id) override; - PageId getNormalPageIdImpl(NamespaceId ns_id, PageId page_id, SnapshotPtr snapshot, bool throw_on_not_exist) override; DB::PageStorage::SnapshotPtr getSnapshot(const String & tracing_id) override; diff --git a/dbms/src/Storages/Page/V2/tests/gtest_page_storage.cpp b/dbms/src/Storages/Page/V2/tests/gtest_page_storage.cpp index fc429dde0ac..5bbd319192b 100644 --- a/dbms/src/Storages/Page/V2/tests/gtest_page_storage.cpp +++ b/dbms/src/Storages/Page/V2/tests/gtest_page_storage.cpp @@ -79,6 +79,15 @@ class PageStorage_test : public DB::base::TiFlashStorageTestBasic return storage; } + std::pair, std::map> reopen() + { + auto delegator = path_pool->getPSDiskDelegatorSingle("log"); + auto storage = std::make_shared("test.t", delegator, config, file_provider); + auto max_ids = storage->restore(); + return {storage, max_ids}; + } + + protected: PageStorage::Config config; std::shared_ptr storage; @@ -727,6 +736,25 @@ try } CATCH +TEST_F(PageStorage_test, getMaxIdsFromRestore) +try +{ + { + WriteBatch batch; + batch.putExternal(1, 0); + batch.putExternal(2, 0); + batch.delPage(1); + batch.delPage(2); + storage->write(std::move(batch)); + } + + storage = nullptr; + auto [page_storage, max_ids] = reopen(); + ASSERT_EQ(max_ids.size(), 1); + ASSERT_EQ(max_ids[0], 2); +} +CATCH + TEST_F(PageStorage_test, IgnoreIncompleteWriteBatch1) try { diff --git a/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp b/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp index 7f7e7f19989..4f2a8a3fbd4 100644 --- a/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp @@ -105,6 +105,15 @@ void PageDirectoryFactory::loadEdit(const PageDirectoryPtr & dir, const PageEntr for (const auto & r : edit.getRecords()) { + if (auto it = max_apply_page_ids.find(r.page_id.high); it == max_apply_page_ids.end()) + { + max_apply_page_ids[r.page_id.high] = r.page_id.low; + } + else + { + it->second = std::max(it->second, r.page_id.low); + } + if (max_applied_ver < r.version) max_applied_ver = r.version; max_applied_page_id = std::max(r.page_id, max_applied_page_id); diff --git a/dbms/src/Storages/Page/V3/PageDirectoryFactory.h b/dbms/src/Storages/Page/V3/PageDirectoryFactory.h index 278298d7010..4136e626050 100644 --- a/dbms/src/Storages/Page/V3/PageDirectoryFactory.h +++ b/dbms/src/Storages/Page/V3/PageDirectoryFactory.h @@ -58,11 +58,17 @@ class PageDirectoryFactory return *this; } + std::map getMaxApplyPageIds() const + { + return max_apply_page_ids; + } + private: void loadFromDisk(const PageDirectoryPtr & dir, WALStoreReaderPtr && reader); void loadEdit(const PageDirectoryPtr & dir, const PageEntriesEdit & edit); BlobStore::BlobStats * blob_stats = nullptr; + std::map max_apply_page_ids; }; } // namespace PS::V3 diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp index bb48f18c658..ab1ba0b04e1 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp @@ -43,7 +43,7 @@ PageStorageImpl::PageStorageImpl( PageStorageImpl::~PageStorageImpl() = default; -void PageStorageImpl::restore() +std::map PageStorageImpl::restore() { // TODO: clean up blobstore. // TODO: Speedup restoring @@ -53,7 +53,7 @@ void PageStorageImpl::restore() page_directory = factory .setBlobStore(blob_store) .create(storage_name, file_provider, delegator, parseWALConfig(config)); - // factory.max_applied_page_id // TODO: return it to outer function + return factory.getMaxApplyPageIds(); } void PageStorageImpl::drop() @@ -61,11 +61,6 @@ void PageStorageImpl::drop() throw Exception("Not implemented", ErrorCodes::NOT_IMPLEMENTED); } -PageId PageStorageImpl::getMaxId(NamespaceId ns_id) -{ - return page_directory->getMaxId(ns_id); -} - PageId PageStorageImpl::getNormalPageIdImpl(NamespaceId ns_id, PageId page_id, SnapshotPtr snapshot, bool throw_on_not_exist) { if (!snapshot) diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.h b/dbms/src/Storages/Page/V3/PageStorageImpl.h index e3df872b1e1..7e86a110ab9 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.h +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.h @@ -60,11 +60,10 @@ class PageStorageImpl : public DB::PageStorage return wal_config; } - void restore() override; + std::map restore() override; void drop() override; - PageId getMaxId(NamespaceId ns_id) override; PageId getNormalPageIdImpl(NamespaceId ns_id, PageId page_id, SnapshotPtr snapshot, bool throw_on_not_exist) override; diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp index 7bbc882f62b..a1cf73dd10a 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp @@ -66,6 +66,16 @@ class PageStorageTest : public DB::base::TiFlashStorageTestBasic return storage; } + + std::pair, std::map> reopen() + { + auto path = getTemporaryPath(); + auto delegator = std::make_shared(path); + auto storage = std::make_shared("test.t", delegator, config, file_provider); + auto max_ids = storage->restore(); + return {storage, max_ids}; + } + protected: FileProviderPtr file_provider; std::unique_ptr path_pool; @@ -1300,5 +1310,41 @@ try } CATCH +TEST_F(PageStorageTest, getMaxIdsFromRestore) +try +{ + { + WriteBatch batch; + batch.putExternal(1, 0); + batch.putExternal(2, 0); + batch.delPage(1); + batch.delPage(2); + page_storage->write(std::move(batch)); + + WriteBatch batch2{TEST_NAMESPACE_ID + 1}; + batch2.putExternal(1, 0); + batch2.putExternal(2, 0); + batch2.putRefPage(3, 1); + batch2.putRefPage(100, 2); + page_storage->write(std::move(batch2)); + + WriteBatch batch3{TEST_NAMESPACE_ID + 2}; + batch3.putExternal(1, 0); + batch3.putExternal(2, 0); + batch3.putRefPage(3, 1); + batch3.putRefPage(10, 2); + batch3.delPage(10); + page_storage->write(std::move(batch3)); + } + + page_storage = nullptr; + auto [page_storage, max_ids] = reopen(); + ASSERT_EQ(max_ids.size(), 3); + ASSERT_EQ(max_ids[TEST_NAMESPACE_ID], 2); // external page 2 is marked as deleted, but we can still restore it. + ASSERT_EQ(max_ids[TEST_NAMESPACE_ID + 1], 100); + ASSERT_EQ(max_ids[TEST_NAMESPACE_ID + 2], 10); // page 10 is marked as deleted, but we can still restore it. +} +CATCH + } // namespace PS::V3::tests } // namespace DB diff --git a/tests/delta-merge-test/ddl/alter.test b/tests/delta-merge-test/ddl/alter.test index 4bf405ac9e1..3dc57b05843 100644 --- a/tests/delta-merge-test/ddl/alter.test +++ b/tests/delta-merge-test/ddl/alter.test @@ -73,18 +73,19 @@ ## rename table ->> drop table if exists dm_test_renamed ->> rename table dm_test to dm_test_renamed ->> select * from dm_test -Received exception from server (version {#WORD}): -Code: 60. DB::Exception: Received from {#WORD} DB::Exception: Table default.dm_test doesn't exist.. +# FIXME: No support rename after PR 4850 (PageStorage V3) +# >> drop table if exists dm_test_renamed +# >> rename table dm_test to dm_test_renamed +# >> select * from dm_test +# Received exception from server (version {#WORD}): +# Code: 60. DB::Exception: Received from {#WORD} DB::Exception: Table default.dm_test doesn't exist.. ->> select * from dm_test_renamed -┌─a─┬────b─┬─────c─┬────d─┐ -│ 1 │ 0 │ 0 │ \N │ -│ 2 │ 1024 │ 65535 │ 4096 │ -│ 3 │ 2048 │ 65536 │ \N │ -└───┴──────┴───────┴──────┘ +# >> select * from dm_test_renamed +# ┌─a─┬────b─┬─────c─┬────d─┐ +# │ 1 │ 0 │ 0 │ \N │ +# │ 2 │ 1024 │ 65535 │ 4096 │ +# │ 3 │ 2048 │ 65536 │ \N │ +# └───┴──────┴───────┴──────┘ ## Clean up diff --git a/tests/delta-merge-test/raft/txn_mock/partition_table.test b/tests/delta-merge-test/raft/txn_mock/partition_table.test index 2f8e67a61a8..3967d34b48e 100644 --- a/tests/delta-merge-test/raft/txn_mock/partition_table.test +++ b/tests/delta-merge-test/raft/txn_mock/partition_table.test @@ -94,19 +94,20 @@ │ 0 │ └──────────────┘ -=> DBGInvoke __rename_tidb_table(default, test, test1) -=> DBGInvoke __refresh_schemas() -=> select count(*) from default.test1_9997 -┌─count()─┐ -│ 2 │ -└─────────┘ - -=> DBGInvoke __drop_tidb_table(default, test1) -=> DBGInvoke __refresh_schemas() -=> DBGInvoke is_tombstone(default, test1_9999) -┌─is_tombstone(default, test_9999)─┐ -│ true │ -└──────────────────────────────────┘ +# FIXME: No support rename after PR 4850 (PageStorage V3) +# => DBGInvoke __rename_tidb_table(default, test, test1) +# => DBGInvoke __refresh_schemas() +# => select count(*) from default.test1_9997 +# ┌─count()─┐ +# │ 2 │ +# └─────────┘ + +# => DBGInvoke __drop_tidb_table(default, test1) +# => DBGInvoke __refresh_schemas() +# => DBGInvoke is_tombstone(default, test1_9999) +# ┌─is_tombstone(default, test_9999)─┐ +# │ true │ +# └──────────────────────────────────┘ => drop table if exists default.test => drop table if exists default.test1 From 1b791ce7c15193d318e9c950beece286c555fd08 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Wed, 11 May 2022 15:20:35 +0800 Subject: [PATCH 006/127] Fix bug for deseri type error that cause data error (#4854) close pingcap/tiflash#4851 --- dbms/src/Storages/Page/V3/PageDirectory.cpp | 10 +++ dbms/src/Storages/Page/V3/WAL/serialize.cpp | 3 +- .../Page/V3/tests/gtest_page_directory.cpp | 75 ++++++++++++++++++- .../Page/V3/tests/gtest_wal_store.cpp | 55 ++++++++++++++ 4 files changed, 140 insertions(+), 3 deletions(-) diff --git a/dbms/src/Storages/Page/V3/PageDirectory.cpp b/dbms/src/Storages/Page/V3/PageDirectory.cpp index aef4e9e1922..2a272392b70 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectory.cpp @@ -334,6 +334,11 @@ VersionedPageEntries::resolveToPageId(UInt64 seq, bool check_prev, PageEntryV3 * return {RESOLVE_TO_REF, ori_page_id, create_ver}; } } + else + { + LOG_FMT_WARNING(&Poco::Logger::get("VersionedPageEntries"), "Can't reslove the EditRecordType {}", type); + } + return {RESOLVE_FAIL, buildV3Id(0, 0), PageVersionType(0)}; } @@ -716,6 +721,11 @@ PageIDAndEntryV3 PageDirectory::get(PageIdV3Internal page_id, const PageDirector { if (throw_on_not_exist) { + LOG_FMT_WARNING(log, "Dump state for invalid page id [page_id={}]", page_id); + for (const auto & [dump_id, dump_entry] : mvcc_table_directory) + { + LOG_FMT_WARNING(log, "Dumping state [page_id={}] [entry={}]", dump_id, dump_entry == nullptr ? "" : dump_entry->toDebugString()); + } throw Exception(fmt::format("Invalid page id, entry not exist [page_id={}] [resolve_id={}]", page_id, id_to_resolve), ErrorCodes::PS_ENTRY_NOT_EXISTS); } else diff --git a/dbms/src/Storages/Page/V3/WAL/serialize.cpp b/dbms/src/Storages/Page/V3/WAL/serialize.cpp index 45104b50cea..26854f9a640 100644 --- a/dbms/src/Storages/Page/V3/WAL/serialize.cpp +++ b/dbms/src/Storages/Page/V3/WAL/serialize.cpp @@ -100,7 +100,6 @@ void deserializePutFrom([[maybe_unused]] const EditRecordType record_type, ReadB UInt32 flags = 0; readIntBinary(flags, buf); - // All consider as put PageEntriesEdit::EditRecord rec; rec.type = record_type; readIntBinary(rec.page_id, buf); @@ -152,7 +151,7 @@ void deserializePutExternalFrom([[maybe_unused]] const EditRecordType record_typ assert(record_type == EditRecordType::PUT_EXTERNAL || record_type == EditRecordType::VAR_EXTERNAL); PageEntriesEdit::EditRecord rec; - rec.type = EditRecordType::PUT_EXTERNAL; + rec.type = record_type; readIntBinary(rec.page_id, buf); deserializeVersionFrom(buf, rec.version); readIntBinary(rec.being_ref_count, buf); diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp index ad00c47c097..9789fd0fe83 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -1760,16 +1761,88 @@ try } CATCH + +TEST_F(PageDirectoryGCTest, GCOnRefedExternalEntries2) +try +{ + { + PageEntriesEdit edit; // ingest + edit.putExternal(352); + dir->apply(std::move(edit)); + } + { + PageEntriesEdit edit; + edit.ref(353, 352); + dir->apply(std::move(edit)); + } + { + PageEntriesEdit edit; // ingest done + edit.del(352); + dir->apply(std::move(edit)); + } + { + PageEntriesEdit edit; // split + edit.ref(357, 352); + edit.ref(359, 352); + dir->apply(std::move(edit)); + } + { + PageEntriesEdit edit; // split done + edit.del(353); + dir->apply(std::move(edit)); + } + { + PageEntriesEdit edit; // one of segment delta-merge + edit.del(359); + dir->apply(std::move(edit)); + } + + { + auto snap = dir->createSnapshot(); + auto normal_id = dir->getNormalPageId(357, snap); + EXPECT_EQ(normal_id.low, 352); + } + dir->gcInMemEntries(); + { + auto snap = dir->createSnapshot(); + auto normal_id = dir->getNormalPageId(357, snap); + EXPECT_EQ(normal_id.low, 352); + } + + auto s0 = dir->createSnapshot(); + auto edit = dir->dumpSnapshotToEdit(s0); + edit.size(); + auto restore_from_edit = [](const PageEntriesEdit & edit) { + auto deseri_edit = DB::PS::V3::ser::deserializeFrom(DB::PS::V3::ser::serializeTo(edit)); + auto ctx = DB::tests::TiFlashTestEnv::getContext(); + auto provider = ctx.getFileProvider(); + auto path = getTemporaryPath(); + PSDiskDelegatorPtr delegator = std::make_shared(path); + PageDirectoryFactory factory; + auto d = factory.createFromEdit(getCurrentTestName(), provider, delegator, deseri_edit); + return d; + }; + { + auto restored_dir = restore_from_edit(edit); + auto snap = restored_dir->createSnapshot(); + auto normal_id = restored_dir->getNormalPageId(357, snap); + EXPECT_EQ(normal_id.low, 352); + } +} +CATCH + + TEST_F(PageDirectoryGCTest, DumpAndRestore) try { auto restore_from_edit = [](const PageEntriesEdit & edit) { + auto deseri_edit = DB::PS::V3::ser::deserializeFrom(DB::PS::V3::ser::serializeTo(edit)); auto ctx = DB::tests::TiFlashTestEnv::getContext(); auto provider = ctx.getFileProvider(); auto path = getTemporaryPath(); PSDiskDelegatorPtr delegator = std::make_shared(path); PageDirectoryFactory factory; - auto d = factory.createFromEdit(getCurrentTestName(), provider, delegator, edit); + auto d = factory.createFromEdit(getCurrentTestName(), provider, delegator, deseri_edit); return d; }; diff --git a/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp b/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp index 23ee2e93f07..fadc0fb3bae 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp @@ -133,6 +133,61 @@ TEST(WALSeriTest, Upserts) EXPECT_SAME_ENTRY(iter->entry, entry_p5_2); } +TEST(WALSeriTest, RefExternalAndEntry) +{ + PageVersionType ver1_0(/*seq=*/1, /*epoch*/ 0); + PageVersionType ver2_0(/*seq=*/2, /*epoch*/ 0); + PageVersionType ver3_0(/*seq=*/3, /*epoch*/ 0); + { + PageEntriesEdit edit; + edit.varExternal(1, ver1_0, 2); + edit.varDel(1, ver2_0); + edit.varRef(2, ver3_0, 1); + + auto deseri_edit = DB::PS::V3::ser::deserializeFrom(DB::PS::V3::ser::serializeTo(edit)); + ASSERT_EQ(deseri_edit.size(), 3); + auto iter = deseri_edit.getRecords().begin(); + EXPECT_EQ(iter->type, EditRecordType::VAR_EXTERNAL); + EXPECT_EQ(iter->page_id.low, 1); + EXPECT_EQ(iter->version, ver1_0); + EXPECT_EQ(iter->being_ref_count, 2); + iter++; + EXPECT_EQ(iter->type, EditRecordType::VAR_DELETE); + EXPECT_EQ(iter->page_id.low, 1); + EXPECT_EQ(iter->version, ver2_0); + EXPECT_EQ(iter->being_ref_count, 1); + iter++; + EXPECT_EQ(iter->type, EditRecordType::VAR_REF); + EXPECT_EQ(iter->page_id.low, 2); + EXPECT_EQ(iter->version, ver3_0); + } + + { + PageEntriesEdit edit; + PageEntryV3 entry_p1_2{.file_id = 2, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + edit.varEntry(1, ver1_0, entry_p1_2, 2); + edit.varDel(1, ver2_0); + edit.varRef(2, ver3_0, 1); + + auto deseri_edit = DB::PS::V3::ser::deserializeFrom(DB::PS::V3::ser::serializeTo(edit)); + ASSERT_EQ(deseri_edit.size(), 3); + auto iter = deseri_edit.getRecords().begin(); + EXPECT_EQ(iter->type, EditRecordType::VAR_ENTRY); + EXPECT_EQ(iter->page_id.low, 1); + EXPECT_EQ(iter->version, ver1_0); + EXPECT_EQ(iter->being_ref_count, 2); + iter++; + EXPECT_EQ(iter->type, EditRecordType::VAR_DELETE); + EXPECT_EQ(iter->page_id.low, 1); + EXPECT_EQ(iter->version, ver2_0); + EXPECT_EQ(iter->being_ref_count, 1); + iter++; + EXPECT_EQ(iter->type, EditRecordType::VAR_REF); + EXPECT_EQ(iter->page_id.low, 2); + EXPECT_EQ(iter->version, ver3_0); + } +} + TEST(WALLognameTest, parsing) { LoggerPtr log = Logger::get("WALLognameTest"); From a446006bccaf6a9137a72ad67541408cf0b052a6 Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Wed, 11 May 2022 16:16:34 +0800 Subject: [PATCH 007/127] Interpreter: Remove Optimize query. (#4589) ref pingcap/tiflash#4860 --- dbms/src/Interpreters/InterpreterFactory.cpp | 7 -- .../Interpreters/InterpreterOptimizeQuery.cpp | 43 ---------- .../Interpreters/InterpreterOptimizeQuery.h | 46 ---------- dbms/src/Parsers/ASTOptimizeQuery.h | 76 ---------------- dbms/src/Parsers/ParserOptimizeQuery.cpp | 86 ------------------- dbms/src/Parsers/ParserOptimizeQuery.h | 33 ------- dbms/src/Parsers/ParserQuery.cpp | 21 ++--- 7 files changed, 8 insertions(+), 304 deletions(-) delete mode 100644 dbms/src/Interpreters/InterpreterOptimizeQuery.cpp delete mode 100644 dbms/src/Interpreters/InterpreterOptimizeQuery.h delete mode 100644 dbms/src/Parsers/ASTOptimizeQuery.h delete mode 100644 dbms/src/Parsers/ParserOptimizeQuery.cpp delete mode 100644 dbms/src/Parsers/ParserOptimizeQuery.h diff --git a/dbms/src/Interpreters/InterpreterFactory.cpp b/dbms/src/Interpreters/InterpreterFactory.cpp index 5231bbd3dd6..631df49227e 100644 --- a/dbms/src/Interpreters/InterpreterFactory.cpp +++ b/dbms/src/Interpreters/InterpreterFactory.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -45,7 +44,6 @@ #include #include #include -#include #include #include #include @@ -126,11 +124,6 @@ std::unique_ptr InterpreterFactory::get(ASTPtr & query, Context & /// readonly is checked inside InterpreterSetQuery return std::make_unique(query, context); } - else if (typeid_cast(query.get())) - { - throwIfReadOnly(context); - return std::make_unique(query, context); - } else if (typeid_cast(query.get())) { return std::make_unique(query, context); diff --git a/dbms/src/Interpreters/InterpreterOptimizeQuery.cpp b/dbms/src/Interpreters/InterpreterOptimizeQuery.cpp deleted file mode 100644 index 173065512b8..00000000000 --- a/dbms/src/Interpreters/InterpreterOptimizeQuery.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include - - -namespace DB -{ -namespace ErrorCodes -{ -extern const int BAD_ARGUMENTS; -} - - -BlockIO InterpreterOptimizeQuery::execute() -{ - const ASTOptimizeQuery & ast = typeid_cast(*query_ptr); - - if (ast.final && !ast.partition) - throw Exception("FINAL flag for OPTIMIZE query is meaningful only with specified PARTITION", ErrorCodes::BAD_ARGUMENTS); - - StoragePtr table = context.getTable(ast.database, ast.table); - auto table_lock = table->lockStructureForShare(RWLock::NO_QUERY); - table->optimize(query_ptr, ast.partition, ast.final, ast.deduplicate, context); - return {}; -} - -} // namespace DB diff --git a/dbms/src/Interpreters/InterpreterOptimizeQuery.h b/dbms/src/Interpreters/InterpreterOptimizeQuery.h deleted file mode 100644 index bf39aba9c71..00000000000 --- a/dbms/src/Interpreters/InterpreterOptimizeQuery.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - - -namespace DB -{ -class Context; -class IAST; -using ASTPtr = std::shared_ptr; - - -/** Just call method "optimize" for table. - */ -class InterpreterOptimizeQuery : public IInterpreter -{ -public: - InterpreterOptimizeQuery(const ASTPtr & query_ptr_, Context & context_) - : query_ptr(query_ptr_) - , context(context_) - { - } - - BlockIO execute() override; - -private: - ASTPtr query_ptr; - Context & context; -}; - - -} // namespace DB diff --git a/dbms/src/Parsers/ASTOptimizeQuery.h b/dbms/src/Parsers/ASTOptimizeQuery.h deleted file mode 100644 index 17d9e6ab41a..00000000000 --- a/dbms/src/Parsers/ASTOptimizeQuery.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - - -namespace DB -{ - - -/** OPTIMIZE query - */ -class ASTOptimizeQuery : public IAST -{ -public: - String database; - String table; - - /// The partition to optimize can be specified. - ASTPtr partition; - /// A flag can be specified - perform optimization "to the end" instead of one step. - bool final; - /// Do deduplicate (default: false) - bool deduplicate; - - /** Get the text that identifies this element. */ - String getID() const override { return "OptimizeQuery_" + database + "_" + table + (final ? "_final" : "") + (deduplicate ? "_deduplicate" : ""); }; - - ASTPtr clone() const override - { - auto res = std::make_shared(*this); - res->children.clear(); - - if (partition) - { - res->partition = partition->clone(); - res->children.push_back(res->partition); - } - - return res; - } - -protected: - void formatImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override - { - settings.ostr << (settings.hilite ? hilite_keyword : "") << "OPTIMIZE TABLE " << (settings.hilite ? hilite_none : "") - << (!database.empty() ? backQuoteIfNeed(database) + "." : "") << backQuoteIfNeed(table); - - if (partition) - { - settings.ostr << (settings.hilite ? hilite_keyword : "") << " PARTITION " << (settings.hilite ? hilite_none : ""); - partition->formatImpl(settings, state, frame); - } - - if (final) - settings.ostr << (settings.hilite ? hilite_keyword : "") << " FINAL" << (settings.hilite ? hilite_none : ""); - - if (deduplicate) - settings.ostr << (settings.hilite ? hilite_keyword : "") << " DEDUPLICATE" << (settings.hilite ? hilite_none : ""); - } -}; - -} diff --git a/dbms/src/Parsers/ParserOptimizeQuery.cpp b/dbms/src/Parsers/ParserOptimizeQuery.cpp deleted file mode 100644 index 1b5f82ff682..00000000000 --- a/dbms/src/Parsers/ParserOptimizeQuery.cpp +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include - -#include -#include -#include - -#include - - -namespace DB -{ - - -bool ParserOptimizeQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) -{ - ParserKeyword s_optimize_table("OPTIMIZE TABLE"); - ParserKeyword s_partition("PARTITION"); - ParserKeyword s_final("FINAL"); - ParserKeyword s_deduplicate("DEDUPLICATE"); - ParserToken s_dot(TokenType::Dot); - ParserIdentifier name_p; - ParserPartition partition_p; - - ASTPtr database; - ASTPtr table; - ASTPtr partition; - bool final = false; - bool deduplicate = false; - - if (!s_optimize_table.ignore(pos, expected)) - return false; - - if (!name_p.parse(pos, table, expected)) - return false; - - if (s_dot.ignore(pos, expected)) - { - database = table; - if (!name_p.parse(pos, table, expected)) - return false; - } - - if (s_partition.ignore(pos, expected)) - { - if (!partition_p.parse(pos, partition, expected)) - return false; - } - - if (s_final.ignore(pos, expected)) - final = true; - - if (s_deduplicate.ignore(pos, expected)) - deduplicate = true; - - auto query = std::make_shared(); - node = query; - - if (database) - query->database = typeid_cast(*database).name; - if (table) - query->table = typeid_cast(*table).name; - query->partition = partition; - query->final = final; - query->deduplicate = deduplicate; - - return true; -} - - -} diff --git a/dbms/src/Parsers/ParserOptimizeQuery.h b/dbms/src/Parsers/ParserOptimizeQuery.h deleted file mode 100644 index c12cfb80c90..00000000000 --- a/dbms/src/Parsers/ParserOptimizeQuery.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include -#include - - -namespace DB -{ - -/** Query OPTIMIZE TABLE [db.]name [PARTITION partition] [FINAL] [DEDUPLICATE] - */ -class ParserOptimizeQuery : public IParserBase -{ -protected: - const char * getName() const { return "OPTIMIZE query"; } - bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected); -}; - -} diff --git a/dbms/src/Parsers/ParserQuery.cpp b/dbms/src/Parsers/ParserQuery.cpp index f6bb7d8ca32..4e20221df82 100644 --- a/dbms/src/Parsers/ParserQuery.cpp +++ b/dbms/src/Parsers/ParserQuery.cpp @@ -12,26 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#include #include -#include -#include #include +#include #include +#include +#include +#include +#include #include -#include -#include #include -#include #include #include -#include +#include namespace DB { - - bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) { ParserQueryWithOutput query_with_output_p; @@ -40,7 +37,6 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) ParserSetQuery set_p; ParserDeleteQuery delete_p; ParserDBGInvokeQuery dbginvoke_p; - ParserOptimizeQuery optimize_p; ParserSystemQuery system_p; ParserTruncateQuery truncate_p; ParserManageQuery manage_p; @@ -51,7 +47,6 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) || set_p.parse(pos, node, expected) || delete_p.parse(pos, node, expected) || dbginvoke_p.parse(pos, node, expected) - || optimize_p.parse(pos, node, expected) || system_p.parse(pos, node, expected) || truncate_p.parse(pos, node, expected) || manage_p.parse(pos, node, expected); @@ -59,4 +54,4 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) return res; } -} +} // namespace DB From c88f1ad5fbbe5ad29e889dc51049b31ab4c0c5ab Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Wed, 11 May 2022 18:00:35 +0800 Subject: [PATCH 008/127] Interpreter: Remove kill Query (#4564) close pingcap/tiflash#4567 --- dbms/src/Interpreters/InterpreterFactory.cpp | 6 - .../InterpreterKillQueryQuery.cpp | 241 ------------------ .../Interpreters/InterpreterKillQueryQuery.h | 45 ---- dbms/src/Interpreters/ProcessList.cpp | 6 - dbms/src/Parsers/ASTKillQueryQuery.cpp | 35 --- dbms/src/Parsers/ASTKillQueryQuery.h | 36 --- dbms/src/Parsers/ParserKillQueryQuery.cpp | 52 ---- dbms/src/Parsers/ParserKillQueryQuery.h | 33 --- dbms/src/Parsers/ParserQueryWithOutput.cpp | 24 +- 9 files changed, 10 insertions(+), 468 deletions(-) delete mode 100644 dbms/src/Interpreters/InterpreterKillQueryQuery.cpp delete mode 100644 dbms/src/Interpreters/InterpreterKillQueryQuery.h delete mode 100644 dbms/src/Parsers/ASTKillQueryQuery.cpp delete mode 100644 dbms/src/Parsers/ASTKillQueryQuery.h delete mode 100644 dbms/src/Parsers/ParserKillQueryQuery.cpp delete mode 100644 dbms/src/Parsers/ParserKillQueryQuery.h diff --git a/dbms/src/Interpreters/InterpreterFactory.cpp b/dbms/src/Interpreters/InterpreterFactory.cpp index 631df49227e..e319ca5d7ec 100644 --- a/dbms/src/Interpreters/InterpreterFactory.cpp +++ b/dbms/src/Interpreters/InterpreterFactory.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -42,7 +41,6 @@ #include #include #include -#include #include #include #include @@ -153,10 +151,6 @@ std::unique_ptr InterpreterFactory::get(ASTPtr & query, Context & { return std::make_unique(query, context); } - else if (typeid_cast(query.get())) - { - return std::make_unique(query, context); - } else if (typeid_cast(query.get())) { bool allow_materialized = static_cast(context.getSettingsRef().insert_allow_materialized_columns); diff --git a/dbms/src/Interpreters/InterpreterKillQueryQuery.cpp b/dbms/src/Interpreters/InterpreterKillQueryQuery.cpp deleted file mode 100644 index 9221c3ff546..00000000000 --- a/dbms/src/Interpreters/InterpreterKillQueryQuery.cpp +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - - -namespace DB -{ -namespace ErrorCodes -{ -extern const int READONLY; -extern const int LOGICAL_ERROR; -} // namespace ErrorCodes - - -using CancellationCode = ProcessList::CancellationCode; - -static const char * cancellationCodeToStatus(CancellationCode code) -{ - switch (code) - { - case CancellationCode::NotFound: - return "finished"; - case CancellationCode::QueryIsNotInitializedYet: - return "pending"; - case CancellationCode::CancelCannotBeSent: - return "error"; - case CancellationCode::CancelSent: - return "waiting"; - default: - return "unknown_status"; - }; -} - - -struct QueryDescriptor -{ - String query_id; - String user; - size_t source_num; - bool processed = false; - - QueryDescriptor(String && query_id_, String && user_, size_t source_num_, bool processed_ = false) - : query_id(std::move(query_id_)) - , user(std::move(user_)) - , source_num(source_num_) - , processed(processed_) - {} -}; - -using QueryDescriptors = std::vector; - - -static void insertResultRow(size_t n, CancellationCode code, const Block & source_processes, const Block & sample_block, MutableColumns & columns) -{ - columns[0]->insert(String(cancellationCodeToStatus(code))); - - for (size_t col_num = 1, size = columns.size(); col_num < size; ++col_num) - columns[col_num]->insertFrom(*source_processes.getByName(sample_block.getByPosition(col_num).name).column, n); -} - -static QueryDescriptors extractQueriesExceptMeAndCheckAccess(const Block & processes_block, Context & context) -{ - QueryDescriptors res; - size_t num_processes = processes_block.rows(); - res.reserve(num_processes); - - const ColumnString & query_id_col = typeid_cast(*processes_block.getByName("query_id").column); - const ColumnString & user_col = typeid_cast(*processes_block.getByName("user").column); - const ClientInfo & my_client = context.getProcessListElement()->getClientInfo(); - - for (size_t i = 0; i < num_processes; ++i) - { - auto query_id = query_id_col.getDataAt(i).toString(); - auto user = user_col.getDataAt(i).toString(); - - if (my_client.current_query_id == query_id && my_client.current_user == user) - continue; - - if (context.getSettingsRef().readonly && my_client.current_user != user) - { - throw Exception("Readonly user " + my_client.current_user + " attempts to kill query created by " + user, - ErrorCodes::READONLY); - } - - res.emplace_back(std::move(query_id), std::move(user), i, false); - } - - return res; -} - - -class SyncKillQueryInputStream : public IProfilingBlockInputStream -{ -public: - SyncKillQueryInputStream(ProcessList & process_list_, QueryDescriptors && processes_to_stop_, Block && processes_block_, const Block & res_sample_block_) - : process_list(process_list_) - , processes_to_stop(std::move(processes_to_stop_)) - , processes_block(std::move(processes_block_)) - , res_sample_block(res_sample_block_) - { - addTotalRowsApprox(processes_to_stop.size()); - } - - String getName() const override - { - return "SynchronousQueryKiller"; - } - - Block getHeader() const override { return res_sample_block; }; - - Block readImpl() override - { - size_t num_result_queries = processes_to_stop.size(); - - if (num_processed_queries >= num_result_queries) - return Block(); - - MutableColumns columns = res_sample_block.cloneEmptyColumns(); - - do - { - for (auto & curr_process : processes_to_stop) - { - if (curr_process.processed) - continue; - - auto code = process_list.sendCancelToQuery(curr_process.query_id, curr_process.user, true); - - if (code != CancellationCode::QueryIsNotInitializedYet && code != CancellationCode::CancelSent) - { - curr_process.processed = true; - insertResultRow(curr_process.source_num, code, processes_block, res_sample_block, columns); - ++num_processed_queries; - } - /// Wait if QueryIsNotInitializedYet or CancelSent - } - - /// KILL QUERY could be killed also - if (isCancelled()) - break; - - /// Sleep if there are unprocessed queries - if (num_processed_queries < num_result_queries) - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - /// Don't produce empty block - } while (columns.empty() || columns[0]->empty()); - - return res_sample_block.cloneWithColumns(std::move(columns)); - } - - ProcessList & process_list; - QueryDescriptors processes_to_stop; - Block processes_block; - Block res_sample_block; - size_t num_processed_queries = 0; -}; - - -BlockIO InterpreterKillQueryQuery::execute() -{ - ASTKillQueryQuery & query = typeid_cast(*query_ptr); - - BlockIO res_io; - Block processes_block = getSelectFromSystemProcessesResult(); - if (!processes_block) - return res_io; - - ProcessList & process_list = context.getProcessList(); - QueryDescriptors queries_to_stop = extractQueriesExceptMeAndCheckAccess(processes_block, context); - - auto header = processes_block.cloneEmpty(); - header.insert(0, {ColumnString::create(), std::make_shared(), "kill_status"}); - - if (!query.sync || query.test) - { - MutableColumns res_columns = header.cloneEmptyColumns(); - - for (const auto & query_desc : queries_to_stop) - { - auto code = (query.test) ? CancellationCode::Unknown : process_list.sendCancelToQuery(query_desc.query_id, query_desc.user, true); - insertResultRow(query_desc.source_num, code, processes_block, header, res_columns); - } - - res_io.in = std::make_shared(header.cloneWithColumns(std::move(res_columns))); - } - else - { - res_io.in = std::make_shared( - process_list, - std::move(queries_to_stop), - std::move(processes_block), - header); - } - - return res_io; -} - -Block InterpreterKillQueryQuery::getSelectFromSystemProcessesResult() -{ - String system_processes_query = "SELECT query_id, user, query FROM system.processes WHERE " - + queryToString(static_cast(*query_ptr).where_expression); - - BlockIO system_processes_io = executeQuery(system_processes_query, context, true); - Block res = system_processes_io.in->read(); - - if (res && system_processes_io.in->read()) - throw Exception("Expected one block from input stream", ErrorCodes::LOGICAL_ERROR); - - return res; -} - - -} // namespace DB diff --git a/dbms/src/Interpreters/InterpreterKillQueryQuery.h b/dbms/src/Interpreters/InterpreterKillQueryQuery.h deleted file mode 100644 index cb80d92930e..00000000000 --- a/dbms/src/Interpreters/InterpreterKillQueryQuery.h +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - - -namespace DB -{ -class Context; -class IAST; -using ASTPtr = std::shared_ptr; - - -class InterpreterKillQueryQuery : public IInterpreter -{ -public: - InterpreterKillQueryQuery(const ASTPtr & query_ptr_, Context & context_) - : query_ptr(query_ptr_) - , context(context_) - {} - - BlockIO execute() override; - -private: - Block getSelectFromSystemProcessesResult(); - - ASTPtr query_ptr; - Context & context; -}; - - -} // namespace DB diff --git a/dbms/src/Interpreters/ProcessList.cpp b/dbms/src/Interpreters/ProcessList.cpp index 606a1ce1f17..0f667cfd396 100644 --- a/dbms/src/Interpreters/ProcessList.cpp +++ b/dbms/src/Interpreters/ProcessList.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -42,11 +41,6 @@ static bool isUnlimitedQuery(const IAST * ast) { if (!ast) return false; - - /// It is KILL QUERY - if (typeid_cast(ast)) - return true; - /// It is SELECT FROM system.processes /// NOTE: This is very rough check. /// False negative: USE system; SELECT * FROM processes; diff --git a/dbms/src/Parsers/ASTKillQueryQuery.cpp b/dbms/src/Parsers/ASTKillQueryQuery.cpp deleted file mode 100644 index aa24f75d5ef..00000000000 --- a/dbms/src/Parsers/ASTKillQueryQuery.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include - -namespace DB -{ - -String ASTKillQueryQuery::getID() const -{ - return "KillQueryQuery_" + (where_expression ? where_expression->getID() : "") + "_" + String(sync ? "SYNC" : "ASYNC"); -} - -void ASTKillQueryQuery::formatQueryImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const -{ - settings.ostr << (settings.hilite ? hilite_keyword : "") << "KILL QUERY WHERE " << (settings.hilite ? hilite_none : ""); - - if (where_expression) - where_expression->formatImpl(settings, state, frame); - - settings.ostr << " " << (settings.hilite ? hilite_keyword : "") << (test ? "TEST" : (sync ? "SYNC" : "ASYNC")) << (settings.hilite ? hilite_none : ""); -} - -} diff --git a/dbms/src/Parsers/ASTKillQueryQuery.h b/dbms/src/Parsers/ASTKillQueryQuery.h deleted file mode 100644 index 74c72f5bc79..00000000000 --- a/dbms/src/Parsers/ASTKillQueryQuery.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include -#include - -namespace DB -{ -class ASTKillQueryQuery : public ASTQueryWithOutput -{ -public: - ASTPtr where_expression; // expression to filter processes from system.processes table - bool sync = false; // SYNC or ASYNC mode - bool test = false; // does it TEST mode? (doesn't cancel queries just checks and shows them) - - ASTPtr clone() const override { return std::make_shared(*this); } - - String getID() const override; - - void formatQueryImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override; -}; - -} // namespace DB diff --git a/dbms/src/Parsers/ParserKillQueryQuery.cpp b/dbms/src/Parsers/ParserKillQueryQuery.cpp deleted file mode 100644 index e9f1be7170c..00000000000 --- a/dbms/src/Parsers/ParserKillQueryQuery.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include - -#include -#include - - -namespace DB -{ - - -bool ParserKillQueryQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) -{ - auto query = std::make_shared(); - - if (!ParserKeyword{"KILL QUERY"}.ignore(pos, expected)) - return false; - - if (!ParserKeyword{"WHERE"}.ignore(pos, expected)) - return false; - - ParserExpression p_where_expression; - if (!p_where_expression.parse(pos, query->where_expression, expected)) - return false; - - if (ParserKeyword{"SYNC"}.ignore(pos)) - query->sync = true; - else if (ParserKeyword{"ASYNC"}.ignore(pos)) - query->sync = false; - else if (ParserKeyword{"TEST"}.ignore(pos)) - query->test = true; - - node = std::move(query); - - return true; -} - -} diff --git a/dbms/src/Parsers/ParserKillQueryQuery.h b/dbms/src/Parsers/ParserKillQueryQuery.h deleted file mode 100644 index 6d29bfe0707..00000000000 --- a/dbms/src/Parsers/ParserKillQueryQuery.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - - -namespace DB -{ - -/** KILL QUERY WHERE [SYNC|ASYNC|TEST] - */ -class ParserKillQueryQuery : public IParserBase -{ -protected: - const char * getName() const override { return "KILL QUERY query"; } - bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override; -}; - -} - diff --git a/dbms/src/Parsers/ParserQueryWithOutput.cpp b/dbms/src/Parsers/ParserQueryWithOutput.cpp index 174d70e3dae..ca88b319bbe 100644 --- a/dbms/src/Parsers/ParserQueryWithOutput.cpp +++ b/dbms/src/Parsers/ParserQueryWithOutput.cpp @@ -12,23 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include -#include -#include -#include -#include +#include #include #include -#include -#include +#include #include -#include +#include +#include +#include +#include +#include +#include namespace DB { - bool ParserQueryWithOutput::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) { ParserShowTablesQuery show_tables_p; @@ -41,7 +39,6 @@ bool ParserQueryWithOutput::parseImpl(Pos & pos, ASTPtr & node, Expected & expec ParserRenameQuery rename_p; ParserDropQuery drop_p; ParserCheckQuery check_p; - ParserKillQueryQuery kill_query_p; ASTPtr query; @@ -54,8 +51,7 @@ bool ParserQueryWithOutput::parseImpl(Pos & pos, ASTPtr & node, Expected & expec || alter_p.parse(pos, query, expected) || rename_p.parse(pos, query, expected) || drop_p.parse(pos, query, expected) - || check_p.parse(pos, query, expected) - || kill_query_p.parse(pos, query, expected); + || check_p.parse(pos, query, expected); if (!parsed) return false; @@ -89,4 +85,4 @@ bool ParserQueryWithOutput::parseImpl(Pos & pos, ASTPtr & node, Expected & expec return true; } -} +} // namespace DB From f7d2d2850e0be4d3a4adc71c82e1088a9f65e791 Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Wed, 11 May 2022 20:06:34 +0800 Subject: [PATCH 009/127] Interpreter: Remove CheckQuery. (#4568) close pingcap/tiflash#4569 --- .../Interpreters/InterpreterCheckQuery.cpp | 124 ------------------ dbms/src/Interpreters/InterpreterCheckQuery.h | 38 ------ dbms/src/Interpreters/InterpreterFactory.cpp | 6 - dbms/src/Parsers/ASTCheckQuery.h | 60 --------- dbms/src/Parsers/ParserCheckQuery.cpp | 63 --------- dbms/src/Parsers/ParserCheckQuery.h | 31 ----- dbms/src/Parsers/ParserQueryWithOutput.cpp | 5 +- 7 files changed, 1 insertion(+), 326 deletions(-) delete mode 100644 dbms/src/Interpreters/InterpreterCheckQuery.cpp delete mode 100644 dbms/src/Interpreters/InterpreterCheckQuery.h delete mode 100644 dbms/src/Parsers/ASTCheckQuery.h delete mode 100644 dbms/src/Parsers/ParserCheckQuery.cpp delete mode 100644 dbms/src/Parsers/ParserCheckQuery.h diff --git a/dbms/src/Interpreters/InterpreterCheckQuery.cpp b/dbms/src/Interpreters/InterpreterCheckQuery.cpp deleted file mode 100644 index 9aa5079c9d8..00000000000 --- a/dbms/src/Interpreters/InterpreterCheckQuery.cpp +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace DB -{ -namespace ErrorCodes -{ -extern const int INVALID_BLOCK_EXTRA_INFO; -extern const int RECEIVED_EMPTY_DATA; -} // namespace ErrorCodes - - -namespace -{ -/// A helper structure for performing a response to a DESCRIBE TABLE query with a Distributed table. -/// Contains information about the local table that was retrieved from a single replica. -struct TableDescription -{ - TableDescription(const Block & block, const BlockExtraInfo & extra_info_) - : extra_info(extra_info_) - { - const auto & name_column = typeid_cast(*block.getByName("name").column); - const auto & type_column = typeid_cast(*block.getByName("type").column); - const auto & default_type_column = typeid_cast(*block.getByName("default_type").column); - const auto & default_expression_column = typeid_cast(*block.getByName("default_expression").column); - - size_t row_count = block.rows(); - - names_with_types.reserve(name_column.byteSize() + type_column.byteSize() + (3 * row_count)); - - SHA512_CTX ctx; - SHA512_Init(&ctx); - - bool is_first = true; - for (size_t i = 0; i < row_count; ++i) - { - const auto & name = name_column.getDataAt(i).toString(); - const auto & type = type_column.getDataAt(i).toString(); - const auto & default_type = default_type_column.getDataAt(i).toString(); - const auto & default_expression = default_expression_column.getDataAt(i).toString(); - - names_with_types.append(is_first ? "" : ", "); - names_with_types.append(name); - names_with_types.append(" "); - names_with_types.append(type); - - SHA512_Update(&ctx, reinterpret_cast(name.data()), name.size()); - SHA512_Update(&ctx, reinterpret_cast(type.data()), type.size()); - SHA512_Update(&ctx, reinterpret_cast(default_type.data()), default_type.size()); - SHA512_Update(&ctx, reinterpret_cast(default_expression.data()), default_expression.size()); - - is_first = false; - } - - SHA512_Final(hash.data(), &ctx); - } - - using Hash = std::array; - - BlockExtraInfo extra_info; - std::string names_with_types; - Hash hash; - UInt32 structure_class; -}; - -using TableDescriptions = std::deque; - -} // namespace - -InterpreterCheckQuery::InterpreterCheckQuery(const ASTPtr & query_ptr_, const Context & context_) - : query_ptr(query_ptr_) - , context(context_) -{ -} - - -BlockIO InterpreterCheckQuery::execute() -{ - ASTCheckQuery & alter = typeid_cast(*query_ptr); - String & table_name = alter.table; - String database_name = alter.database.empty() ? context.getCurrentDatabase() : alter.database; - - StoragePtr table = context.getTable(database_name, table_name); - - { - auto column = ColumnUInt8::create(); - column->insert(UInt64(table->checkData())); - result = Block{{std::move(column), std::make_shared(), "result"}}; - - BlockIO res; - res.in = std::make_shared(result); - - return res; - } -} - -} // namespace DB diff --git a/dbms/src/Interpreters/InterpreterCheckQuery.h b/dbms/src/Interpreters/InterpreterCheckQuery.h deleted file mode 100644 index b0de817ab53..00000000000 --- a/dbms/src/Interpreters/InterpreterCheckQuery.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include -#include - -namespace DB -{ -class Context; - -class InterpreterCheckQuery : public IInterpreter -{ -public: - InterpreterCheckQuery(const ASTPtr & query_ptr_, const Context & context_); - - BlockIO execute() override; - -private: - ASTPtr query_ptr; - - const Context & context; - Block result; -}; - -} // namespace DB diff --git a/dbms/src/Interpreters/InterpreterFactory.cpp b/dbms/src/Interpreters/InterpreterFactory.cpp index e319ca5d7ec..1007dbf8f71 100644 --- a/dbms/src/Interpreters/InterpreterFactory.cpp +++ b/dbms/src/Interpreters/InterpreterFactory.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -35,7 +34,6 @@ #include #include #include -#include #include #include #include @@ -147,10 +145,6 @@ std::unique_ptr InterpreterFactory::get(ASTPtr & query, Context & throwIfReadOnly(context); return std::make_unique(query, context); } - else if (typeid_cast(query.get())) - { - return std::make_unique(query, context); - } else if (typeid_cast(query.get())) { bool allow_materialized = static_cast(context.getSettingsRef().insert_allow_materialized_columns); diff --git a/dbms/src/Parsers/ASTCheckQuery.h b/dbms/src/Parsers/ASTCheckQuery.h deleted file mode 100644 index 9ece682c2b0..00000000000 --- a/dbms/src/Parsers/ASTCheckQuery.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - -namespace DB -{ - -struct ASTCheckQuery : public ASTQueryWithOutput -{ - /** Get the text that identifies this element. */ - String getID() const override { return ("CheckQuery_" + database + "_" + table); }; - - ASTPtr clone() const override - { - auto res = std::make_shared(*this); - res->children.clear(); - cloneOutputOptions(*res); - return res; - } - - std::string database; - std::string table; - -protected: - void formatQueryImpl(const FormatSettings & settings, FormatState &, FormatStateStacked frame) const override - { - std::string nl_or_nothing = settings.one_line ? "" : "\n"; - - std::string indent_str = settings.one_line ? "" : std::string(4 * frame.indent, ' '); - std::string nl_or_ws = settings.one_line ? " " : "\n"; - - settings.ostr << (settings.hilite ? hilite_keyword : "") << indent_str << "CHECK TABLE " << (settings.hilite ? hilite_none : ""); - - if (!table.empty()) - { - if (!database.empty()) - { - settings.ostr << (settings.hilite ? hilite_keyword : "") << indent_str << backQuoteIfNeed(database) << (settings.hilite ? hilite_none : ""); - settings.ostr << "."; - } - settings.ostr << (settings.hilite ? hilite_keyword : "") << indent_str << backQuoteIfNeed(table) << (settings.hilite ? hilite_none : ""); - } - } -}; - -} diff --git a/dbms/src/Parsers/ParserCheckQuery.cpp b/dbms/src/Parsers/ParserCheckQuery.cpp deleted file mode 100644 index ae668be50d8..00000000000 --- a/dbms/src/Parsers/ParserCheckQuery.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include - -#include - - -namespace DB -{ - -bool ParserCheckQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) -{ - ParserKeyword s_check_table("CHECK TABLE"); - ParserToken s_dot(TokenType::Dot); - - ParserIdentifier table_parser; - - ASTPtr table; - ASTPtr database; - - if (!s_check_table.ignore(pos, expected)) - return false; - if (!table_parser.parse(pos, database, expected)) - return false; - - if (s_dot.ignore(pos)) - { - if (!table_parser.parse(pos, table, expected)) - return false; - - auto query = std::make_shared(); - query->database = typeid_cast(*database).name; - query->table = typeid_cast(*table).name; - node = query; - } - else - { - table = database; - auto query = std::make_shared(); - query->table = typeid_cast(*table).name; - node = query; - } - - return true; -} - -} diff --git a/dbms/src/Parsers/ParserCheckQuery.h b/dbms/src/Parsers/ParserCheckQuery.h deleted file mode 100644 index 17d03fd3eec..00000000000 --- a/dbms/src/Parsers/ParserCheckQuery.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - -namespace DB -{ -/** Query of form - * CHECK [TABLE] [database.]table - */ -class ParserCheckQuery : public IParserBase -{ -protected: - const char * getName() const { return "ALTER query"; } - bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected); -}; - -} diff --git a/dbms/src/Parsers/ParserQueryWithOutput.cpp b/dbms/src/Parsers/ParserQueryWithOutput.cpp index ca88b319bbe..7a1c36e9b84 100644 --- a/dbms/src/Parsers/ParserQueryWithOutput.cpp +++ b/dbms/src/Parsers/ParserQueryWithOutput.cpp @@ -13,7 +13,6 @@ // limitations under the License. #include -#include #include #include #include @@ -38,7 +37,6 @@ bool ParserQueryWithOutput::parseImpl(Pos & pos, ASTPtr & node, Expected & expec ParserAlterQuery alter_p; ParserRenameQuery rename_p; ParserDropQuery drop_p; - ParserCheckQuery check_p; ASTPtr query; @@ -50,8 +48,7 @@ bool ParserQueryWithOutput::parseImpl(Pos & pos, ASTPtr & node, Expected & expec || create_p.parse(pos, query, expected) || alter_p.parse(pos, query, expected) || rename_p.parse(pos, query, expected) - || drop_p.parse(pos, query, expected) - || check_p.parse(pos, query, expected); + || drop_p.parse(pos, query, expected); if (!parsed) return false; From d99b0b7b3086a232c23390241b6f6f74343d3cd3 Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Wed, 11 May 2022 23:06:34 +0800 Subject: [PATCH 010/127] Interpreter: Remove Truncate query (#4587) ref pingcap/tiflash#4860 --- dbms/src/Functions/FunctionBinaryArithmetic.h | 1 + dbms/src/Interpreters/InterpreterFactory.cpp | 7 -- .../Interpreters/InterpreterTruncateQuery.cpp | 35 ---------- .../Interpreters/InterpreterTruncateQuery.h | 43 ------------ dbms/src/Parsers/ASTTruncateQuery.h | 47 ------------- dbms/src/Parsers/ParserQuery.cpp | 3 - dbms/src/Parsers/ParserTruncateQuery.cpp | 69 ------------------- dbms/src/Parsers/ParserTruncateQuery.h | 32 --------- 8 files changed, 1 insertion(+), 236 deletions(-) delete mode 100644 dbms/src/Interpreters/InterpreterTruncateQuery.cpp delete mode 100644 dbms/src/Interpreters/InterpreterTruncateQuery.h delete mode 100644 dbms/src/Parsers/ASTTruncateQuery.h delete mode 100644 dbms/src/Parsers/ParserTruncateQuery.cpp delete mode 100644 dbms/src/Parsers/ParserTruncateQuery.h diff --git a/dbms/src/Functions/FunctionBinaryArithmetic.h b/dbms/src/Functions/FunctionBinaryArithmetic.h index 648e0ad0287..0970d8ddd73 100644 --- a/dbms/src/Functions/FunctionBinaryArithmetic.h +++ b/dbms/src/Functions/FunctionBinaryArithmetic.h @@ -978,6 +978,7 @@ class FunctionBinaryArithmetic : public IFunction using Type = If, ResultType, typename T::NativeType>; }; + template struct RefineCls { diff --git a/dbms/src/Interpreters/InterpreterFactory.cpp b/dbms/src/Interpreters/InterpreterFactory.cpp index 1007dbf8f71..2dc63c13657 100644 --- a/dbms/src/Interpreters/InterpreterFactory.cpp +++ b/dbms/src/Interpreters/InterpreterFactory.cpp @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -47,7 +46,6 @@ #include #include #include -#include #include #include @@ -159,11 +157,6 @@ std::unique_ptr InterpreterFactory::get(ASTPtr & query, Context & throwIfReadOnly(context); return std::make_unique(query, context); } - else if (typeid_cast(query.get())) - { - throwIfReadOnly(context); - return std::make_unique(query, context); - } else if (typeid_cast(query.get())) { throwIfReadOnly(context); diff --git a/dbms/src/Interpreters/InterpreterTruncateQuery.cpp b/dbms/src/Interpreters/InterpreterTruncateQuery.cpp deleted file mode 100644 index 367db131272..00000000000 --- a/dbms/src/Interpreters/InterpreterTruncateQuery.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include - - -namespace DB -{ -BlockIO InterpreterTruncateQuery::execute() -{ - auto & truncate = typeid_cast(*query_ptr); - - const String & table_name = truncate.table; - String database_name = truncate.database.empty() ? context.getCurrentDatabase() : truncate.database; - StoragePtr table = context.getTable(database_name, table_name); - table->truncate(query_ptr, context); - return {}; -} - -} // namespace DB diff --git a/dbms/src/Interpreters/InterpreterTruncateQuery.h b/dbms/src/Interpreters/InterpreterTruncateQuery.h deleted file mode 100644 index fc9a5469cb3..00000000000 --- a/dbms/src/Interpreters/InterpreterTruncateQuery.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - - -namespace DB -{ -class Context; -class IAST; -using ASTPtr = std::shared_ptr; - - -class InterpreterTruncateQuery : public IInterpreter -{ -public: - InterpreterTruncateQuery(const ASTPtr & query_ptr_, Context & context_) - : query_ptr(query_ptr_) - , context(context_) - {} - - BlockIO execute() override; - -private: - ASTPtr query_ptr; - Context & context; -}; - - -} // namespace DB diff --git a/dbms/src/Parsers/ASTTruncateQuery.h b/dbms/src/Parsers/ASTTruncateQuery.h deleted file mode 100644 index 49787067a77..00000000000 --- a/dbms/src/Parsers/ASTTruncateQuery.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - -namespace DB -{ - -class ASTTruncateQuery : public IAST -{ -public: - - String database; - String table; - - /** Get the text that identifies this element. */ - String getID() const override { return "Truncate_" + database + "_" + table; }; - - ASTPtr clone() const override { return std::make_shared(*this); } - -protected: - void formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const override - { - settings.ostr << (settings.hilite ? hilite_keyword : "") << "TRUNCATE TABLE " << (settings.hilite ? hilite_none : ""); - if (!database.empty()) - { - settings.ostr << backQuoteIfNeed(database); - settings.ostr << "."; - } - settings.ostr << backQuoteIfNeed(table); - } -}; - -} diff --git a/dbms/src/Parsers/ParserQuery.cpp b/dbms/src/Parsers/ParserQuery.cpp index 4e20221df82..7325a3850b3 100644 --- a/dbms/src/Parsers/ParserQuery.cpp +++ b/dbms/src/Parsers/ParserQuery.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include namespace DB @@ -38,7 +37,6 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) ParserDeleteQuery delete_p; ParserDBGInvokeQuery dbginvoke_p; ParserSystemQuery system_p; - ParserTruncateQuery truncate_p; ParserManageQuery manage_p; bool res = query_with_output_p.parse(pos, node, expected) @@ -48,7 +46,6 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) || delete_p.parse(pos, node, expected) || dbginvoke_p.parse(pos, node, expected) || system_p.parse(pos, node, expected) - || truncate_p.parse(pos, node, expected) || manage_p.parse(pos, node, expected); return res; diff --git a/dbms/src/Parsers/ParserTruncateQuery.cpp b/dbms/src/Parsers/ParserTruncateQuery.cpp deleted file mode 100644 index dac18471023..00000000000 --- a/dbms/src/Parsers/ParserTruncateQuery.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include - -#include -#include -#include -#include -#include - - -namespace DB -{ - -bool ParserTruncateQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) -{ - ParserKeyword s_truncate("TRUNCATE"); - ParserKeyword s_table("TABLE"); - - ParserToken s_dot(TokenType::Dot); - ParserIdentifier table_parser; - - ASTPtr table; - ASTPtr database; - - auto query = std::make_shared(); - - if (!s_truncate.ignore(pos, expected)) - return false; - - if (!s_table.ignore(pos, expected)) - return false; - - if (!table_parser.parse(pos, database, expected)) - return false; - - /// Parse [db].name - if (s_dot.ignore(pos)) - { - if (!table_parser.parse(pos, table, expected)) - return false; - - query->table = typeid_cast(*table).name; - query->database = typeid_cast(*database).name; - } - else - { - table = database; - query->table = typeid_cast(*table).name; - } - - node = query; - - return true; -} - -} diff --git a/dbms/src/Parsers/ParserTruncateQuery.h b/dbms/src/Parsers/ParserTruncateQuery.h deleted file mode 100644 index 196a5d67ce8..00000000000 --- a/dbms/src/Parsers/ParserTruncateQuery.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - - -namespace DB -{ - -/** Query USE db - */ -class ParserTruncateQuery : public IParserBase -{ -protected: - const char * getName() const { return "TRUNCATE query"; } - bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected); -}; - -} From c0459782af4499b98bef3e3edc523e103827e38b Mon Sep 17 00:00:00 2001 From: SeaRise Date: Wed, 11 May 2022 23:48:34 +0800 Subject: [PATCH 011/127] fix tispark query err: `for PushDownFilter, conditions and executor_id should both be empty or neither should be empty` (#4864) close pingcap/tiflash#4865 --- dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp | 2 +- dbms/src/Flash/Coprocessor/PushDownFilter.cpp | 8 ++++---- dbms/src/Flash/Coprocessor/PushDownFilter.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp index c11c5bd75a2..cef1fe1b596 100644 --- a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp +++ b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp @@ -172,7 +172,7 @@ void DAGQueryBlockInterpreter::handleMockTableScan(const TiDBTableScan & table_s void DAGQueryBlockInterpreter::handleTableScan(const TiDBTableScan & table_scan, DAGPipeline & pipeline) { - const auto push_down_filter = PushDownFilter::toPushDownFilter(query_block.selection); + const auto push_down_filter = PushDownFilter::toPushDownFilter(query_block.selection_name, query_block.selection); DAGStorageInterpreter storage_interpreter(context, table_scan, push_down_filter, max_streams); storage_interpreter.execute(pipeline); diff --git a/dbms/src/Flash/Coprocessor/PushDownFilter.cpp b/dbms/src/Flash/Coprocessor/PushDownFilter.cpp index 54638f899b3..bc08d9ac8e3 100644 --- a/dbms/src/Flash/Coprocessor/PushDownFilter.cpp +++ b/dbms/src/Flash/Coprocessor/PushDownFilter.cpp @@ -49,17 +49,17 @@ tipb::Executor * PushDownFilter::constructSelectionForRemoteRead(tipb::Executor } } -PushDownFilter PushDownFilter::toPushDownFilter(const tipb::Executor * filter_executor) +PushDownFilter PushDownFilter::toPushDownFilter(const String & executor_id, const tipb::Executor * executor) { - if (!filter_executor || !filter_executor->has_selection()) + if (!executor || !executor->has_selection()) { return {"", {}}; } std::vector conditions; - for (const auto & condition : filter_executor->selection().conditions()) + for (const auto & condition : executor->selection().conditions()) conditions.push_back(&condition); - return {filter_executor->executor_id(), conditions}; + return {executor_id, conditions}; } } // namespace DB \ No newline at end of file diff --git a/dbms/src/Flash/Coprocessor/PushDownFilter.h b/dbms/src/Flash/Coprocessor/PushDownFilter.h index 0c461ef42e3..9645264a71c 100644 --- a/dbms/src/Flash/Coprocessor/PushDownFilter.h +++ b/dbms/src/Flash/Coprocessor/PushDownFilter.h @@ -23,7 +23,7 @@ namespace DB { struct PushDownFilter { - static PushDownFilter toPushDownFilter(const tipb::Executor * filter_executor); + static PushDownFilter toPushDownFilter(const String & executor_id, const tipb::Executor * executor); PushDownFilter( const String & executor_id_, From 506df23d702d3c996866f6483737e4c3cd7a9e7e Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Thu, 12 May 2022 10:36:34 +0800 Subject: [PATCH 012/127] PageStorage: Add mix mode (#4726) ref pingcap/tiflash#3594 --- dbms/src/Debug/dbgFuncMisc.cpp | 4 +- dbms/src/Interpreters/Context.cpp | 106 +++- dbms/src/Interpreters/Context.h | 7 +- dbms/src/Server/DTTool/DTToolBench.cpp | 2 +- dbms/src/Server/Server.cpp | 11 +- dbms/src/Server/StorageConfigParser.cpp | 4 +- dbms/src/Server/StorageConfigParser.h | 1 - dbms/src/Server/tests/gtest_dttool.cpp | 2 +- dbms/src/Server/tests/gtest_server_config.cpp | 11 +- .../DeltaMerge/ColumnFile/ColumnFileBig.cpp | 2 +- .../Delta/ColumnFilePersistedSet.cpp | 2 +- .../DeltaMerge/Delta/DeltaValueSpace.cpp | 4 +- .../Storages/DeltaMerge/DeltaMergeStore.cpp | 41 +- .../src/Storages/DeltaMerge/DeltaMergeStore.h | 4 +- dbms/src/Storages/DeltaMerge/Segment.cpp | 2 +- .../Storages/DeltaMerge/StableValueSpace.cpp | 4 +- dbms/src/Storages/DeltaMerge/StoragePool.cpp | 449 +++++++++---- dbms/src/Storages/DeltaMerge/StoragePool.h | 154 +++-- dbms/src/Storages/DeltaMerge/WriteBatches.h | 16 +- .../tests/gtest_dm_delta_merge_store.cpp | 4 +- .../tests/gtest_dm_delta_value_space.cpp | 2 +- .../DeltaMerge/tests/gtest_dm_file.cpp | 4 +- .../DeltaMerge/tests/gtest_dm_segment.cpp | 4 +- .../tests/gtest_dm_segment_common_handle.cpp | 2 +- dbms/src/Storages/FormatVersion.h | 19 +- dbms/src/Storages/Page/Page.h | 14 + dbms/src/Storages/Page/PageStorage.cpp | 592 ++++++++++++++++++ dbms/src/Storages/Page/PageStorage.h | 127 ++-- dbms/src/Storages/Page/Snapshot.h | 34 + dbms/src/Storages/Page/V2/PageFile.cpp | 2 +- dbms/src/Storages/Page/V2/PageStorage.cpp | 13 + dbms/src/Storages/Page/V2/PageStorage.h | 8 +- dbms/src/Storages/Page/V3/BlobStore.cpp | 22 + dbms/src/Storages/Page/V3/PageDirectory.cpp | 4 +- dbms/src/Storages/Page/V3/PageEntry.h | 9 +- dbms/src/Storages/Page/V3/PageStorageImpl.cpp | 22 +- dbms/src/Storages/Page/V3/PageStorageImpl.h | 2 + .../Page/V3/tests/gtest_blob_store.cpp | 1 + .../Page/V3/tests/gtest_page_storage.cpp | 2 +- .../V3/tests/gtest_page_storage_mix_mode.cpp | 494 +++++++++++++++ dbms/src/Storages/Page/WriteBatch.h | 13 + dbms/src/Storages/PathPool.cpp | 10 +- dbms/src/Storages/PathPool.h | 5 +- .../Storages/Transaction/RegionPersister.cpp | 121 +++- .../Storages/Transaction/RegionPersister.h | 10 +- .../tests/gtest_region_persister.cpp | 12 +- dbms/src/TestUtils/TiFlashTestEnv.cpp | 9 +- 47 files changed, 2023 insertions(+), 364 deletions(-) create mode 100644 dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp diff --git a/dbms/src/Debug/dbgFuncMisc.cpp b/dbms/src/Debug/dbgFuncMisc.cpp index b9f62317189..e43ca5dd725 100644 --- a/dbms/src/Debug/dbgFuncMisc.cpp +++ b/dbms/src/Debug/dbgFuncMisc.cpp @@ -104,9 +104,7 @@ void dbgFuncTriggerGlobalPageStorageGC(Context & context, const ASTs & /*args*/, auto global_storage_pool = context.getGlobalStoragePool(); if (global_storage_pool) { - global_storage_pool->meta()->gc(); - global_storage_pool->log()->gc(); - global_storage_pool->data()->gc(); + global_storage_pool->gc(); } } } // namespace DB diff --git a/dbms/src/Interpreters/Context.cpp b/dbms/src/Interpreters/Context.cpp index ce4ecc692c0..25dffe263fd 100644 --- a/dbms/src/Interpreters/Context.cpp +++ b/dbms/src/Interpreters/Context.cpp @@ -161,6 +161,7 @@ struct ContextShared PathCapacityMetricsPtr path_capacity_ptr; /// Path capacity metrics FileProviderPtr file_provider; /// File provider. IORateLimiter io_rate_limiter; + PageStorageRunMode storage_run_mode; DM::GlobalStoragePoolPtr global_storage_pool; /// Named sessions. The user could specify session identifier to reuse settings and temporary tables in subsequent requests. @@ -1547,24 +1548,38 @@ ReadLimiterPtr Context::getReadLimiter() const return getIORateLimiter().getReadLimiter(); } -static bool isUsingPageStorageV3(const PathPool & path_pool, bool enable_ps_v3) + +static bool isPageStorageV2Existed(const PathPool & path_pool) { - // Check whether v3 is already enabled - for (const auto & path : path_pool.listGlobalPagePaths()) + for (const auto & path : path_pool.listKVStorePaths()) { - if (PS::V3::PageStorageImpl::isManifestsFileExists(path)) + Poco::File dir(path); + if (!dir.exists()) + continue; + + std::vector files; + dir.list(files); + if (!files.empty()) { - return true; + for (const auto & file_name : files) + { + const auto & find_index = file_name.find("page"); + if (find_index != std::string::npos) + { + return true; + } + } + // KVStore is not empty, but can't find any of v2 data in it. } } - // Check whether v3 on new node is enabled in the config, if not, no need to check anymore - if (!enable_ps_v3) - return false; + // If not data in KVStore. It means V2 data must not existed. + return false; +} - // Check whether there are any files in kvstore path, if exists, then this is not a new node. - // If it's a new node, then we enable v3. Otherwise, we use v2. - for (const auto & path : path_pool.listKVStorePaths()) +static bool isPageStorageV3Existed(const PathPool & path_pool) +{ + for (const auto & path : path_pool.listGlobalPagePaths()) { Poco::File dir(path); if (!dir.exists()) @@ -1574,23 +1589,76 @@ static bool isUsingPageStorageV3(const PathPool & path_pool, bool enable_ps_v3) dir.list(files); if (!files.empty()) { - return false; + return true; } } - return true; + return false; } -bool Context::initializeGlobalStoragePoolIfNeed(const PathPool & path_pool, bool enable_ps_v3) +void Context::initializePageStorageMode(const PathPool & path_pool, UInt64 storage_page_format_version) { auto lock = getLock(); - if (isUsingPageStorageV3(path_pool, enable_ps_v3)) + + /** + * PageFormat::V2 + isPageStorageV3Existed is false + whatever isPageStorageV2Existed true or false = ONLY_V2 + * PageFormat::V2 + isPageStorageV3Existed is true + whatever isPageStorageV2Existed true or false = ERROR Config + * PageFormat::V3 + isPageStorageV2Existed is true + whatever isPageStorageV3Existed true or false = MIX_MODE + * PageFormat::V3 + isPageStorageV2Existed is false + whatever isPageStorageV3Existed true or false = ONLY_V3 + */ + + switch (storage_page_format_version) { - try + case PageFormat::V1: + case PageFormat::V2: + { + if (isPageStorageV3Existed(path_pool)) { - // create manifests file before initialize GlobalStoragePool - for (const auto & path : path_pool.listGlobalPagePaths()) - PS::V3::PageStorageImpl::createManifestsFileIfNeed(path); + throw Exception("Invalid config `storage.format_version`, Current page V3 data exist. But using the PageFormat::V2." + "If you are downgrading the format_version for this TiFlash node, you need to rebuild the data from scratch.", + ErrorCodes::LOGICAL_ERROR); + } + // not exist V3 + shared->storage_run_mode = PageStorageRunMode::ONLY_V2; + return; + } + case PageFormat::V3: + { + shared->storage_run_mode = isPageStorageV2Existed(path_pool) ? PageStorageRunMode::MIX_MODE : PageStorageRunMode::ONLY_V3; + return; + } + default: + throw Exception(fmt::format("Can't detect the format version of Page [page_version={}]", storage_page_format_version), + ErrorCodes::LOGICAL_ERROR); + } +} + +PageStorageRunMode Context::getPageStorageRunMode() const +{ + auto lock = getLock(); + return shared->storage_run_mode; +} +void Context::setPageStorageRunMode(PageStorageRunMode run_mode) const +{ + auto lock = getLock(); + shared->storage_run_mode = run_mode; +} + +bool Context::initializeGlobalStoragePoolIfNeed(const PathPool & path_pool) +{ + auto lock = getLock(); + if (shared->global_storage_pool) + { + // Can't init GlobalStoragePool twice. + // Because we won't remove the gc task in BackGroundPool + // Also won't remove it from ~GlobalStoragePool() + throw Exception("GlobalStoragePool has already been initialized.", ErrorCodes::LOGICAL_ERROR); + } + + if (shared->storage_run_mode == PageStorageRunMode::MIX_MODE || shared->storage_run_mode == PageStorageRunMode::ONLY_V3) + { + try + { shared->global_storage_pool = std::make_shared(path_pool, *this, settings); shared->global_storage_pool->restore(); return true; diff --git a/dbms/src/Interpreters/Context.h b/dbms/src/Interpreters/Context.h index 9e8a1b248ba..63aefcbece9 100644 --- a/dbms/src/Interpreters/Context.h +++ b/dbms/src/Interpreters/Context.h @@ -99,6 +99,7 @@ using WriteLimiterPtr = std::shared_ptr; class ReadLimiter; using ReadLimiterPtr = std::shared_ptr; +enum class PageStorageRunMode : UInt8; namespace DM { class MinMaxIndexCache; @@ -405,8 +406,10 @@ class Context ReadLimiterPtr getReadLimiter() const; IORateLimiter & getIORateLimiter() const; - bool initializeGlobalStoragePoolIfNeed(const PathPool & path_pool, bool enable_ps_v3); - + void initializePageStorageMode(const PathPool & path_pool, UInt64 storage_page_format_version); + void setPageStorageRunMode(PageStorageRunMode run_mode) const; + PageStorageRunMode getPageStorageRunMode() const; + bool initializeGlobalStoragePoolIfNeed(const PathPool & path_pool); DM::GlobalStoragePoolPtr getGlobalStoragePool() const; /// Call after initialization before using system logs. Call for global context. diff --git a/dbms/src/Server/DTTool/DTToolBench.cpp b/dbms/src/Server/DTTool/DTToolBench.cpp index 491ac710721..e30f95e14b2 100644 --- a/dbms/src/Server/DTTool/DTToolBench.cpp +++ b/dbms/src/Server/DTTool/DTToolBench.cpp @@ -336,7 +336,7 @@ int benchEntry(const std::vector & opts) auto settings = DB::Settings(); auto db_context = env.getContext(); auto path_pool = std::make_unique(db_context->getPathPool().withTable("test", "t1", false)); - auto storage_pool = std::make_unique("test.t1", /*table_id*/ 1, *path_pool, *db_context, db_context->getSettingsRef()); + auto storage_pool = std::make_unique(*db_context, /*ns_id*/ 1, *path_pool, "test.t1"); auto dm_settings = DB::DM::DeltaMergeStore::Settings{}; auto dm_context = std::make_unique( // *db_context, diff --git a/dbms/src/Server/Server.cpp b/dbms/src/Server/Server.cpp index 4762935cd6f..705b8a533f3 100644 --- a/dbms/src/Server/Server.cpp +++ b/dbms/src/Server/Server.cpp @@ -1112,13 +1112,10 @@ int Server::main(const std::vector & /*args*/) raft_config.enable_compatible_mode, // global_context->getPathCapacity(), global_context->getFileProvider()); - // must initialize before the following operation: - // 1. load data from disk(because this process may depend on the initialization of global StoragePool) - // 2. initialize KVStore service - // 1) because we need to check whether this is the first startup of this node, and we judge it based on whether there are any files in kvstore directory - // 2) KVStore service also choose its data format based on whether the GlobalStoragePool is initialized - if (global_context->initializeGlobalStoragePoolIfNeed(global_context->getPathPool(), storage_config.enable_ps_v3)) - LOG_FMT_INFO(log, "PageStorage V3 enabled."); + + global_context->initializePageStorageMode(global_context->getPathPool(), STORAGE_FORMAT_CURRENT.page); + global_context->initializeGlobalStoragePoolIfNeed(global_context->getPathPool()); + LOG_FMT_INFO(log, "Global PageStorage run mode is {}", static_cast(global_context->getPageStorageRunMode())); // Use pd address to define which default_database we use by default. // For mock test, we use "default". For deployed with pd/tidb/tikv use "system", which is always exist in TiFlash. diff --git a/dbms/src/Server/StorageConfigParser.cpp b/dbms/src/Server/StorageConfigParser.cpp index 270390ac1fa..d43ccb850f1 100644 --- a/dbms/src/Server/StorageConfigParser.cpp +++ b/dbms/src/Server/StorageConfigParser.cpp @@ -213,10 +213,8 @@ void TiFlashStorageConfig::parseMisc(const String & storage_section, Poco::Logge }; lazily_init_store = get_bool_config_or_default("lazily_init_store", lazily_init_store); - // config for experimental feature, may remove later - enable_ps_v3 = get_bool_config_or_default("enable_ps_v3", enable_ps_v3); - LOG_FMT_INFO(log, "format_version {} lazily_init_store {} enable_ps_v3 {}", format_version, lazily_init_store, enable_ps_v3); + LOG_FMT_INFO(log, "format_version {} lazily_init_store {}", format_version, lazily_init_store); } Strings TiFlashStorageConfig::getAllNormalPaths() const diff --git a/dbms/src/Server/StorageConfigParser.h b/dbms/src/Server/StorageConfigParser.h index 65df7be3174..4efc5637634 100644 --- a/dbms/src/Server/StorageConfigParser.h +++ b/dbms/src/Server/StorageConfigParser.h @@ -103,7 +103,6 @@ struct TiFlashStorageConfig UInt64 format_version = 0; bool lazily_init_store = true; - bool enable_ps_v3 = true; public: TiFlashStorageConfig() = default; diff --git a/dbms/src/Server/tests/gtest_dttool.cpp b/dbms/src/Server/tests/gtest_dttool.cpp index 6b0d6e3d5c2..a03fc779ae3 100644 --- a/dbms/src/Server/tests/gtest_dttool.cpp +++ b/dbms/src/Server/tests/gtest_dttool.cpp @@ -72,7 +72,7 @@ struct DTToolTest : public DB::base::TiFlashStorageTestBasic properties.push_back(property); } auto path_pool = std::make_unique(db_context->getPathPool().withTable("test", "t1", false)); - auto storage_pool = std::make_unique("test.t1", /*table_id*/ 1, *path_pool, *db_context, db_context->getSettingsRef()); + auto storage_pool = std::make_unique(*db_context, /*ns_id*/ 1, *path_pool, "test.t1"); auto dm_settings = DB::DM::DeltaMergeStore::Settings{}; auto dm_context = std::make_unique( // *db_context, diff --git a/dbms/src/Server/tests/gtest_server_config.cpp b/dbms/src/Server/tests/gtest_server_config.cpp index 69e1fe4cb6a..53705f1a351 100644 --- a/dbms/src/Server/tests/gtest_server_config.cpp +++ b/dbms/src/Server/tests/gtest_server_config.cpp @@ -296,7 +296,7 @@ dt_page_gc_low_write_prob = 0.2 auto verify_persister_reload_config = [&global_ctx](RegionPersister & persister) { DB::Settings & settings = global_ctx.getSettingsRef(); - auto cfg = persister.page_storage->getSettings(); + auto cfg = persister.getPageStorageSettings(); EXPECT_NE(cfg.gc_min_files, settings.dt_storage_pool_data_gc_min_file_num); EXPECT_NE(cfg.gc_min_legacy_num, settings.dt_storage_pool_data_gc_min_legacy_num); EXPECT_NE(cfg.gc_min_bytes, settings.dt_storage_pool_data_gc_min_bytes); @@ -307,8 +307,7 @@ dt_page_gc_low_write_prob = 0.2 EXPECT_NE(cfg.prob_do_gc_when_write_is_low, settings.dt_page_gc_low_write_prob * 1000); persister.gc(); - cfg = persister.page_storage->getSettings(); - + cfg = persister.getPageStorageSettings(); EXPECT_NE(cfg.gc_min_files, settings.dt_storage_pool_data_gc_min_file_num); EXPECT_NE(cfg.gc_min_legacy_num, settings.dt_storage_pool_data_gc_min_legacy_num); EXPECT_NE(cfg.gc_min_bytes, settings.dt_storage_pool_data_gc_min_bytes); @@ -370,12 +369,12 @@ dt_page_gc_low_write_prob = 0.2 auto & global_ctx = TiFlashTestEnv::getGlobalContext(); std::unique_ptr path_pool = std::make_unique(global_ctx.getPathPool().withTable("test", "t1", false)); - std::unique_ptr storage_pool = std::make_unique("test.t1", /*table_id*/ 100, *path_pool, global_ctx, global_ctx.getSettingsRef()); + std::unique_ptr storage_pool = std::make_unique(global_ctx, /*ns_id*/ 100, *path_pool, "test.t1"); auto verify_storage_pool_reload_config = [&global_ctx](std::unique_ptr & storage_pool) { DB::Settings & settings = global_ctx.getSettingsRef(); - auto cfg = storage_pool->data()->getSettings(); + auto cfg = storage_pool->data_storage_v2->getSettings(); EXPECT_NE(cfg.gc_min_files, settings.dt_storage_pool_data_gc_min_file_num); EXPECT_NE(cfg.gc_min_legacy_num, settings.dt_storage_pool_data_gc_min_legacy_num); EXPECT_NE(cfg.gc_min_bytes, settings.dt_storage_pool_data_gc_min_bytes); @@ -387,7 +386,7 @@ dt_page_gc_low_write_prob = 0.2 storage_pool->gc(settings, DM::StoragePool::Seconds(0)); - cfg = storage_pool->data()->getSettings(); + cfg = storage_pool->data_storage_v2->getSettings(); EXPECT_EQ(cfg.gc_min_files, settings.dt_storage_pool_data_gc_min_file_num); EXPECT_EQ(cfg.gc_min_legacy_num, settings.dt_storage_pool_data_gc_min_legacy_num); EXPECT_EQ(cfg.gc_min_bytes, settings.dt_storage_pool_data_gc_min_bytes); diff --git a/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileBig.cpp b/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileBig.cpp index 9fe72839628..306f9470f5d 100644 --- a/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileBig.cpp +++ b/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileBig.cpp @@ -72,7 +72,7 @@ ColumnFilePersistedPtr ColumnFileBig::deserializeMetadata(DMContext & context, / readIntBinary(valid_rows, buf); readIntBinary(valid_bytes, buf); - auto file_id = context.storage_pool.dataReader().getNormalPageId(file_ref_id); + auto file_id = context.storage_pool.dataReader()->getNormalPageId(file_ref_id); auto file_parent_path = context.path_pool.getStableDiskDelegator().getDTFilePath(file_id); auto dmfile = DMFile::restore(context.db_context.getFileProvider(), file_id, file_ref_id, file_parent_path, DMFile::ReadMetaMode::all()); diff --git a/dbms/src/Storages/DeltaMerge/Delta/ColumnFilePersistedSet.cpp b/dbms/src/Storages/DeltaMerge/Delta/ColumnFilePersistedSet.cpp index b96acdad424..289caf5816c 100644 --- a/dbms/src/Storages/DeltaMerge/Delta/ColumnFilePersistedSet.cpp +++ b/dbms/src/Storages/DeltaMerge/Delta/ColumnFilePersistedSet.cpp @@ -107,7 +107,7 @@ ColumnFilePersistedSet::ColumnFilePersistedSet(PageId metadata_id_, const Column ColumnFilePersistedSetPtr ColumnFilePersistedSet::restore(DMContext & context, const RowKeyRange & segment_range, PageId id) { - Page page = context.storage_pool.metaReader().read(id); + Page page = context.storage_pool.metaReader()->read(id); ReadBufferFromMemory buf(page.data.begin(), page.data.size()); auto column_files = deserializeSavedColumnFiles(context, segment_range, buf); return std::make_shared(id, column_files); diff --git a/dbms/src/Storages/DeltaMerge/Delta/DeltaValueSpace.cpp b/dbms/src/Storages/DeltaMerge/Delta/DeltaValueSpace.cpp index b57ce3cb50a..132732d6989 100644 --- a/dbms/src/Storages/DeltaMerge/Delta/DeltaValueSpace.cpp +++ b/dbms/src/Storages/DeltaMerge/Delta/DeltaValueSpace.cpp @@ -236,12 +236,12 @@ bool DeltaValueSpace::compact(DMContext & context) LOG_FMT_DEBUG(log, "{} Nothing to compact", simpleInfo()); return true; } - log_storage_snap = context.storage_pool.log()->getSnapshot(/*tracing_id*/ fmt::format("minor_compact_{}", simpleInfo())); + log_storage_snap = context.storage_pool.logReader()->getSnapshot(/*tracing_id*/ fmt::format("minor_compact_{}", simpleInfo())); } // do compaction task WriteBatches wbs(context.storage_pool, context.getWriteLimiter()); - PageReader reader(context.storage_pool.getNamespaceId(), context.storage_pool.log(), std::move(log_storage_snap), context.getReadLimiter()); + const auto & reader = context.storage_pool.newLogReader(context.getReadLimiter(), log_storage_snap); compaction_task->prepare(context, wbs, reader); { diff --git a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp index b27c94305aa..6b4339938af 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp +++ b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp @@ -212,18 +212,15 @@ DeltaMergeStore::DeltaMergeStore(Context & db_context, , hash_salt(++DELTA_MERGE_STORE_HASH_SALT) , log(Logger::get("DeltaMergeStore", fmt::format("{}.{}", db_name, table_name))) { - LOG_FMT_INFO(log, "Restore DeltaMerge Store start [{}.{}]", db_name, table_name); - // for mock test, table_id_ should be DB::InvalidTableID NamespaceId ns_id = physical_table_id == DB::InvalidTableID ? TEST_NAMESPACE_ID : physical_table_id; - if (auto global_storage_pool = global_context.getGlobalStoragePool(); global_storage_pool) - { - storage_pool = std::make_shared(ns_id, *global_storage_pool, global_context); - } - else - { - storage_pool = std::make_shared(db_name_ + "." + table_name_, ns_id, path_pool, global_context, db_context.getSettingsRef()); - } + + LOG_FMT_INFO(log, "Restore DeltaMerge Store start [{}.{}] [table_id = {}]", db_name, table_name, physical_table_id); + + storage_pool = std::make_shared(global_context, + ns_id, + path_pool, + db_name_ + "." + table_name_); // Restore existing dm files and set capacity for path_pool. // Should be done before any background task setup. @@ -242,10 +239,10 @@ DeltaMergeStore::DeltaMergeStore(Context & db_context, store_columns = generateStoreColumns(original_table_columns, is_common_handle); auto dm_context = newDMContext(db_context, db_context.getSettingsRef()); - + PageStorageRunMode page_storage_run_mode; try { - storage_pool->restore(); // restore from disk + page_storage_run_mode = storage_pool->restore(); // restore from disk if (!storage_pool->maxMetaPageId()) { // Create the first segment. @@ -278,7 +275,7 @@ DeltaMergeStore::DeltaMergeStore(Context & db_context, setUpBackgroundTask(dm_context); - LOG_FMT_INFO(log, "Restore DeltaMerge Store end [{}.{}]", db_name, table_name); + LOG_FMT_INFO(log, "Restore DeltaMerge Store end [{}.{}], [ps_run_mode={}]", db_name, table_name, static_cast(page_storage_run_mode)); } DeltaMergeStore::~DeltaMergeStore() @@ -332,7 +329,7 @@ void DeltaMergeStore::setUpBackgroundTask(const DMContextPtr & dm_context) }; callbacks.ns_id = storage_pool->getNamespaceId(); // remember to unregister it when shutdown - storage_pool->data()->registerExternalPagesCallbacks(callbacks); + storage_pool->dataRegisterExternalPagesCallbacks(callbacks); storage_pool->enableGC(); background_task_handle = background_pool.addTask([this] { return handleBackgroundTask(false); }); @@ -480,7 +477,7 @@ void DeltaMergeStore::shutdown() // shutdown before unregister to avoid conflict between this thread and background gc thread on the `ExternalPagesCallbacks` // because PageStorage V2 doesn't have any lock protection on the `ExternalPagesCallbacks`.(The order doesn't matter for V3) storage_pool->shutdown(); - storage_pool->data()->unregisterExternalPagesCallbacks(storage_pool->getNamespaceId()); + storage_pool->dataUnregisterExternalPagesCallbacks(storage_pool->getNamespaceId()); background_pool.removeTask(background_task_handle); blockable_background_pool.removeTask(blockable_background_pool_handle); @@ -716,7 +713,7 @@ void DeltaMergeStore::preIngestFile(const String & parent_path, const PageId fil void DeltaMergeStore::ingestFiles( const DMContextPtr & dm_context, const RowKeyRange & range, - const std::vector & file_ids, + const PageIds & file_ids, bool clear_data_in_range) { if (unlikely(shutdown_called.load(std::memory_order_relaxed))) @@ -2386,12 +2383,12 @@ DeltaMergeStoreStat DeltaMergeStore::getStat() static const String useless_tracing_id("DeltaMergeStore::getStat"); { - auto snaps_stat = storage_pool->data()->getSnapshotsStat(); + auto snaps_stat = storage_pool->dataReader()->getSnapshotsStat(); stat.storage_stable_num_snapshots = snaps_stat.num_snapshots; stat.storage_stable_oldest_snapshot_lifetime = snaps_stat.longest_living_seconds; stat.storage_stable_oldest_snapshot_thread_id = snaps_stat.longest_living_from_thread_id; stat.storage_stable_oldest_snapshot_tracing_id = snaps_stat.longest_living_from_tracing_id; - PageStorage::SnapshotPtr stable_snapshot = storage_pool->data()->getSnapshot(useless_tracing_id); + PageStorage::SnapshotPtr stable_snapshot = storage_pool->dataReader()->getSnapshot(useless_tracing_id); const auto * concrete_snap = toConcreteSnapshot(stable_snapshot); if (concrete_snap) { @@ -2408,12 +2405,12 @@ DeltaMergeStoreStat DeltaMergeStore::getStat() } } { - auto snaps_stat = storage_pool->log()->getSnapshotsStat(); + auto snaps_stat = storage_pool->logReader()->getSnapshotsStat(); stat.storage_delta_num_snapshots = snaps_stat.num_snapshots; stat.storage_delta_oldest_snapshot_lifetime = snaps_stat.longest_living_seconds; stat.storage_delta_oldest_snapshot_thread_id = snaps_stat.longest_living_from_thread_id; stat.storage_delta_oldest_snapshot_tracing_id = snaps_stat.longest_living_from_tracing_id; - PageStorage::SnapshotPtr log_snapshot = storage_pool->log()->getSnapshot(useless_tracing_id); + PageStorage::SnapshotPtr log_snapshot = storage_pool->logReader()->getSnapshot(useless_tracing_id); const auto * concrete_snap = toConcreteSnapshot(log_snapshot); if (concrete_snap) { @@ -2430,12 +2427,12 @@ DeltaMergeStoreStat DeltaMergeStore::getStat() } } { - auto snaps_stat = storage_pool->meta()->getSnapshotsStat(); + auto snaps_stat = storage_pool->metaReader()->getSnapshotsStat(); stat.storage_meta_num_snapshots = snaps_stat.num_snapshots; stat.storage_meta_oldest_snapshot_lifetime = snaps_stat.longest_living_seconds; stat.storage_meta_oldest_snapshot_thread_id = snaps_stat.longest_living_from_thread_id; stat.storage_meta_oldest_snapshot_tracing_id = snaps_stat.longest_living_from_tracing_id; - PageStorage::SnapshotPtr meta_snapshot = storage_pool->meta()->getSnapshot(useless_tracing_id); + PageStorage::SnapshotPtr meta_snapshot = storage_pool->metaReader()->getSnapshot(useless_tracing_id); const auto * concrete_snap = toConcreteSnapshot(meta_snapshot); if (concrete_snap) { diff --git a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h index 9b09b6f37c5..c600ac9dbf8 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h +++ b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h @@ -330,13 +330,13 @@ class DeltaMergeStore : private boost::noncopyable void ingestFiles(const DMContextPtr & dm_context, // const RowKeyRange & range, - const std::vector & file_ids, + const PageIds & file_ids, bool clear_data_in_range); void ingestFiles(const Context & db_context, // const DB::Settings & db_settings, const RowKeyRange & range, - const std::vector & file_ids, + const PageIds & file_ids, bool clear_data_in_range) { auto dm_context = newDMContext(db_context, db_settings); diff --git a/dbms/src/Storages/DeltaMerge/Segment.cpp b/dbms/src/Storages/DeltaMerge/Segment.cpp index eccc06a8257..e8cde4ba0c6 100644 --- a/dbms/src/Storages/DeltaMerge/Segment.cpp +++ b/dbms/src/Storages/DeltaMerge/Segment.cpp @@ -236,7 +236,7 @@ SegmentPtr Segment::newSegment( SegmentPtr Segment::restoreSegment(DMContext & context, PageId segment_id) { - Page page = context.storage_pool.metaReader().read(segment_id); // not limit restore + Page page = context.storage_pool.metaReader()->read(segment_id); // not limit restore ReadBufferFromMemory buf(page.data.begin(), page.data.size()); SegmentFormat::Version version; diff --git a/dbms/src/Storages/DeltaMerge/StableValueSpace.cpp b/dbms/src/Storages/DeltaMerge/StableValueSpace.cpp index 648ffd4f084..752a112b565 100644 --- a/dbms/src/Storages/DeltaMerge/StableValueSpace.cpp +++ b/dbms/src/Storages/DeltaMerge/StableValueSpace.cpp @@ -90,7 +90,7 @@ StableValueSpacePtr StableValueSpace::restore(DMContext & context, PageId id) { auto stable = std::make_shared(id); - Page page = context.storage_pool.metaReader().read(id); // not limit restore + Page page = context.storage_pool.metaReader()->read(id); // not limit restore ReadBufferFromMemory buf(page.data.begin(), page.data.size()); UInt64 version, valid_rows, valid_bytes, size; readIntBinary(version, buf); @@ -105,7 +105,7 @@ StableValueSpacePtr StableValueSpace::restore(DMContext & context, PageId id) { readIntBinary(ref_id, buf); - auto file_id = context.storage_pool.dataReader().getNormalPageId(ref_id); + auto file_id = context.storage_pool.dataReader()->getNormalPageId(ref_id); auto file_parent_path = context.path_pool.getStableDiskDelegator().getDTFilePath(file_id); auto dmfile = DMFile::restore(context.db_context.getFileProvider(), file_id, ref_id, file_parent_path, DMFile::ReadMetaMode::all()); diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.cpp b/dbms/src/Storages/DeltaMerge/StoragePool.cpp index 8c4468ae2ed..2979a75e192 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.cpp +++ b/dbms/src/Storages/DeltaMerge/StoragePool.cpp @@ -17,11 +17,15 @@ #include #include #include -#include #include namespace DB { +namespace ErrorCodes +{ +extern const int LOGICAL_ERROR; +} // namespace ErrorCodes + namespace FailPoints { extern const char force_set_dtfile_exist_when_acquire_id[]; @@ -60,34 +64,13 @@ PageStorage::Config extractConfig(const Settings & settings, StorageType subtype SET_CONFIG(meta); break; default: - throw Exception("Unknown subtype in extractConfig: " + DB::toString(static_cast(subtype))); + throw Exception(fmt::format("Unknown subtype in extractConfig: {} ", static_cast(subtype)), ErrorCodes::LOGICAL_ERROR); } #undef SET_CONFIG return config; } -template -static bool doStoragePoolGC(const Context & global_context, const Settings & settings, const T & storage_pool) -{ - bool done_anything = false; - auto write_limiter = global_context.getWriteLimiter(); - auto read_limiter = global_context.getReadLimiter(); - auto config = extractConfig(settings, StorageType::Meta); - storage_pool.meta()->reloadSettings(config); - done_anything |= storage_pool.meta()->gc(/*not_skip*/ false, write_limiter, read_limiter); - - config = extractConfig(settings, StorageType::Data); - storage_pool.data()->reloadSettings(config); - done_anything |= storage_pool.data()->gc(/*not_skip*/ false, write_limiter, read_limiter); - - config = extractConfig(settings, StorageType::Log); - storage_pool.log()->reloadSettings(config); - done_anything |= storage_pool.log()->gc(/*not_skip*/ false, write_limiter, read_limiter); - - return done_anything; -} - GlobalStoragePool::GlobalStoragePool(const PathPool & path_pool, Context & global_ctx, const Settings & settings) : // The iops and bandwidth in log_storage are relatively high, use multi-disks if possible log_storage(PageStorage::create("__global__.log", @@ -110,7 +93,18 @@ GlobalStoragePool::GlobalStoragePool(const PathPool & path_pool, Context & globa global_ctx.getFileProvider(), true)) , global_context(global_ctx) -{} +{ +} + + +GlobalStoragePool::~GlobalStoragePool() +{ + if (gc_handle) + { + global_context.getBackgroundPool().removeTask(gc_handle); + gc_handle = nullptr; + } +} void GlobalStoragePool::restore() { @@ -125,81 +119,135 @@ void GlobalStoragePool::restore() false); } -GlobalStoragePool::~GlobalStoragePool() +bool GlobalStoragePool::gc() { - if (gc_handle) - { - global_context.getBackgroundPool().removeTask(gc_handle); - gc_handle = nullptr; - } + return gc(Settings(), true, DELTA_MERGE_GC_PERIOD); } -bool GlobalStoragePool::gc(const Settings & settings, const Seconds & try_gc_period) +bool GlobalStoragePool::gc(const Settings & settings, bool immediately, const Seconds & try_gc_period) { + Timepoint now = Clock::now(); + if (!immediately) { - std::lock_guard lock(mutex); - - Timepoint now = Clock::now(); + // No need lock if (now < (last_try_gc_time.load() + try_gc_period)) return false; - - last_try_gc_time = now; } - return doStoragePoolGC(global_context, settings, *this); -} + last_try_gc_time = now; + bool done_anything = false; + auto write_limiter = global_context.getWriteLimiter(); + auto read_limiter = global_context.getReadLimiter(); + auto config = extractConfig(settings, StorageType::Meta); + meta_storage->reloadSettings(config); + done_anything |= meta_storage->gc(/*not_skip*/ false, write_limiter, read_limiter); -StoragePool::StoragePool(const String & name, NamespaceId ns_id_, StoragePathPool & path_pool, Context & global_ctx, const Settings & settings) - : owned_storage(true) - , ns_id(ns_id_) - , - // The iops and bandwidth in log_storage are relatively high, use multi-disks if possible - log_storage(PageStorage::create(name + ".log", - path_pool.getPSDiskDelegatorMulti("log"), - extractConfig(settings, StorageType::Log), - global_ctx.getFileProvider())) - , - // The iops in data_storage is low, only use the first disk for storing data - data_storage(PageStorage::create(name + ".data", - path_pool.getPSDiskDelegatorSingle("data"), - extractConfig(settings, StorageType::Data), - global_ctx.getFileProvider())) - , - // The iops in meta_storage is relatively high, use multi-disks if possible - meta_storage(PageStorage::create(name + ".meta", - path_pool.getPSDiskDelegatorMulti("meta"), - extractConfig(settings, StorageType::Meta), - global_ctx.getFileProvider())) - , log_storage_reader(ns_id, log_storage, nullptr) - , data_storage_reader(ns_id, data_storage, nullptr) - , meta_storage_reader(ns_id, meta_storage, nullptr) - , global_context(global_ctx) -{} + config = extractConfig(settings, StorageType::Data); + data_storage->reloadSettings(config); + done_anything |= data_storage->gc(/*not_skip*/ false, write_limiter, read_limiter); -StoragePool::StoragePool(NamespaceId ns_id_, const GlobalStoragePool & global_storage_pool, Context & global_ctx) - : owned_storage(false) + config = extractConfig(settings, StorageType::Log); + log_storage->reloadSettings(config); + done_anything |= log_storage->gc(/*not_skip*/ false, write_limiter, read_limiter); + + return done_anything; +} + +StoragePool::StoragePool(Context & global_ctx, NamespaceId ns_id_, StoragePathPool & storage_path_pool, const String & name) + : logger(Logger::get("StoragePool", !name.empty() ? name : DB::toString(ns_id_))) + , run_mode(global_ctx.getPageStorageRunMode()) , ns_id(ns_id_) - , log_storage(global_storage_pool.log()) - , data_storage(global_storage_pool.data()) - , meta_storage(global_storage_pool.meta()) - , log_storage_reader(ns_id, log_storage, nullptr) - , data_storage_reader(ns_id, data_storage, nullptr) - , meta_storage_reader(ns_id, meta_storage, nullptr) , global_context(global_ctx) - , v3_log_max_ids(global_storage_pool.getLogMaxIds()) - , v3_data_max_ids(global_storage_pool.getDataMaxIds()) - , v3_meta_max_ids(global_storage_pool.getMetaMaxIds()) -{} +{ + const auto & global_storage_pool = global_context.getGlobalStoragePool(); + switch (run_mode) + { + case PageStorageRunMode::ONLY_V2: + { + log_storage_v2 = PageStorage::create(name + ".log", + storage_path_pool.getPSDiskDelegatorMulti("log"), + extractConfig(global_context.getSettingsRef(), StorageType::Log), + global_context.getFileProvider()); + data_storage_v2 = PageStorage::create(name + ".data", + storage_path_pool.getPSDiskDelegatorSingle("data"), + extractConfig(global_context.getSettingsRef(), StorageType::Data), + global_ctx.getFileProvider()); + meta_storage_v2 = PageStorage::create(name + ".meta", + storage_path_pool.getPSDiskDelegatorMulti("meta"), + extractConfig(global_context.getSettingsRef(), StorageType::Meta), + global_ctx.getFileProvider()); + log_storage_reader = std::make_shared(run_mode, ns_id, log_storage_v2, /*storage_v3_*/ nullptr, nullptr); + data_storage_reader = std::make_shared(run_mode, ns_id, data_storage_v2, /*storage_v3_*/ nullptr, nullptr); + meta_storage_reader = std::make_shared(run_mode, ns_id, meta_storage_v2, /*storage_v3_*/ nullptr, nullptr); + + log_storage_writer = std::make_shared(run_mode, log_storage_v2, /*storage_v3_*/ nullptr); + data_storage_writer = std::make_shared(run_mode, data_storage_v2, /*storage_v3_*/ nullptr); + meta_storage_writer = std::make_shared(run_mode, meta_storage_v2, /*storage_v3_*/ nullptr); + break; + } + case PageStorageRunMode::ONLY_V3: + { + assert(global_storage_pool != nullptr); + log_storage_v3 = global_storage_pool->log_storage; + data_storage_v3 = global_storage_pool->data_storage; + meta_storage_v3 = global_storage_pool->meta_storage; + + log_storage_reader = std::make_shared(run_mode, ns_id, /*storage_v2_*/ nullptr, log_storage_v3, nullptr); + data_storage_reader = std::make_shared(run_mode, ns_id, /*storage_v2_*/ nullptr, data_storage_v3, nullptr); + meta_storage_reader = std::make_shared(run_mode, ns_id, /*storage_v2_*/ nullptr, meta_storage_v3, nullptr); + + log_storage_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, log_storage_v3); + data_storage_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, data_storage_v3); + meta_storage_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, meta_storage_v3); -void StoragePool::restore() + break; + } + case PageStorageRunMode::MIX_MODE: + { + assert(global_storage_pool != nullptr); + log_storage_v3 = global_storage_pool->log_storage; + data_storage_v3 = global_storage_pool->data_storage; + meta_storage_v3 = global_storage_pool->meta_storage; + + log_storage_v2 = PageStorage::create(name + ".log", + storage_path_pool.getPSDiskDelegatorMulti("log"), + extractConfig(global_context.getSettingsRef(), StorageType::Log), + global_context.getFileProvider()); + data_storage_v2 = PageStorage::create(name + ".data", + storage_path_pool.getPSDiskDelegatorMulti("data"), + extractConfig(global_context.getSettingsRef(), StorageType::Data), + global_ctx.getFileProvider()); + meta_storage_v2 = PageStorage::create(name + ".meta", + storage_path_pool.getPSDiskDelegatorMulti("meta"), + extractConfig(global_context.getSettingsRef(), StorageType::Meta), + global_ctx.getFileProvider()); + + log_storage_reader = std::make_shared(run_mode, ns_id, log_storage_v2, log_storage_v3, nullptr); + data_storage_reader = std::make_shared(run_mode, ns_id, data_storage_v2, data_storage_v3, nullptr); + meta_storage_reader = std::make_shared(run_mode, ns_id, meta_storage_v2, meta_storage_v3, nullptr); + + log_storage_writer = std::make_shared(run_mode, log_storage_v2, log_storage_v3); + data_storage_writer = std::make_shared(run_mode, data_storage_v2, data_storage_v3); + meta_storage_writer = std::make_shared(run_mode, meta_storage_v2, meta_storage_v3); + break; + } + default: + throw Exception(fmt::format("Unknown PageStorageRunMode {}", static_cast(run_mode)), ErrorCodes::LOGICAL_ERROR); + } +} + +PageStorageRunMode StoragePool::restore() { - // If the storage instances is not global, we need to initialize it by ourselves and add a gc task. - if (owned_storage) + const auto & global_storage_pool = global_context.getGlobalStoragePool(); + + switch (run_mode) + { + case PageStorageRunMode::ONLY_V2: { - auto log_max_ids = log_storage->restore(); - auto data_max_ids = data_storage->restore(); - auto meta_max_ids = meta_storage->restore(); + auto log_max_ids = log_storage_v2->restore(); + auto data_max_ids = data_storage_v2->restore(); + auto meta_max_ids = meta_storage_v2->restore(); assert(log_max_ids.size() == 1); assert(data_max_ids.size() == 1); @@ -208,37 +256,74 @@ void StoragePool::restore() max_log_page_id = log_max_ids[0]; max_data_page_id = data_max_ids[0]; max_meta_page_id = meta_max_ids[0]; + + break; } - else + case PageStorageRunMode::ONLY_V3: { - if (const auto & it = v3_log_max_ids.find(ns_id); it == v3_log_max_ids.end()) - { - max_log_page_id = 0; - } - else - { - max_log_page_id = it->second; - } + max_log_page_id = global_storage_pool->getLogMaxId(ns_id); + max_data_page_id = global_storage_pool->getDataMaxId(ns_id); + max_meta_page_id = global_storage_pool->getMetaMaxId(ns_id); - if (const auto & it = v3_data_max_ids.find(ns_id); it == v3_data_max_ids.end()) - { - max_data_page_id = 0; - } - else - { - max_data_page_id = it->second; - } + break; + } + case PageStorageRunMode::MIX_MODE: + { + auto v2_log_max_ids = log_storage_v2->restore(); + auto v2_data_max_ids = data_storage_v2->restore(); + auto v2_meta_max_ids = meta_storage_v2->restore(); - if (const auto & it = v3_meta_max_ids.find(ns_id); it == v3_meta_max_ids.end()) + assert(v2_log_max_ids.size() == 1); + assert(v2_data_max_ids.size() == 1); + assert(v2_meta_max_ids.size() == 1); + + // Check number of valid pages in v2 + // If V2 already have no any data in disk, Then change run_mode to ONLY_V3 + if (log_storage_v2->getNumberOfPages() == 0 && data_storage_v2->getNumberOfPages() == 0 && meta_storage_v2->getNumberOfPages() == 0) { - max_meta_page_id = 0; + // TODO: when `compaction` happend + // 1. Will rewrite meta into V3 by DT. + // 2. Need `DEL` meta in V2. + // 3. Need drop V2 in disk and check. + LOG_FMT_INFO(logger, "Current pagestorage change from {} to {}", static_cast(PageStorageRunMode::MIX_MODE), static_cast(PageStorageRunMode::ONLY_V3)); + + log_storage_v2 = nullptr; + data_storage_v2 = nullptr; + meta_storage_v2 = nullptr; + + log_storage_reader = std::make_shared(run_mode, ns_id, /*storage_v2_*/ nullptr, log_storage_v3, nullptr); + data_storage_reader = std::make_shared(run_mode, ns_id, /*storage_v2_*/ nullptr, data_storage_v3, nullptr); + meta_storage_reader = std::make_shared(run_mode, ns_id, /*storage_v2_*/ nullptr, meta_storage_v3, nullptr); + + log_storage_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, log_storage_v3); + data_storage_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, data_storage_v3); + meta_storage_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, meta_storage_v3); + + max_log_page_id = global_storage_pool->getLogMaxId(ns_id); + max_data_page_id = global_storage_pool->getDataMaxId(ns_id); + max_meta_page_id = global_storage_pool->getMetaMaxId(ns_id); + + run_mode = PageStorageRunMode::ONLY_V3; } - else + else // Still running Mix Mode { - max_meta_page_id = it->second; + max_log_page_id = std::max(v2_log_max_ids[0], global_storage_pool->getLogMaxId(ns_id)); + max_data_page_id = std::max(v2_data_max_ids[0], global_storage_pool->getDataMaxId(ns_id)); + max_meta_page_id = std::max(v2_meta_max_ids[0], global_storage_pool->getMetaMaxId(ns_id)); } + break; + } + default: + throw Exception(fmt::format("Unknown PageStorageRunMode {}", static_cast(run_mode)), ErrorCodes::LOGICAL_ERROR); } - // TODO: add a log to show max_*_page_id after mix mode pr ready. + LOG_FMT_TRACE(logger, "Finished StoragePool restore. [current_run_mode={}] [ns_id={}]" + " [max_log_page_id={}] [max_data_page_id={}] [max_meta_page_id={}]", + static_cast(run_mode), + ns_id, + max_log_page_id, + max_data_page_id, + max_meta_page_id); + return run_mode; } StoragePool::~StoragePool() @@ -248,13 +333,100 @@ StoragePool::~StoragePool() void StoragePool::enableGC() { - if (owned_storage) + // The data in V3 will be GCed by `GlobalStoragePool::gc`, only register gc task under only v2/mix mode + if (run_mode == PageStorageRunMode::ONLY_V2 || run_mode == PageStorageRunMode::MIX_MODE) + { gc_handle = global_context.getBackgroundPool().addTask([this] { return this->gc(global_context.getSettingsRef()); }); + } +} + +void StoragePool::dataRegisterExternalPagesCallbacks(const ExternalPageCallbacks & callbacks) +{ + switch (run_mode) + { + case PageStorageRunMode::ONLY_V2: + { + data_storage_v2->registerExternalPagesCallbacks(callbacks); + break; + } + case PageStorageRunMode::ONLY_V3: + { + data_storage_v3->registerExternalPagesCallbacks(callbacks); + break; + } + case PageStorageRunMode::MIX_MODE: + { + // When PageStorage run as Mix Mode. + // We need both get alive pages from V2 and V3 which will feedback for the DM. + // But V2 and V3 won't GC in the same time. So V3 need proxy V2 external pages callback. + // When V3 GC happend, scan the external pages from V3, in remover will scanner all of external pages from V2. + ExternalPageCallbacks mix_mode_callbacks; + + mix_mode_callbacks.scanner = callbacks.scanner; + mix_mode_callbacks.remover = [this, callbacks](const ExternalPageCallbacks::PathAndIdsVec & path_and_ids_vec, const std::set & valid_ids) { + // ns_id won't used on V2 + auto v2_valid_page_ids = data_storage_v2->getAliveExternalPageIds(ns_id); + v2_valid_page_ids.insert(valid_ids.begin(), valid_ids.end()); + callbacks.remover(path_and_ids_vec, v2_valid_page_ids); + }; + mix_mode_callbacks.ns_id = ns_id; + data_storage_v3->registerExternalPagesCallbacks(mix_mode_callbacks); + break; + } + default: + throw Exception(fmt::format("Unknown PageStorageRunMode {}", static_cast(run_mode)), ErrorCodes::LOGICAL_ERROR); + } +} + +void StoragePool::dataUnregisterExternalPagesCallbacks(NamespaceId ns_id) +{ + switch (run_mode) + { + case PageStorageRunMode::ONLY_V2: + { + data_storage_v2->unregisterExternalPagesCallbacks(ns_id); + break; + } + case PageStorageRunMode::ONLY_V3: + { + data_storage_v3->unregisterExternalPagesCallbacks(ns_id); + break; + } + case PageStorageRunMode::MIX_MODE: + { + // no need unregister callback in V2. + data_storage_v3->unregisterExternalPagesCallbacks(ns_id); + break; + } + default: + throw Exception(fmt::format("Unknown PageStorageRunMode {}", static_cast(run_mode)), ErrorCodes::LOGICAL_ERROR); + } +} + + +bool StoragePool::doV2Gc(const Settings & settings) +{ + bool done_anything = false; + auto write_limiter = global_context.getWriteLimiter(); + auto read_limiter = global_context.getReadLimiter(); + + auto config = extractConfig(settings, StorageType::Meta); + meta_storage_v2->reloadSettings(config); + done_anything |= meta_storage_v2->gc(/*not_skip*/ false, write_limiter, read_limiter); + + config = extractConfig(settings, StorageType::Data); + data_storage_v2->reloadSettings(config); + done_anything |= data_storage_v2->gc(/*not_skip*/ false, write_limiter, read_limiter); + + config = extractConfig(settings, StorageType::Log); + log_storage_v2->reloadSettings(config); + done_anything |= log_storage_v2->gc(/*not_skip*/ false, write_limiter, read_limiter); + return done_anything; } bool StoragePool::gc(const Settings & settings, const Seconds & try_gc_period) { - if (!owned_storage) + if (run_mode == PageStorageRunMode::ONLY_V3) return false; { @@ -268,7 +440,8 @@ bool StoragePool::gc(const Settings & settings, const Seconds & try_gc_period) last_try_gc_time = now; } - return doStoragePoolGC(global_context, settings, *this); + // Only do the v2 GC + return doV2Gc(settings); } void StoragePool::shutdown() @@ -284,11 +457,11 @@ void StoragePool::drop() { shutdown(); - if (owned_storage) + if (run_mode == PageStorageRunMode::ONLY_V2 || run_mode == PageStorageRunMode::MIX_MODE) { - meta_storage->drop(); - data_storage->drop(); - log_storage->drop(); + meta_storage_v2->drop(); + data_storage_v2->drop(); + log_storage_v2->drop(); } } @@ -316,13 +489,63 @@ PageId StoragePool::newDataPageIdForDTFile(StableDiskDelegator & delegator, cons break; } // else there is a DTFile with that id, continue to acquire a new ID. - LOG_FMT_WARNING(&Poco::Logger::get(who), - "The DTFile is already exists, continute to acquire another ID. [path={}] [id={}]", + LOG_FMT_WARNING(logger, + "The DTFile is already exists, continute to acquire another ID. [call={}][path={}] [id={}]", + who, existed_path, dtfile_id); } while (true); return dtfile_id; } +template +inline static PageReader newReader(const PageStorageRunMode run_mode, const NamespaceId ns_id, T & storage_v2, T & storage_v3, ReadLimiterPtr read_limiter, bool snapshot_read, const String & tracing_id) +{ + switch (run_mode) + { + case PageStorageRunMode::ONLY_V2: + return PageReader(run_mode, ns_id, storage_v2, nullptr, snapshot_read ? storage_v2->getSnapshot(tracing_id) : nullptr, read_limiter); + case PageStorageRunMode::ONLY_V3: + return PageReader(run_mode, ns_id, nullptr, storage_v3, snapshot_read ? storage_v3->getSnapshot(tracing_id) : nullptr, read_limiter); + case PageStorageRunMode::MIX_MODE: + return PageReader(run_mode, ns_id, storage_v2, storage_v3, snapshot_read ? std::make_shared(storage_v2->getSnapshot(fmt::format("{}-v2", tracing_id)), // + storage_v3->getSnapshot(fmt::format("{}-v3", tracing_id))) + : nullptr, + read_limiter); + default: + throw Exception(fmt::format("Unknown PageStorageRunMode {}", static_cast(run_mode)), ErrorCodes::LOGICAL_ERROR); + } +} + +PageReader StoragePool::newLogReader(ReadLimiterPtr read_limiter, bool snapshot_read, const String & tracing_id) +{ + return newReader(run_mode, ns_id, log_storage_v2, log_storage_v3, read_limiter, snapshot_read, tracing_id); +} + +PageReader StoragePool::newLogReader(ReadLimiterPtr read_limiter, PageStorage::SnapshotPtr & snapshot) +{ + return PageReader(run_mode, ns_id, log_storage_v2, log_storage_v3, snapshot, read_limiter); +} + +PageReader StoragePool::newDataReader(ReadLimiterPtr read_limiter, bool snapshot_read, const String & tracing_id) +{ + return newReader(run_mode, ns_id, data_storage_v2, data_storage_v3, read_limiter, snapshot_read, tracing_id); +} + +PageReader StoragePool::newDataReader(ReadLimiterPtr read_limiter, PageStorage::SnapshotPtr & snapshot) +{ + return PageReader(run_mode, ns_id, data_storage_v2, data_storage_v3, snapshot, read_limiter); +} + +PageReader StoragePool::newMetaReader(ReadLimiterPtr read_limiter, bool snapshot_read, const String & tracing_id) +{ + return newReader(run_mode, ns_id, meta_storage_v2, meta_storage_v3, read_limiter, snapshot_read, tracing_id); +} + +PageReader StoragePool::newMetaReader(ReadLimiterPtr read_limiter, PageStorage::SnapshotPtr & snapshot) +{ + return PageReader(run_mode, ns_id, meta_storage_v2, meta_storage_v3, snapshot, read_limiter); +} + } // namespace DM } // namespace DB diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.h b/dbms/src/Storages/DeltaMerge/StoragePool.h index 3a02232902b..5f439899a84 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.h +++ b/dbms/src/Storages/DeltaMerge/StoragePool.h @@ -14,8 +14,10 @@ #pragma once +#include #include #include +#include #include #include @@ -31,8 +33,6 @@ namespace DM { class StoragePool; using StoragePoolPtr = std::shared_ptr; -class GlobalStoragePool; -using GlobalStoragePoolPtr = std::shared_ptr; static const std::chrono::seconds DELTA_MERGE_GC_PERIOD(60); @@ -45,32 +45,51 @@ class GlobalStoragePool : private boost::noncopyable GlobalStoragePool(const PathPool & path_pool, Context & global_ctx, const Settings & settings); - void restore(); - ~GlobalStoragePool(); - PageStoragePtr log() const { return log_storage; } - PageStoragePtr data() const { return data_storage; } - PageStoragePtr meta() const { return meta_storage; } + void restore(); + + friend class StoragePool; - std::map getLogMaxIds() const + PageId getLogMaxId(NamespaceId ns_id) const { - return log_max_ids; + PageId max_log_page_id = 0; + if (const auto & it = log_max_ids.find(ns_id); it != log_max_ids.end()) + { + max_log_page_id = it->second; + } + + return max_log_page_id; } - std::map getDataMaxIds() const + PageId getDataMaxId(NamespaceId ns_id) const { - return data_max_ids; + PageId max_data_page_id = 0; + if (const auto & it = data_max_ids.find(ns_id); it != data_max_ids.end()) + { + max_data_page_id = it->second; + } + + return max_data_page_id; } - std::map getMetaMaxIds() const + PageId getMetaMaxId(NamespaceId ns_id) const { - return meta_max_ids; + PageId max_meta_page_id = 0; + if (const auto & it = meta_max_ids.find(ns_id); it != meta_max_ids.end()) + { + max_meta_page_id = it->second; + } + + return max_meta_page_id; } + // GC immediately + // Only used on dbgFuncMisc + bool gc(); + private: - // TODO: maybe more frequent gc for GlobalStoragePool? - bool gc(const Settings & settings, const Seconds & try_gc_period = DELTA_MERGE_GC_PERIOD); + bool gc(const Settings & settings, bool immediately = false, const Seconds & try_gc_period = DELTA_MERGE_GC_PERIOD); private: PageStoragePtr log_storage; @@ -83,11 +102,10 @@ class GlobalStoragePool : private boost::noncopyable std::atomic last_try_gc_time = Clock::now(); - std::mutex mutex; - Context & global_context; BackgroundProcessingPool::TaskHandle gc_handle; }; +using GlobalStoragePoolPtr = std::shared_ptr; class StoragePool : private boost::noncopyable { @@ -96,39 +114,71 @@ class StoragePool : private boost::noncopyable using Timepoint = Clock::time_point; using Seconds = std::chrono::seconds; - StoragePool(const String & name, NamespaceId ns_id_, StoragePathPool & path_pool, Context & global_ctx, const Settings & settings); + StoragePool(Context & global_ctx, NamespaceId ns_id_, StoragePathPool & path_pool, const String & name = ""); - StoragePool(NamespaceId ns_id_, const GlobalStoragePool & global_storage_pool, Context & global_ctx); - - void restore(); + PageStorageRunMode restore(); ~StoragePool(); NamespaceId getNamespaceId() const { return ns_id; } - PageStoragePtr log() const { return log_storage; } - PageStoragePtr data() const { return data_storage; } - PageStoragePtr meta() const { return meta_storage; } + PageStorageRunMode getPageStorageRunMode() + { + return run_mode; + } - PageReader & logReader() { return log_storage_reader; } - PageReader & dataReader() { return data_storage_reader; } - PageReader & metaReader() { return meta_storage_reader; } + PageReaderPtr & logReader() + { + assert(log_storage_reader); + return log_storage_reader; + } - PageReader newLogReader(ReadLimiterPtr read_limiter, bool snapshot_read, const String & tracing_id) + PageReaderPtr & dataReader() { - return PageReader(ns_id, log_storage, snapshot_read ? log_storage->getSnapshot(tracing_id) : nullptr, read_limiter); + assert(data_storage_reader); + return data_storage_reader; } - PageReader newDataReader(ReadLimiterPtr read_limiter, bool snapshot_read, const String & tracing_id) + + PageReaderPtr & metaReader() { - return PageReader(ns_id, data_storage, snapshot_read ? data_storage->getSnapshot(tracing_id) : nullptr, read_limiter); + assert(meta_storage_reader); + return meta_storage_reader; } - PageReader newMetaReader(ReadLimiterPtr read_limiter, bool snapshot_read, const String & tracing_id) + + PageWriterPtr & logWriter() { - return PageReader(ns_id, meta_storage, snapshot_read ? meta_storage->getSnapshot(tracing_id) : nullptr, read_limiter); + assert(log_storage_writer); + return log_storage_writer; } + PageWriterPtr & dataWriter() + { + assert(data_storage_writer); + return data_storage_writer; + } + + PageWriterPtr & metaWriter() + { + assert(meta_storage_writer); + return meta_storage_writer; + } + + + PageReader newLogReader(ReadLimiterPtr read_limiter, bool snapshot_read, const String & tracing_id); + PageReader newLogReader(ReadLimiterPtr read_limiter, PageStorage::SnapshotPtr & snapshot); + + PageReader newDataReader(ReadLimiterPtr read_limiter, bool snapshot_read, const String & tracing_id); + PageReader newDataReader(ReadLimiterPtr read_limiter, PageStorage::SnapshotPtr & snapshot); + + PageReader newMetaReader(ReadLimiterPtr read_limiter, bool snapshot_read, const String & tracing_id); + PageReader newMetaReader(ReadLimiterPtr read_limiter, PageStorage::SnapshotPtr & snapshot); + void enableGC(); + void dataRegisterExternalPagesCallbacks(const ExternalPageCallbacks & callbacks); + + void dataUnregisterExternalPagesCallbacks(NamespaceId ns_id); + bool gc(const Settings & settings, const Seconds & try_gc_period = DELTA_MERGE_GC_PERIOD); void shutdown(); @@ -139,22 +189,37 @@ class StoragePool : private boost::noncopyable PageId newDataPageIdForDTFile(StableDiskDelegator & delegator, const char * who); PageId maxMetaPageId() { return max_meta_page_id; } - PageId newLogPageId() { return ++max_log_page_id; } PageId newMetaPageId() { return ++max_meta_page_id; } - +#ifndef DBMS_PUBLIC_GTEST +private: +#endif + bool doV2Gc(const Settings & settings); +#ifndef DBMS_PUBLIC_GTEST private: +#endif + LoggerPtr logger; + + PageStorageRunMode run_mode; + // whether the three storage instance is owned by this StoragePool - const bool owned_storage = false; const NamespaceId ns_id; - const PageStoragePtr log_storage; - const PageStoragePtr data_storage; - const PageStoragePtr meta_storage; + PageStoragePtr log_storage_v2; + PageStoragePtr data_storage_v2; + PageStoragePtr meta_storage_v2; + + PageStoragePtr log_storage_v3; + PageStoragePtr data_storage_v3; + PageStoragePtr meta_storage_v3; - PageReader log_storage_reader; - PageReader data_storage_reader; - PageReader meta_storage_reader; + PageReaderPtr log_storage_reader; + PageReaderPtr data_storage_reader; + PageReaderPtr meta_storage_reader; + + PageWriterPtr log_storage_writer; + PageWriterPtr data_storage_writer; + PageWriterPtr meta_storage_writer; std::atomic last_try_gc_time = Clock::now(); @@ -162,11 +227,6 @@ class StoragePool : private boost::noncopyable Context & global_context; - // TBD: Will be replaced GlobalPathPoolPtr after mix mode ptr ready - std::map v3_log_max_ids; - std::map v3_data_max_ids; - std::map v3_meta_max_ids; - std::atomic max_log_page_id = 0; std::atomic max_data_page_id = 0; std::atomic max_meta_page_id = 0; diff --git a/dbms/src/Storages/DeltaMerge/WriteBatches.h b/dbms/src/Storages/DeltaMerge/WriteBatches.h index c64ff2cde38..2b508fed068 100644 --- a/dbms/src/Storages/DeltaMerge/WriteBatches.h +++ b/dbms/src/Storages/DeltaMerge/WriteBatches.h @@ -112,8 +112,8 @@ struct WriteBatches : private boost::noncopyable for (auto & w : data.getWrites()) data_write_pages.push_back(w.page_id); - storage_pool.log()->write(std::move(log), write_limiter); - storage_pool.data()->write(std::move(data), write_limiter); + storage_pool.logWriter()->write(std::move(log), write_limiter); + storage_pool.dataWriter()->write(std::move(data), write_limiter); for (auto page_id : log_write_pages) written_log.push_back(page_id); @@ -152,8 +152,8 @@ struct WriteBatches : private boost::noncopyable check(data_wb, "data_wb", logger); } - storage_pool.log()->write(std::move(log_wb), write_limiter); - storage_pool.data()->write(std::move(data_wb), write_limiter); + storage_pool.logWriter()->write(std::move(log_wb), write_limiter); + storage_pool.dataWriter()->write(std::move(data_wb), write_limiter); written_log.clear(); written_data.clear(); @@ -179,7 +179,7 @@ struct WriteBatches : private boost::noncopyable check(meta, "meta", logger); } - storage_pool.meta()->write(std::move(meta), write_limiter); + storage_pool.metaWriter()->write(std::move(meta), write_limiter); meta.clear(); } @@ -205,9 +205,9 @@ struct WriteBatches : private boost::noncopyable check(removed_meta, "removed_meta", logger); } - storage_pool.log()->write(std::move(removed_log), write_limiter); - storage_pool.data()->write(std::move(removed_data), write_limiter); - storage_pool.meta()->write(std::move(removed_meta), write_limiter); + storage_pool.logWriter()->write(std::move(removed_log), write_limiter); + storage_pool.dataWriter()->write(std::move(removed_data), write_limiter); + storage_pool.metaWriter()->write(std::move(removed_meta), write_limiter); removed_log.clear(); removed_data.clear(); diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp index bb0e47bddbd..b759d1fe9ef 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp @@ -178,7 +178,7 @@ class DeltaMergeStoreRWTest return s; } - std::pair> genDMFile(DMContext & context, const Block & block) + std::pair genDMFile(DMContext & context, const Block & block) { auto input_stream = std::make_shared(block); auto [store_path, file_id] = store->preAllocateIngestFile(); @@ -1344,7 +1344,7 @@ try { auto dm_context = store->newDMContext(*db_context, db_context->getSettingsRef()); - std::vector file_ids; + PageIds file_ids; auto ingest_range = RowKeyRange::fromHandleRange(HandleRange{32, 256}); store->ingestFiles(dm_context, ingest_range, file_ids, /*clear_data_in_range*/ true); } diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_value_space.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_value_space.cpp index 93b366f3b15..2b64fd90c09 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_value_space.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_value_space.cpp @@ -84,7 +84,7 @@ class DeltaValueSpaceTest : public DB::base::TiFlashStorageTestBasic { TiFlashStorageTestBasic::reload(std::move(db_settings)); storage_path_pool = std::make_unique(db_context->getPathPool().withTable("test", "t1", false)); - storage_pool = std::make_unique("test.t1", table_id, *storage_path_pool, *db_context, db_context->getSettingsRef()); + storage_pool = std::make_unique(*db_context, table_id, *storage_path_pool, "test.t1"); storage_pool->restore(); ColumnDefinesPtr cols = (!pre_define_columns) ? DMTestEnv::getDefaultColumns() : pre_define_columns; setColumns(cols); diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_file.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_file.cpp index e6fb6f236f3..3ddf318509f 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_file.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_file.cpp @@ -105,7 +105,7 @@ class DMFile_Test parent_path = TiFlashStorageTestBasic::getTemporaryPath(); path_pool = std::make_unique(db_context->getPathPool().withTable("test", "DMFile_Test", false)); - storage_pool = std::make_unique("test.t1", /*table_id*/ 100, *path_pool, *db_context, db_context->getSettingsRef()); + storage_pool = std::make_unique(*db_context, /*ns_id*/ 100, *path_pool, "test.t1"); dm_file = DMFile::create(1, parent_path, single_file_mode, std::move(configuration)); table_columns_ = std::make_shared(); column_cache_ = std::make_shared(); @@ -1057,7 +1057,7 @@ class DMFile_Clustered_Index_Test : public DB::base::TiFlashStorageTestBasic auto configuration = mode == DMFileMode::DirectoryChecksum ? std::make_optional() : std::nullopt; path_pool = std::make_unique(db_context->getPathPool().withTable("test", "t", false)); - storage_pool = std::make_unique("test.t1", table_id, *path_pool, *db_context, DB::Settings()); + storage_pool = std::make_unique(*db_context, table_id, *path_pool, "test.t1"); dm_file = DMFile::create(0, path, single_file_mode, std::move(configuration)); table_columns_ = std::make_shared(); column_cache_ = std::make_shared(); diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment.cpp index 197ec73fdb5..6bf33465366 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment.cpp @@ -74,7 +74,7 @@ class Segment_test : public DB::base::TiFlashStorageTestBasic { TiFlashStorageTestBasic::reload(std::move(db_settings)); storage_path_pool = std::make_unique(db_context->getPathPool().withTable("test", "t1", false)); - storage_pool = std::make_unique("test.t1", /*table_id*/ 100, *storage_path_pool, *db_context, db_context->getSettingsRef()); + storage_pool = std::make_unique(*db_context, /*ns_id*/ 100, *storage_path_pool, "test.t1"); storage_pool->restore(); ColumnDefinesPtr cols = (!pre_define_columns) ? DMTestEnv::getDefaultColumns() : pre_define_columns; setColumns(cols); @@ -1280,7 +1280,7 @@ class Segment_test_2 : public Segment_test Segment_test::SetUp(); } - std::pair> genDMFile(DMContext & context, const Block & block) + std::pair genDMFile(DMContext & context, const Block & block) { auto delegator = context.path_pool.getStableDiskDelegator(); auto file_id = context.storage_pool.newDataPageIdForDTFile(delegator, __PRETTY_FUNCTION__); diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment_common_handle.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment_common_handle.cpp index 8fc5c145f81..1cc61663a2f 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment_common_handle.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment_common_handle.cpp @@ -53,7 +53,7 @@ class Segment_Common_Handle_test : public DB::base::TiFlashStorageTestBasic { TiFlashStorageTestBasic::reload(std::move(db_settings)); path_pool = std::make_unique(db_context->getPathPool().withTable("test", "t", false)); - storage_pool = std::make_unique("test.t1", /*table_id*/ 100, *path_pool, *db_context, db_context->getSettingsRef()); + storage_pool = std::make_unique(*db_context, /*table_id*/ 100, *path_pool, "test.t1"); storage_pool->restore(); if (!cols) cols = DMTestEnv::getDefaultColumns(is_common_handle ? DMTestEnv::PkType::CommonHandle : DMTestEnv::PkType::HiddenTiDBRowID); diff --git a/dbms/src/Storages/FormatVersion.h b/dbms/src/Storages/FormatVersion.h index 4095ffb1498..dbe3d9ee9bc 100644 --- a/dbms/src/Storages/FormatVersion.h +++ b/dbms/src/Storages/FormatVersion.h @@ -64,6 +64,11 @@ using Version = UInt32; inline static constexpr Version V1 = 1; // Support multiple thread-write && read with offset inside page. See FLASH_341 && FLASH-942 for details. inline static constexpr Version V2 = 2; +// Support multiple thread-write/read with offset inside page && support wal store meta && support space reused. +// If we do enabled PageFormat::V3, it is not means all data in Disk will be V3 format. +// - If we already have V2 data in disk. It will turn PageStorage into MIX_MODE +// - If we don't have any v2 data in disk. It will turn PageStorage into ONLY_V3 +inline static constexpr Version V3 = 3; } // namespace PageFormat struct StorageFormatVersion @@ -103,7 +108,17 @@ inline static const StorageFormatVersion STORAGE_FORMAT_V3 = StorageFormatVersio .identifier = 3, }; -inline StorageFormatVersion STORAGE_FORMAT_CURRENT = STORAGE_FORMAT_V3; + +inline static const StorageFormatVersion STORAGE_FORMAT_V4 = StorageFormatVersion{ + .segment = SegmentFormat::V2, + .dm_file = DMFileFormat::V2, + .stable = StableFormat::V1, + .delta = DeltaFormat::V3, + .page = PageFormat::V3, // diff + .identifier = 4, +}; + +inline StorageFormatVersion STORAGE_FORMAT_CURRENT = STORAGE_FORMAT_V4; inline const StorageFormatVersion & toStorageFormat(UInt64 setting) { @@ -115,6 +130,8 @@ inline const StorageFormatVersion & toStorageFormat(UInt64 setting) return STORAGE_FORMAT_V2; case 3: return STORAGE_FORMAT_V3; + case 4: + return STORAGE_FORMAT_V4; default: throw Exception("Illegal setting value: " + DB::toString(setting)); } diff --git a/dbms/src/Storages/Page/Page.h b/dbms/src/Storages/Page/Page.h index b36bf7245d6..0217fd82ccf 100644 --- a/dbms/src/Storages/Page/Page.h +++ b/dbms/src/Storages/Page/Page.h @@ -76,6 +76,20 @@ struct Page return ByteBuffer(data.begin() + beg, data.begin() + end); } + inline static PageFieldSizes fieldOffsetsToSizes(const PageFieldOffsetChecksums & field_offsets, size_t data_size) + { + PageFieldSizes field_size = {}; + + auto it = field_offsets.begin(); + while (it != field_offsets.end()) + { + PageFieldOffset beg = it->first; + ++it; + field_size.emplace_back(it == field_offsets.end() ? data_size - beg : it->first - beg); + } + return field_size; + } + size_t fieldSize() const { return field_offsets.size(); diff --git a/dbms/src/Storages/Page/PageStorage.cpp b/dbms/src/Storages/Page/PageStorage.cpp index 7f14d905858..cf8a9698a55 100644 --- a/dbms/src/Storages/Page/PageStorage.cpp +++ b/dbms/src/Storages/Page/PageStorage.cpp @@ -31,4 +31,596 @@ PageStoragePtr PageStorage::create( return std::make_shared(name, delegator, config, file_provider); } +/*************************** + * PageReaderImpl methods * + **************************/ + +class PageReaderImpl : private boost::noncopyable +{ +public: + static std::unique_ptr create( + PageStorageRunMode run_mode_, + NamespaceId ns_id_, + PageStoragePtr storage_v2_, + PageStoragePtr storage_v3_, + const PageStorage::SnapshotPtr & snap_, + ReadLimiterPtr read_limiter_); + + virtual ~PageReaderImpl() = default; + + virtual DB::Page read(PageId page_id) const = 0; + + virtual PageMap read(const PageIds & page_ids) const = 0; + + virtual void read(const PageIds & page_ids, PageHandler & handler) const = 0; + + using PageReadFields = PageStorage::PageReadFields; + virtual PageMap read(const std::vector & page_fields) const = 0; + + virtual PageId getNormalPageId(PageId page_id) const = 0; + + virtual PageEntry getPageEntry(PageId page_id) const = 0; + + virtual PageStorage::SnapshotPtr getSnapshot(const String & tracing_id) const = 0; + + // Get some statistics of all living snapshots and the oldest living snapshot. + virtual SnapshotsStatistics getSnapshotsStat() const = 0; + + virtual void traverse(const std::function & acceptor) const = 0; +}; + + +class PageReaderImplNormal : public PageReaderImpl +{ +public: + /// Not snapshot read. + explicit PageReaderImplNormal(NamespaceId ns_id_, PageStoragePtr storage_, ReadLimiterPtr read_limiter_) + : ns_id(ns_id_) + , storage(storage_) + , read_limiter(read_limiter_) + { + } + + /// Snapshot read. + PageReaderImplNormal(NamespaceId ns_id_, PageStoragePtr storage_, const PageStorage::SnapshotPtr & snap_, ReadLimiterPtr read_limiter_) + : ns_id(ns_id_) + , storage(storage_) + , snap(snap_) + , read_limiter(read_limiter_) + { + } + + DB::Page read(PageId page_id) const override + { + return storage->read(ns_id, page_id, read_limiter, snap); + } + + PageMap read(const PageIds & page_ids) const override + { + return storage->read(ns_id, page_ids, read_limiter, snap); + } + + void read(const PageIds & page_ids, PageHandler & handler) const override + { + storage->read(ns_id, page_ids, handler, read_limiter, snap); + } + + using PageReadFields = PageStorage::PageReadFields; + PageMap read(const std::vector & page_fields) const override + { + return storage->read(ns_id, page_fields, read_limiter, snap); + } + + PageId getNormalPageId(PageId page_id) const override + { + return storage->getNormalPageId(ns_id, page_id, snap); + } + + PageEntry getPageEntry(PageId page_id) const override + { + return storage->getEntry(ns_id, page_id, snap); + } + + PageStorage::SnapshotPtr getSnapshot(const String & tracing_id) const override + { + return storage->getSnapshot(tracing_id); + } + + // Get some statistics of all living snapshots and the oldest living snapshot. + SnapshotsStatistics getSnapshotsStat() const override + { + return storage->getSnapshotsStat(); + } + + void traverse(const std::function & acceptor) const override + { + storage->traverse(acceptor, nullptr); + } + +private: + NamespaceId ns_id; + PageStoragePtr storage; + PageStorage::SnapshotPtr snap; + ReadLimiterPtr read_limiter; +}; + + +class PageReaderImplMixed : public PageReaderImpl +{ +public: + /// Not snapshot read. + explicit PageReaderImplMixed(NamespaceId ns_id_, PageStoragePtr storage_v2_, PageStoragePtr storage_v3_, ReadLimiterPtr read_limiter_) + : ns_id(ns_id_) + , storage_v2(storage_v2_) + , storage_v3(storage_v3_) + , read_limiter(read_limiter_) + { + } + + /// Snapshot read. + PageReaderImplMixed(NamespaceId ns_id_, PageStoragePtr storage_v2_, PageStoragePtr storage_v3_, const PageStorage::SnapshotPtr & snap_, ReadLimiterPtr read_limiter_) + : ns_id(ns_id_) + , storage_v2(storage_v2_) + , storage_v3(storage_v3_) + , snap(snap_) + , read_limiter(read_limiter_) + { + } + + PageReaderImplMixed(NamespaceId ns_id_, PageStoragePtr storage_v2_, PageStoragePtr storage_v3_, PageStorage::SnapshotPtr && snap_, ReadLimiterPtr read_limiter_) + : ns_id(ns_id_) + , storage_v2(storage_v2_) + , storage_v3(storage_v3_) + , snap(std::move(snap_)) + , read_limiter(read_limiter_) + { + } + + DB::Page read(PageId page_id) const override + { + const auto & page_from_v3 = storage_v3->read(ns_id, page_id, read_limiter, toConcreteV3Snapshot(), false); + if (page_from_v3.isValid()) + { + return page_from_v3; + } + return storage_v2->read(ns_id, page_id, read_limiter, toConcreteV2Snapshot()); + } + + PageMap read(const PageIds & page_ids) const override + { + auto page_maps = storage_v3->read(ns_id, page_ids, read_limiter, toConcreteV3Snapshot(), false); + PageIds invalid_page_ids; + for (const auto & [query_page_id, page] : page_maps) + { + if (!page.isValid()) + { + invalid_page_ids.emplace_back(query_page_id); + } + } + + if (!invalid_page_ids.empty()) + { + const auto & page_maps_from_v2 = storage_v2->read(ns_id, invalid_page_ids, read_limiter, toConcreteV2Snapshot()); + for (const auto & [page_id_, page_] : page_maps_from_v2) + { + page_maps[page_id_] = page_; + } + } + + return page_maps; + } + + void read(const PageIds & page_ids, PageHandler & handler) const override + { + const auto & page_ids_not_found = storage_v3->read(ns_id, page_ids, handler, read_limiter, toConcreteV3Snapshot(), false); + storage_v2->read(ns_id, page_ids_not_found, handler, read_limiter, toConcreteV2Snapshot()); + } + + using PageReadFields = PageStorage::PageReadFields; + PageMap read(const std::vector & page_fields) const override + { + auto page_maps = storage_v3->read(ns_id, page_fields, read_limiter, toConcreteV3Snapshot(), false); + + std::vector invalid_page_fields; + + for (const auto & page_field : page_fields) + { + if (!page_maps[page_field.first].isValid()) + { + invalid_page_fields.emplace_back(page_field); + } + } + + if (!invalid_page_fields.empty()) + { + auto page_maps_from_v2 = storage_v2->read(ns_id, invalid_page_fields, read_limiter, toConcreteV2Snapshot()); + for (const auto & page_field : invalid_page_fields) + { + page_maps[page_field.first] = page_maps_from_v2[page_field.first]; + } + } + + return page_maps; + } + + PageId getNormalPageId(PageId page_id) const override + { + PageId resolved_page_id = storage_v3->getNormalPageId(ns_id, page_id, toConcreteV3Snapshot(), false); + if (resolved_page_id != INVALID_PAGE_ID) + { + return resolved_page_id; + } + return storage_v2->getNormalPageId(ns_id, page_id, toConcreteV2Snapshot()); + } + + PageEntry getPageEntry(PageId page_id) const override + { + PageEntry page_entry = storage_v3->getEntry(ns_id, page_id, toConcreteV3Snapshot()); + if (page_entry.file_id != INVALID_BLOBFILE_ID) + { + return page_entry; + } + return storage_v2->getEntry(ns_id, page_id, toConcreteV2Snapshot()); + } + + PageStorage::SnapshotPtr getSnapshot(const String & tracing_id) const override + { + return std::make_shared( + storage_v2->getSnapshot(fmt::format("{}-v2", tracing_id)), + storage_v3->getSnapshot(fmt::format("{}-v3", tracing_id))); + } + + // Get some statistics of all living snapshots and the oldest living snapshot. + SnapshotsStatistics getSnapshotsStat() const override + { + SnapshotsStatistics statistics_total; + const auto & statistics_from_v2 = storage_v2->getSnapshotsStat(); + const auto & statistics_from_v3 = storage_v3->getSnapshotsStat(); + + statistics_total.num_snapshots = statistics_from_v2.num_snapshots + statistics_from_v3.num_snapshots; + if (statistics_from_v2.longest_living_seconds > statistics_from_v3.longest_living_seconds) + { + statistics_total.longest_living_seconds = statistics_from_v2.longest_living_seconds; + statistics_total.longest_living_from_thread_id = statistics_from_v2.longest_living_from_thread_id; + statistics_total.longest_living_from_tracing_id = statistics_from_v2.longest_living_from_tracing_id; + } + else + { + statistics_total.longest_living_seconds = statistics_from_v3.longest_living_seconds; + statistics_total.longest_living_from_thread_id = statistics_from_v3.longest_living_from_thread_id; + statistics_total.longest_living_from_tracing_id = statistics_from_v3.longest_living_from_tracing_id; + } + + return statistics_total; + } + + void traverse(const std::function & acceptor) const override + { + // Used by RegionPersister::restore + // Must traverse storage_v3 before storage_v2 + storage_v3->traverse(acceptor, toConcreteV3Snapshot()); + storage_v2->traverse(acceptor, toConcreteV2Snapshot()); + } + +private: + PageStorage::SnapshotPtr toConcreteV3Snapshot() const + { + return snap ? toConcreteMixedSnapshot(snap)->getV3Snapshot() : snap; + } + + PageStorage::SnapshotPtr toConcreteV2Snapshot() const + { + return snap ? toConcreteMixedSnapshot(snap)->getV2Snapshot() : snap; + } + +private: + const NamespaceId ns_id; + PageStoragePtr storage_v2; + PageStoragePtr storage_v3; + PageStorage::SnapshotPtr snap; + ReadLimiterPtr read_limiter; +}; + +std::unique_ptr PageReaderImpl::create( + PageStorageRunMode run_mode_, + NamespaceId ns_id_, + PageStoragePtr storage_v2_, + PageStoragePtr storage_v3_, + const PageStorage::SnapshotPtr & snap_, + ReadLimiterPtr read_limiter_) +{ + switch (run_mode_) + { + case PageStorageRunMode::ONLY_V2: + { + return std::make_unique(ns_id_, storage_v2_, snap_, read_limiter_); + } + case PageStorageRunMode::ONLY_V3: + { + return std::make_unique(ns_id_, storage_v3_, snap_, read_limiter_); + } + case PageStorageRunMode::MIX_MODE: + { + return std::make_unique(ns_id_, storage_v2_, storage_v3_, snap_, read_limiter_); + } + default: + throw Exception(fmt::format("Unknown PageStorageRunMode {}", static_cast(run_mode_)), ErrorCodes::LOGICAL_ERROR); + } +} + +/*********************** + * PageReader methods * + **********************/ +/// Not snapshot read. +PageReader::PageReader(const PageStorageRunMode & run_mode_, NamespaceId ns_id_, PageStoragePtr storage_v2_, PageStoragePtr storage_v3_, ReadLimiterPtr read_limiter_) + : impl(PageReaderImpl::create(run_mode_, ns_id_, storage_v2_, storage_v3_, /*snap_=*/nullptr, read_limiter_)) +{ +} + +/// Snapshot read. +PageReader::PageReader(const PageStorageRunMode & run_mode_, NamespaceId ns_id_, PageStoragePtr storage_v2_, PageStoragePtr storage_v3_, PageStorage::SnapshotPtr snap_, ReadLimiterPtr read_limiter_) + : impl(PageReaderImpl::create(run_mode_, ns_id_, storage_v2_, storage_v3_, std::move(snap_), read_limiter_)) +{ +} + +PageReader::~PageReader() = default; + +DB::Page PageReader::read(PageId page_id) const +{ + return impl->read(page_id); +} + +PageMap PageReader::read(const PageIds & page_ids) const +{ + return impl->read(page_ids); +} + +void PageReader::read(const PageIds & page_ids, PageHandler & handler) const +{ + impl->read(page_ids, handler); +} + +PageMap PageReader::read(const std::vector & page_fields) const +{ + return impl->read(page_fields); +} + +PageId PageReader::getNormalPageId(PageId page_id) const +{ + return impl->getNormalPageId(page_id); +} + +PageEntry PageReader::getPageEntry(PageId page_id) const +{ + return impl->getPageEntry(page_id); +} + +PageStorage::SnapshotPtr PageReader::getSnapshot(const String & tracing_id) const +{ + return impl->getSnapshot(tracing_id); +} + +// Get some statistics of all living snapshots and the oldest living snapshot. +SnapshotsStatistics PageReader::getSnapshotsStat() const +{ + return impl->getSnapshotsStat(); +} + +void PageReader::traverse(const std::function & acceptor) const +{ + impl->traverse(acceptor); +} + +/********************** + * PageWriter methods * + *********************/ + +void PageWriter::write(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const +{ + switch (run_mode) + { + case PageStorageRunMode::ONLY_V2: + { + writeIntoV2(std::move(write_batch), write_limiter); + break; + } + case PageStorageRunMode::ONLY_V3: + { + writeIntoV3(std::move(write_batch), write_limiter); + break; + } + case PageStorageRunMode::MIX_MODE: + { + writeIntoMixMode(std::move(write_batch), write_limiter); + break; + } + } +} + + +void PageWriter::writeIntoV2(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const +{ + storage_v2->write(std::move(write_batch), write_limiter); +} + +void PageWriter::writeIntoV3(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const +{ + storage_v3->write(std::move(write_batch), write_limiter); +} + +void PageWriter::writeIntoMixMode(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const +{ + const auto & ns_id = write_batch.getNamespaceId(); + WriteBatch wb_for_v2{ns_id}; + WriteBatch wb_for_put_v3{ns_id}; + + // If we do need copy entry from V2 into V3 + // We need hold mem from V2 pages after write. + std::list mem_holders; + + for (const auto & write : write_batch.getWrites()) + { + switch (write.type) + { + // PUT/PUT_EXTERNAL only for V3 + case WriteBatch::WriteType::PUT: + case WriteBatch::WriteType::PUT_EXTERNAL: + { + break; + } + // Both need del in v2 and v3 + case WriteBatch::WriteType::DEL: + { + wb_for_v2.copyWrite(write); + break; + } + case WriteBatch::WriteType::REF: + { + PageId resolved_page_id = storage_v3->getNormalPageId(ns_id, + write.ori_page_id, + /*snapshot*/ nullptr, + false); + // If the normal id is not found in v3, read from v2 and create a new put + ref + if (resolved_page_id == INVALID_PAGE_ID) + { + const auto & entry_for_put = storage_v2->getEntry(ns_id, write.ori_page_id, /*snapshot*/ {}); + if (entry_for_put.isValid()) + { + auto page_for_put = storage_v2->read(ns_id, write.ori_page_id); + + // Keep the mem hold, no need create new one. + mem_holders.emplace_back(page_for_put.mem_holder); + assert(entry_for_put.size == page_for_put.data.size()); + + // Page with fields + if (!entry_for_put.field_offsets.empty()) + { + wb_for_put_v3.putPage(write.ori_page_id, // + entry_for_put.tag, + std::make_shared(page_for_put.data.begin(), page_for_put.data.size()), + page_for_put.data.size(), + Page::fieldOffsetsToSizes(entry_for_put.field_offsets, entry_for_put.size)); + } + else + { // Normal page with fields + wb_for_put_v3.putPage(write.ori_page_id, // + entry_for_put.tag, + std::make_shared(page_for_put.data.begin(), + page_for_put.data.size()), + page_for_put.data.size()); + } + + LOG_FMT_INFO( + Logger::get("PageWriter"), + "Can't find the origin page in v3, migrate a new being ref page into V3 [page_id={}] [origin_id={}] [field_offsets={}]", + write.page_id, + write.ori_page_id, + entry_for_put.field_offsets.size()); + } + else + { + throw Exception(fmt::format("Can't find origin entry in V2 and V3, [ns_id={}, ori_page_id={}]", + ns_id, + write.ori_page_id), + ErrorCodes::LOGICAL_ERROR); + } + } + // else V3 found the origin one. + // Then do nothing. + break; + } + default: + { + throw Exception(fmt::format("Unknown write type: {}", write.type)); + } + } + } + + if (!wb_for_put_v3.empty()) + { + // The `writes` in wb_for_put_v3 must come before the `writes` in write_batch + wb_for_put_v3.copyWrites(write_batch.getWrites()); + storage_v3->write(std::move(wb_for_put_v3), write_limiter); + } + else + { + storage_v3->write(std::move(write_batch), write_limiter); + } + + if (!wb_for_v2.empty()) + { + storage_v2->write(std::move(wb_for_v2), write_limiter); + } +} + + +PageStorage::Config PageWriter::getSettings() const +{ + switch (run_mode) + { + case PageStorageRunMode::ONLY_V2: + { + return storage_v2->getSettings(); + } + case PageStorageRunMode::ONLY_V3: + { + return storage_v3->getSettings(); + } + case PageStorageRunMode::MIX_MODE: + { + throw Exception("Not support.", ErrorCodes::NOT_IMPLEMENTED); + } + default: + throw Exception(fmt::format("Unknown PageStorageRunMode {}", static_cast(run_mode)), ErrorCodes::LOGICAL_ERROR); + } +} + +void PageWriter::reloadSettings(const PageStorage::Config & new_config) const +{ + switch (run_mode) + { + case PageStorageRunMode::ONLY_V2: + { + storage_v2->reloadSettings(new_config); + break; + } + case PageStorageRunMode::ONLY_V3: + { + storage_v3->reloadSettings(new_config); + break; + } + case PageStorageRunMode::MIX_MODE: + { + storage_v2->reloadSettings(new_config); + storage_v3->reloadSettings(new_config); + break; + } + default: + throw Exception(fmt::format("Unknown PageStorageRunMode {}", static_cast(run_mode)), ErrorCodes::LOGICAL_ERROR); + } +}; + +bool PageWriter::gc(bool not_skip, const WriteLimiterPtr & write_limiter, const ReadLimiterPtr & read_limiter) const +{ + switch (run_mode) + { + case PageStorageRunMode::ONLY_V2: + { + return storage_v2->gc(not_skip, write_limiter, read_limiter); + } + case PageStorageRunMode::ONLY_V3: + { + return storage_v3->gc(not_skip, write_limiter, read_limiter); + } + case PageStorageRunMode::MIX_MODE: + { + bool ok = storage_v2->gc(not_skip, write_limiter, read_limiter); + ok |= storage_v3->gc(not_skip, write_limiter, read_limiter); + return ok; + } + default: + throw Exception(fmt::format("Unknown PageStorageRunMode {}", static_cast(run_mode)), ErrorCodes::LOGICAL_ERROR); + } +} + } // namespace DB diff --git a/dbms/src/Storages/Page/PageStorage.h b/dbms/src/Storages/Page/PageStorage.h index 34d91dbd1ad..fe8946549cd 100644 --- a/dbms/src/Storages/Page/PageStorage.h +++ b/dbms/src/Storages/Page/PageStorage.h @@ -14,6 +14,7 @@ #pragma once +#include #include #include #include @@ -23,10 +24,12 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -34,6 +37,7 @@ #include #include + namespace DB { class FileProvider; @@ -45,6 +49,20 @@ using PSDiskDelegatorPtr = std::shared_ptr; class Context; class PageStorage; using PageStoragePtr = std::shared_ptr; +class RegionPersister; + +namespace ErrorCodes +{ +extern const int LOGICAL_ERROR; +} // namespace ErrorCodes + + +enum class PageStorageRunMode : UInt8 +{ + ONLY_V2 = 1, + ONLY_V3 = 2, + MIX_MODE = 3, +}; struct ExternalPageCallbacks { @@ -228,6 +246,8 @@ class PageStorage : private boost::noncopyable virtual size_t getNumberOfPages() = 0; + virtual std::set getAliveExternalPageIds(NamespaceId ns_id) = 0; + void write(WriteBatch && write_batch, const WriteLimiterPtr & write_limiter = nullptr) { writeImpl(std::move(write_batch), write_limiter); @@ -321,72 +341,81 @@ class PageStorage : private boost::noncopyable FileProviderPtr file_provider; }; - +// An impl class to hide the details for PageReaderImplMixed +class PageReaderImpl; +// A class to wrap read with a specify snapshot class PageReader : private boost::noncopyable { public: /// Not snapshot read. - explicit PageReader(NamespaceId ns_id_, PageStoragePtr storage_, ReadLimiterPtr read_limiter_) - : ns_id(ns_id_) - , storage(storage_) - , read_limiter(read_limiter_) - {} + explicit PageReader(const PageStorageRunMode & run_mode_, NamespaceId ns_id_, PageStoragePtr storage_v2_, PageStoragePtr storage_v3_, ReadLimiterPtr read_limiter_); + /// Snapshot read. - PageReader(NamespaceId ns_id_, PageStoragePtr storage_, const PageStorage::SnapshotPtr & snap_, ReadLimiterPtr read_limiter_) - : ns_id(ns_id_) - , storage(storage_) - , snap(snap_) - , read_limiter(read_limiter_) - {} - PageReader(NamespaceId ns_id_, PageStoragePtr storage_, PageStorage::SnapshotPtr && snap_, ReadLimiterPtr read_limiter_) - : ns_id(ns_id_) - , storage(storage_) - , snap(std::move(snap_)) - , read_limiter(read_limiter_) - {} - - DB::Page read(PageId page_id) const - { - return storage->read(ns_id, page_id, read_limiter, snap); - } + PageReader(const PageStorageRunMode & run_mode_, NamespaceId ns_id_, PageStoragePtr storage_v2_, PageStoragePtr storage_v3_, PageStorage::SnapshotPtr snap_, ReadLimiterPtr read_limiter_); - PageMap read(const std::vector & page_ids) const - { - return storage->read(ns_id, page_ids, read_limiter, snap); - } + ~PageReader(); - void read(const std::vector & page_ids, PageHandler & handler) const - { - storage->read(ns_id, page_ids, handler, read_limiter, snap); - } + DB::Page read(PageId page_id) const; + + PageMap read(const PageIds & page_ids) const; + + void read(const PageIds & page_ids, PageHandler & handler) const; using PageReadFields = PageStorage::PageReadFields; - PageMap read(const std::vector & page_fields) const - { - return storage->read(ns_id, page_fields, read_limiter, snap); - } + PageMap read(const std::vector & page_fields) const; - PageId getNormalPageId(PageId page_id) const - { - return storage->getNormalPageId(ns_id, page_id, snap); - } + PageId getNormalPageId(PageId page_id) const; - UInt64 getPageChecksum(PageId page_id) const - { - return storage->getEntry(ns_id, page_id, snap).checksum; - } + PageEntry getPageEntry(PageId page_id) const; + + PageStorage::SnapshotPtr getSnapshot(const String & tracing_id) const; + + // Get some statistics of all living snapshots and the oldest living snapshot. + SnapshotsStatistics getSnapshotsStat() const; - PageEntry getPageEntry(PageId page_id) const + void traverse(const std::function & acceptor) const; + +private: + std::unique_ptr impl; +}; +using PageReaderPtr = std::shared_ptr; + +class PageWriter : private boost::noncopyable +{ +public: + PageWriter(PageStorageRunMode run_mode_, PageStoragePtr storage_v2_, PageStoragePtr storage_v3_) + : run_mode(run_mode_) + , storage_v2(storage_v2_) + , storage_v3(storage_v3_) { - return storage->getEntry(ns_id, page_id, snap); } + void write(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const; + + friend class RegionPersister; + +private: + void writeIntoV2(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const; + + void writeIntoV3(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const; + + void writeIntoMixMode(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const; + + // A wrap of getSettings only used for `RegionPersister::gc` + PageStorage::Config getSettings() const; + + // A wrap of reloadSettings only used for `RegionPersister::gc` + void reloadSettings(const PageStorage::Config & new_config) const; + + // A wrap of gc only used for `RegionPersister::gc` + bool gc(bool not_skip, const WriteLimiterPtr & write_limiter, const ReadLimiterPtr & read_limiter) const; + private: - NamespaceId ns_id; - PageStoragePtr storage; - PageStorage::SnapshotPtr snap; - ReadLimiterPtr read_limiter; + PageStorageRunMode run_mode; + PageStoragePtr storage_v2; + PageStoragePtr storage_v3; }; +using PageWriterPtr = std::shared_ptr; } // namespace DB diff --git a/dbms/src/Storages/Page/Snapshot.h b/dbms/src/Storages/Page/Snapshot.h index 348d084382f..77e68f1b054 100644 --- a/dbms/src/Storages/Page/Snapshot.h +++ b/dbms/src/Storages/Page/Snapshot.h @@ -13,6 +13,7 @@ // limitations under the License. #pragma once +#include #include #include @@ -33,4 +34,37 @@ class PageStorageSnapshot }; using PageStorageSnapshotPtr = std::shared_ptr; +class PageStorageSnapshotMixed : public PageStorageSnapshot +{ +public: + // TODO: add/sub CurrentMetrics::PSMVCCNumSnapshots in here + PageStorageSnapshotMixed(const PageStorageSnapshotPtr & snapshot_v2_, const PageStorageSnapshotPtr & snapshot_v3_) + : snapshot_v2(snapshot_v2_) + , snapshot_v3(snapshot_v3_) + {} + + ~PageStorageSnapshotMixed() = default; + + PageStorageSnapshotPtr getV2Snapshot() + { + return snapshot_v2; + } + + PageStorageSnapshotPtr getV3Snapshot() + { + return snapshot_v3; + } + +private: + PageStorageSnapshotPtr snapshot_v2; + PageStorageSnapshotPtr snapshot_v3; +}; +using PageStorageSnapshotMixedPtr = std::shared_ptr; + +static inline PageStorageSnapshotMixedPtr +toConcreteMixedSnapshot(const PageStorageSnapshotPtr & ptr) +{ + return std::static_pointer_cast(ptr); +} + } // namespace DB diff --git a/dbms/src/Storages/Page/V2/PageFile.cpp b/dbms/src/Storages/Page/V2/PageFile.cpp index 60846685eb5..5f2ef7c26e0 100644 --- a/dbms/src/Storages/Page/V2/PageFile.cpp +++ b/dbms/src/Storages/Page/V2/PageFile.cpp @@ -134,7 +134,7 @@ std::pair genWriteData( // char * data_pos = data_buffer; PageUtil::put(meta_pos, meta_write_bytes); - PageUtil::put(meta_pos, STORAGE_FORMAT_CURRENT.page); + PageUtil::put(meta_pos, PageFormat::V2); PageUtil::put(meta_pos, wb.getSequence()); PageOffset page_data_file_off = page_file.getDataFileAppendPos(); diff --git a/dbms/src/Storages/Page/V2/PageStorage.cpp b/dbms/src/Storages/Page/V2/PageStorage.cpp index 4c47645e7d7..0452d0cc8ae 100644 --- a/dbms/src/Storages/Page/V2/PageStorage.cpp +++ b/dbms/src/Storages/Page/V2/PageStorage.cpp @@ -607,6 +607,19 @@ size_t PageStorage::getNumberOfPages() } } +std::set PageStorage::getAliveExternalPageIds(NamespaceId /*ns_id*/) +{ + const auto & concrete_snap = getConcreteSnapshot(); + if (concrete_snap) + { + return concrete_snap->version()->validNormalPageIds(); + } + else + { + throw Exception("Can't get concrete snapshot", ErrorCodes::LOGICAL_ERROR); + } +} + DB::Page PageStorage::readImpl(NamespaceId /*ns_id*/, PageId page_id, const ReadLimiterPtr & read_limiter, SnapshotPtr snapshot, bool throw_on_not_exist) { if (!snapshot) diff --git a/dbms/src/Storages/Page/V2/PageStorage.h b/dbms/src/Storages/Page/V2/PageStorage.h index 1e32de40dd0..6276c5e5086 100644 --- a/dbms/src/Storages/Page/V2/PageStorage.h +++ b/dbms/src/Storages/Page/V2/PageStorage.h @@ -107,6 +107,8 @@ class PageStorage : public DB::PageStorage size_t getNumberOfPages() override; + std::set getAliveExternalPageIds(NamespaceId ns_id) override; + void writeImpl(DB::WriteBatch && wb, const WriteLimiterPtr & write_limiter) override; DB::PageEntry getEntryImpl(NamespaceId ns_id, PageId page_id, SnapshotPtr snapshot) override; @@ -144,11 +146,11 @@ class PageStorage : public DB::PageStorage option.remove_tmp_files = false; auto page_files = listAllPageFiles(file_provider, delegator, log, option); if (page_files.empty()) - return STORAGE_FORMAT_CURRENT.page; + return PageFormat::V2; bool all_empty = true; PageFormat::Version max_binary_version = PageFormat::V1; - PageFormat::Version temp_version = STORAGE_FORMAT_CURRENT.page; + PageFormat::Version temp_version = PageFormat::V2; for (auto iter = page_files.rbegin(); iter != page_files.rend(); ++iter) { // Skip those files without valid meta @@ -167,7 +169,7 @@ class PageStorage : public DB::PageStorage LOG_FMT_DEBUG(log, "getMaxDataVersion done from {} [max version={}]", reader->toString(), max_binary_version); break; } - max_binary_version = (all_empty ? STORAGE_FORMAT_CURRENT.page : max_binary_version); + max_binary_version = (all_empty ? PageFormat::V2 : max_binary_version); return max_binary_version; } diff --git a/dbms/src/Storages/Page/V3/BlobStore.cpp b/dbms/src/Storages/Page/V3/BlobStore.cpp index 2c568d8e85b..3e58a3eb87e 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.cpp +++ b/dbms/src/Storages/Page/V3/BlobStore.cpp @@ -380,6 +380,11 @@ void BlobStore::removePosFromStats(BlobFileId blob_id, BlobFileOffset offset, si void BlobStore::read(PageIDAndEntriesV3 & entries, const PageHandler & handler, const ReadLimiterPtr & read_limiter) { + if (entries.empty()) + { + return; + } + ProfileEvents::increment(ProfileEvents::PSMReadPages, entries.size()); // Sort in ascending order by offset in file. @@ -444,6 +449,11 @@ void BlobStore::read(PageIDAndEntriesV3 & entries, const PageHandler & handler, PageMap BlobStore::read(FieldReadInfos & to_read, const ReadLimiterPtr & read_limiter) { + if (to_read.empty()) + { + return {}; + } + ProfileEvents::increment(ProfileEvents::PSMReadPages, to_read.size()); // Sort in ascending order by offset in file. @@ -542,6 +552,11 @@ PageMap BlobStore::read(FieldReadInfos & to_read, const ReadLimiterPtr & read_li PageMap BlobStore::read(PageIDAndEntriesV3 & entries, const ReadLimiterPtr & read_limiter) { + if (entries.empty()) + { + return {}; + } + ProfileEvents::increment(ProfileEvents::PSMReadPages, entries.size()); // Sort in ascending order by offset in file. @@ -621,6 +636,13 @@ PageMap BlobStore::read(PageIDAndEntriesV3 & entries, const ReadLimiterPtr & rea Page BlobStore::read(const PageIDAndEntryV3 & id_entry, const ReadLimiterPtr & read_limiter) { + if (!id_entry.second.isValid()) + { + Page page_not_found; + page_not_found.page_id = INVALID_PAGE_ID; + return page_not_found; + } + const auto & [page_id_v3, entry] = id_entry; const size_t buf_size = entry.size; diff --git a/dbms/src/Storages/Page/V3/PageDirectory.cpp b/dbms/src/Storages/Page/V3/PageDirectory.cpp index 2a272392b70..35e73e5c5ff 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectory.cpp @@ -841,7 +841,7 @@ PageIdV3Internal PageDirectory::getNormalPageId(PageIdV3Internal page_id, const } else { - return PageIdV3Internal(0, INVALID_PAGE_ID); + return buildV3Id(0, INVALID_PAGE_ID); } } } @@ -878,7 +878,7 @@ PageIdV3Internal PageDirectory::getNormalPageId(PageIdV3Internal page_id, const } else { - return PageIdV3Internal(0, INVALID_PAGE_ID); + return buildV3Id(0, INVALID_PAGE_ID); } } diff --git a/dbms/src/Storages/Page/V3/PageEntry.h b/dbms/src/Storages/Page/V3/PageEntry.h index 22379611972..bcefc0e845b 100644 --- a/dbms/src/Storages/Page/V3/PageEntry.h +++ b/dbms/src/Storages/Page/V3/PageEntry.h @@ -45,6 +45,7 @@ struct PageEntryV3 { return size + padded_size; } + inline bool isValid() const { return file_id != INVALID_BLOBFILE_ID; } size_t getFieldSize(size_t index) const @@ -86,7 +87,13 @@ inline PageIdV3Internal buildV3Id(NamespaceId n_id, PageId p_id) inline String toDebugString(const PageEntryV3 & entry) { - return fmt::format("PageEntry{{file: {}, offset: 0x{:X}, size: {}, checksum: 0x{:X}}}", entry.file_id, entry.offset, entry.size, entry.checksum); + return fmt::format("PageEntry{{file: {}, offset: 0x{:X}, size: {}, checksum: 0x{:X}, tag: {}, field_offsets_size: {}}}", + entry.file_id, + entry.offset, + entry.size, + entry.checksum, + entry.tag, + entry.field_offsets.size()); } } // namespace PS::V3 diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp index ab1ba0b04e1..de0a26f68c7 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp @@ -86,6 +86,11 @@ size_t PageStorageImpl::getNumberOfPages() return page_directory->numPages(); } +std::set PageStorageImpl::getAliveExternalPageIds(NamespaceId ns_id) +{ + return page_directory->getAliveExternalIds(ns_id); +} + void PageStorageImpl::writeImpl(DB::WriteBatch && write_batch, const WriteLimiterPtr & write_limiter) { if (unlikely(write_batch.empty())) @@ -134,13 +139,6 @@ DB::Page PageStorageImpl::readImpl(NamespaceId ns_id, PageId page_id, const Read } auto page_entry = throw_on_not_exist ? page_directory->get(buildV3Id(ns_id, page_id), snapshot) : page_directory->getOrNull(buildV3Id(ns_id, page_id), snapshot); - if (!page_entry.second.isValid()) - { - Page page_not_found; - page_not_found.page_id = INVALID_PAGE_ID; - return page_not_found; - } - return blob_store.read(page_entry, read_limiter); } @@ -153,7 +151,9 @@ PageMap PageStorageImpl::readImpl(NamespaceId ns_id, const PageIds & page_ids, c PageIdV3Internals page_id_v3s; for (auto p_id : page_ids) + { page_id_v3s.emplace_back(buildV3Id(ns_id, p_id)); + } if (throw_on_not_exist) { @@ -163,7 +163,8 @@ PageMap PageStorageImpl::readImpl(NamespaceId ns_id, const PageIds & page_ids, c else { auto [page_entries, page_ids_not_found] = page_directory->getOrNull(page_id_v3s, snapshot); - auto page_map = blob_store.read(page_entries, read_limiter); + PageMap page_map = blob_store.read(page_entries, read_limiter); + for (const auto & page_id_not_found : page_ids_not_found) { Page page_not_found; @@ -211,6 +212,7 @@ PageMap PageStorageImpl::readImpl(NamespaceId ns_id, const std::vectorget(buildV3Id(ns_id, page_id), snapshot) : page_directory->getOrNull(buildV3Id(ns_id, page_id), snapshot); + if (entry.isValid()) { auto info = BlobStore::FieldReadInfo(buildV3Id(ns_id, page_id), entry, field_indices); @@ -221,8 +223,8 @@ PageMap PageStorageImpl::readImpl(NamespaceId ns_id, const std::vectorgetAliveExternalIds(ns_id); + auto alive_external_ids = getAliveExternalPageIds(ns_id); callbacks.remover(pending_external_pages, alive_external_ids); } } diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.h b/dbms/src/Storages/Page/V3/PageStorageImpl.h index 7e86a110ab9..62c45ac685d 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.h +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.h @@ -73,6 +73,8 @@ class PageStorageImpl : public DB::PageStorage size_t getNumberOfPages() override; + std::set getAliveExternalPageIds(NamespaceId ns_id) override; + void writeImpl(DB::WriteBatch && write_batch, const WriteLimiterPtr & write_limiter) override; DB::PageEntry getEntryImpl(NamespaceId ns_id, PageId page_id, SnapshotPtr snapshot) override; diff --git a/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp b/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp index c8facc39b80..adaa4e2ee08 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp @@ -328,6 +328,7 @@ try { for (const auto & [path, stats] : blob_store.blob_stats.getStats()) { + (void)path; for (const auto & stat : stats) { if (stat->id == file_id1) diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp index a1cf73dd10a..4bd3b2832b0 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp @@ -190,7 +190,7 @@ try rate_target, LimiterType::UNKNOW); - std::vector page_ids; + PageIds page_ids; for (size_t i = 0; i < wb_nums; ++i) { page_ids.emplace_back(page_id + i); diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp new file mode 100644 index 00000000000..d3fdafe57e8 --- /dev/null +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp @@ -0,0 +1,494 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include +#include +#include +#include +#include +#include + +namespace DB +{ +using namespace DM; +using namespace tests; +namespace PS::V3::tests +{ +class PageStorageMixedTest : public DB::base::TiFlashStorageTestBasic +{ +public: + static void SetUpTestCase() + { + auto path = TiFlashTestEnv::getTemporaryPath("PageStorageMixedTestV3Path"); + dropDataOnDisk(path); + createIfNotExist(path); + + std::vector caps = {}; + Strings paths = {path}; + + auto & global_context = TiFlashTestEnv::getGlobalContext(); + + storage_path_pool_v3 = std::make_unique(Strings{path}, Strings{path}, Strings{}, std::make_shared(0, paths, caps, Strings{}, caps), global_context.getFileProvider(), true); + + global_context.setPageStorageRunMode(PageStorageRunMode::MIX_MODE); + if (!global_context.getGlobalStoragePool()) + global_context.initializeGlobalStoragePoolIfNeed(*storage_path_pool_v3); + } + + void SetUp() override + { + TiFlashStorageTestBasic::SetUp(); + const auto & path = getTemporaryPath(); + createIfNotExist(path); + + auto & global_context = DB::tests::TiFlashTestEnv::getGlobalContext(); + + std::vector caps = {}; + Strings paths = {path}; + + PathCapacityMetricsPtr cap_metrics = std::make_shared(0, paths, caps, Strings{}, caps); + storage_path_pool_v2 = std::make_unique(Strings{path}, Strings{path}, "test", "t1", true, cap_metrics, global_context.getFileProvider()); + + global_context.setPageStorageRunMode(PageStorageRunMode::ONLY_V2); + storage_pool_v2 = std::make_unique(global_context, TEST_NAMESPACE_ID, *storage_path_pool_v2, "test.t1"); + + global_context.setPageStorageRunMode(PageStorageRunMode::MIX_MODE); + storage_pool_mix = std::make_unique(global_context, + TEST_NAMESPACE_ID, + *storage_path_pool_v2, + "test.t1"); + + reloadV2StoragePool(); + } + + PageStorageRunMode reloadMixedStoragePool() + { + DB::tests::TiFlashTestEnv::getContext().setPageStorageRunMode(PageStorageRunMode::MIX_MODE); + PageStorageRunMode run_mode = storage_pool_mix->restore(); + page_writer_mix = storage_pool_mix->logWriter(); + page_reader_mix = storage_pool_mix->logReader(); + return run_mode; + } + + void reloadV2StoragePool() + { + DB::tests::TiFlashTestEnv::getContext().setPageStorageRunMode(PageStorageRunMode::ONLY_V2); + storage_pool_v2->restore(); + page_writer_v2 = storage_pool_v2->logWriter(); + page_reader_v2 = storage_pool_v2->logReader(); + } + +protected: + std::unique_ptr storage_path_pool_v2; + static std::unique_ptr storage_path_pool_v3; + std::unique_ptr storage_pool_v2; + std::unique_ptr storage_pool_mix; + + PageWriterPtr page_writer_v2; + PageWriterPtr page_writer_mix; + + PageReaderPtr page_reader_v2; + PageReaderPtr page_reader_mix; +}; + +std::unique_ptr PageStorageMixedTest::storage_path_pool_v3 = nullptr; + +inline ::testing::AssertionResult getPageCompare( + const char * /*buff_cmp_expr*/, + const char * buf_size_expr, + const char * /*page_cmp_expr*/, + const char * page_id_expr, + char * buff_cmp, + const size_t buf_size, + const Page & page_cmp, + const PageId & page_id) +{ + if (page_cmp.data.size() != buf_size) + { + return testing::internal::EqFailure( + DB::toString(buf_size).c_str(), + DB::toString(page_cmp.data.size()).c_str(), + buf_size_expr, + "page.data.size()", + false); + } + + if (page_cmp.page_id != page_id) + { + return testing::internal::EqFailure( + DB::toString(page_id).c_str(), + DB::toString(page_cmp.page_id).c_str(), + page_id_expr, + "page.page_id", + false); + } + + if (strncmp(page_cmp.data.begin(), buff_cmp, buf_size) != 0) + { + return ::testing::AssertionFailure( // + ::testing::Message( + "Page data not match the buffer")); + } + + return ::testing::AssertionSuccess(); +} + +#define ASSERT_PAGE_EQ(buff_cmp, buf_size, page_cmp, page_id) \ + ASSERT_PRED_FORMAT4(getPageCompare, buff_cmp, buf_size, page_cmp, page_id) +#define EXPECT_PAGE_EQ(buff_cmp, buf_size, page_cmp, page_id) \ + EXPECT_PRED_FORMAT4(getPageCompare, buff_cmp, buf_size, page_cmp, page_id) + +TEST_F(PageStorageMixedTest, WriteRead) +try +{ + UInt64 tag = 0; + const size_t buf_sz = 1024; + char c_buff[buf_sz]; + for (size_t i = 0; i < buf_sz; ++i) + { + c_buff[i] = i % 0xff; + } + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, tag, buff, buf_sz); + buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(2, tag, buff, buf_sz); + page_writer_v2->write(std::move(batch), nullptr); + } + + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + { + const auto & page1 = page_reader_mix->read(1); + const auto & page2 = page_reader_mix->read(2); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page2, 2); + } + + { + WriteBatch batch; + const size_t buf_sz2 = 2048; + char c_buff2[buf_sz2] = {0}; + + ReadBufferPtr buff2 = std::make_shared(c_buff2, sizeof(c_buff2)); + batch.putPage(3, tag, buff2, buf_sz2); + page_writer_mix->write(std::move(batch), nullptr); + + const auto & page3 = page_reader_mix->read(3); + ASSERT_PAGE_EQ(c_buff2, buf_sz2, page3, 3); + reloadV2StoragePool(); + ASSERT_THROW(page_reader_v2->read(3), DB::Exception); + } + + { + // Revert v3 + WriteBatch batch; + batch.delPage(3); + page_writer_mix->write(std::move(batch), nullptr); + } +} +CATCH + +TEST_F(PageStorageMixedTest, Read) +try +{ + UInt64 tag = 0; + const size_t buf_sz = 1024; + char c_buff[buf_sz]; + for (size_t i = 0; i < buf_sz; ++i) + { + c_buff[i] = i % 0xff; + } + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, tag, buff, buf_sz); + buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(2, tag, buff, buf_sz, {20, 120, 400, 200, 15, 75, 170, 24}); + buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(7, tag, buff, buf_sz, {500, 500, 24}); + page_writer_v2->write(std::move(batch), nullptr); + } + + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + { + const auto & page1 = page_reader_mix->read(1); + const auto & page2 = page_reader_mix->read(2); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page2, 2); + } + + const size_t buf_sz2 = 2048; + char c_buff2[buf_sz2] = {0}; + { + WriteBatch batch; + ReadBufferPtr buff2 = std::make_shared(c_buff2, sizeof(c_buff2)); + batch.putPage(3, tag, buff2, buf_sz2); + buff2 = std::make_shared(c_buff2, sizeof(c_buff2)); + batch.putPage(4, tag, buff2, buf_sz2, {20, 120, 400, 200, 15, 75, 170, 24, 500, 500, 24}); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + PageIds page_ids = {1, 2, 3, 4}; + auto page_maps = page_reader_mix->read(page_ids); + ASSERT_EQ(page_maps.size(), 4); + ASSERT_PAGE_EQ(c_buff, buf_sz, page_maps[1], 1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page_maps[2], 2); + ASSERT_PAGE_EQ(c_buff2, buf_sz2, page_maps[3], 3); + ASSERT_PAGE_EQ(c_buff2, buf_sz2, page_maps[4], 4); + + // Read page ids which only exited in V2 + page_ids = {1, 2, 7}; + page_maps = page_reader_mix->read(page_ids); + ASSERT_EQ(page_maps.size(), 3); + ASSERT_PAGE_EQ(c_buff, buf_sz, page_maps[1], 1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page_maps[2], 2); + ASSERT_PAGE_EQ(c_buff, buf_sz, page_maps[7], 7); + } + + { + PageIds page_ids = {1, 2, 3, 4}; + PageHandler hander = [](DB::PageId /*page_id*/, const Page & /*page*/) { + }; + ASSERT_NO_THROW(page_reader_mix->read(page_ids, hander)); + + // Read page ids which only exited in V2 + page_ids = {1, 2, 7}; + ASSERT_NO_THROW(page_reader_mix->read(page_ids, hander)); + } + + { + std::vector read_fields; + read_fields.emplace_back(std::make_pair(2, {1, 3, 6})); + read_fields.emplace_back(std::make_pair(4, {1, 3, 4, 8, 10})); + read_fields.emplace_back(std::make_pair(7, {0, 1, 2})); + PageMap page_maps = page_reader_mix->read(read_fields); + ASSERT_EQ(page_maps.size(), 3); + ASSERT_EQ(page_maps[2].page_id, 2); + ASSERT_EQ(page_maps[2].field_offsets.size(), 3); + ASSERT_EQ(page_maps[4].page_id, 4); + ASSERT_EQ(page_maps[4].field_offsets.size(), 5); + ASSERT_EQ(page_maps[7].page_id, 7); + ASSERT_EQ(page_maps[7].field_offsets.size(), 3); + } + + { + // Read page ids which only exited in V2 + std::vector read_fields; + read_fields.emplace_back(std::make_pair(2, {1, 3, 6})); + ASSERT_NO_THROW(page_reader_mix->read(read_fields)); + } + + { + // Revert v3 + WriteBatch batch; + batch.delPage(3); + batch.delPage(4); + page_writer_mix->write(std::move(batch), nullptr); + } +} +CATCH + +TEST_F(PageStorageMixedTest, ReadWithSnapshot) +try +{ + UInt64 tag = 0; + const size_t buf_sz = 1024; + char c_buff[buf_sz]; + for (size_t i = 0; i < buf_sz; ++i) + { + c_buff[i] = i % 0xff; + } + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, tag, buff, buf_sz); + buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(2, tag, buff, buf_sz, {20, 120, 400, 200, 15, 75, 170, 24}); + page_writer_v2->write(std::move(batch), nullptr); + } + + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + const size_t buf_sz2 = 2048; + char c_buff2[buf_sz2] = {0}; + { + WriteBatch batch; + ReadBufferPtr buff2 = std::make_shared(c_buff2, sizeof(c_buff2)); + batch.putPage(3, tag, buff2, buf_sz2); + page_writer_mix->write(std::move(batch), nullptr); + } + + auto snapshot_mix = page_reader_mix->getSnapshot("ReadWithSnapshotTest"); + + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_mix); + + const auto & page1 = page_reader_mix_with_snap.read(1); + const auto & page2 = page_reader_mix_with_snap.read(2); + const auto & page3 = page_reader_mix_with_snap.read(3); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page2, 2); + ASSERT_PAGE_EQ(c_buff2, buf_sz2, page3, 3); + } + + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, true, "ReadWithSnapshotTest"); + const auto & page1 = page_reader_mix_with_snap.read(1); + const auto & page2 = page_reader_mix_with_snap.read(2); + const auto & page3 = page_reader_mix_with_snap.read(3); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page2, 2); + ASSERT_PAGE_EQ(c_buff2, buf_sz2, page3, 3); + } + + { + WriteBatch batch; + ReadBufferPtr buff2 = std::make_shared(c_buff2, sizeof(c_buff2)); + batch.putPage(4, tag, buff2, buf_sz2); + page_writer_mix->write(std::move(batch), nullptr); + } + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_mix); + ASSERT_THROW(page_reader_mix_with_snap.read(4), DB::Exception); + } + + { + // Revert v3 + WriteBatch batch; + batch.delPage(3); + batch.delPage(4); + page_writer_mix->write(std::move(batch), nullptr); + } +} +CATCH + + +TEST_F(PageStorageMixedTest, PutExt) +try +{ + { + WriteBatch batch; + batch.putExternal(1, 0); + batch.putExternal(2, 0); + batch.putExternal(3, 0); + page_writer_v2->write(std::move(batch), nullptr); + } + + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); +} +CATCH + + +TEST_F(PageStorageMixedTest, Del) +try +{ + const size_t buf_sz = 1024; + char c_buff[buf_sz] = {0}; + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, 0, buff, buf_sz); + page_writer_v2->write(std::move(batch), nullptr); + } + + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + + { + WriteBatch batch; + batch.delPage(1); + ASSERT_NO_THROW(page_writer_mix->write(std::move(batch), nullptr)); + } + + reloadV2StoragePool(); + ASSERT_THROW(page_reader_v2->read(1), DB::Exception); +} +CATCH + +TEST_F(PageStorageMixedTest, Ref) +try +{ + const size_t buf_sz = 1024; + char c_buff[buf_sz] = {0}; + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(7, 0, buff, buf_sz); + buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(8, 0, buff, buf_sz, {20, 120, 400, 200, 15, 75, 170, 24}); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + const auto & entry = page_reader_mix->getPageEntry(8); + ASSERT_EQ(entry.field_offsets.size(), 8); + } + + { + WriteBatch batch; + batch.putRefPage(9, 7); + // ASSERT_NO_THROW(page_writer_mix->write(std::move(batch), nullptr)); + page_writer_mix->write(std::move(batch), nullptr); + ASSERT_EQ(page_reader_mix->getNormalPageId(9), 7); + } + + reloadV2StoragePool(); + ASSERT_THROW(page_reader_v2->read(9), DB::Exception); + + { + WriteBatch batch; + batch.putRefPage(10, 8); + + ASSERT_NO_THROW(page_writer_mix->write(std::move(batch), nullptr)); + ASSERT_EQ(page_reader_mix->getNormalPageId(10), 8); + + std::vector read_fields; + read_fields.emplace_back(std::pair(10, {0, 1, 2, 6})); + + PageMap page_maps = page_reader_mix->read(read_fields); + ASSERT_EQ(page_maps.size(), 1); + ASSERT_EQ(page_maps[10].page_id, 10); + ASSERT_EQ(page_maps[10].field_offsets.size(), 4); + ASSERT_EQ(page_maps[10].data.size(), 710); + + auto field_offset = page_maps[10].field_offsets; + auto it = field_offset.begin(); + ASSERT_EQ(it->offset, 0); + ++it; + ASSERT_EQ(it->offset, 20); + ++it; + ASSERT_EQ(it->offset, 140); + ++it; + ASSERT_EQ(it->offset, 540); + } + + { + // Revert v3 + WriteBatch batch; + batch.delPage(9); + batch.delPage(10); + page_writer_mix->write(std::move(batch), nullptr); + } +} +CATCH + +} // namespace PS::V3::tests +} // namespace DB diff --git a/dbms/src/Storages/Page/WriteBatch.h b/dbms/src/Storages/Page/WriteBatch.h index bde03c4de57..30180874fe6 100644 --- a/dbms/src/Storages/Page/WriteBatch.h +++ b/dbms/src/Storages/Page/WriteBatch.h @@ -184,6 +184,19 @@ class WriteBatch : private boost::noncopyable std::swap(o.sequence, sequence); } + void copyWrite(const Write write) + { + writes.emplace_back(write); + } + + void copyWrites(const Writes & writes_) + { + for (const auto & write_ : writes_) + { + copyWrite(write_); + } + } + void clear() { Writes tmp; diff --git a/dbms/src/Storages/PathPool.cpp b/dbms/src/Storages/PathPool.cpp index 5a542ba0eea..0ae42ec7313 100644 --- a/dbms/src/Storages/PathPool.cpp +++ b/dbms/src/Storages/PathPool.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include #include @@ -24,7 +25,6 @@ #include #include #include -#include #include #include @@ -64,7 +64,7 @@ PathPool::PathPool( , enable_raft_compatible_mode(enable_raft_compatible_mode_) , global_capacity(global_capacity_) , file_provider(file_provider_) - , log(&Poco::Logger::get("PathPool")) + , log(Logger::get("PathPool")) { if (kvstore_paths.empty()) { @@ -134,7 +134,7 @@ StoragePathPool::StoragePathPool( // , path_need_database_name(path_need_database_name_) , global_capacity(std::move(global_capacity_)) , file_provider(std::move(file_provider_)) - , log(&Poco::Logger::get("StoragePathPool")) + , log(Logger::get("StoragePathPool")) { if (unlikely(database.empty() || table.empty())) throw Exception(fmt::format("Can NOT create StoragePathPool [database={}] [table={}]", database, table), ErrorCodes::LOGICAL_ERROR); @@ -240,6 +240,7 @@ void StoragePathPool::drop(bool recursive, bool must_success) { if (Poco::File dir(path_info.path); dir.exists()) { + LOG_FMT_INFO(log, "Begin to drop [dir={}] from main_path_infos", path_info.path); file_provider->deleteDirectory(dir.path(), false, recursive); // update global used size @@ -269,6 +270,7 @@ void StoragePathPool::drop(bool recursive, bool must_success) { if (Poco::File dir(path_info.path); dir.exists()) { + LOG_FMT_INFO(log, "Begin to drop [dir={}] from latest_path_infos", path_info.path); file_provider->deleteDirectory(dir.path(), false, recursive); // When PageStorage is dropped, it will update the size in global_capacity. @@ -318,7 +320,7 @@ String genericChoosePath(const std::vector & paths, // const PathCapacityMetricsPtr & global_capacity, // std::function & paths, size_t idx)> path_generator, // std::function path_getter, // - Poco::Logger * log, // + LoggerPtr log, // const String & log_msg) { if (paths.size() == 1) diff --git a/dbms/src/Storages/PathPool.h b/dbms/src/Storages/PathPool.h index fd7b70a4bbd..40a3cb9a636 100644 --- a/dbms/src/Storages/PathPool.h +++ b/dbms/src/Storages/PathPool.h @@ -14,6 +14,7 @@ #pragma once +#include #include #include @@ -109,7 +110,7 @@ class PathPool FileProviderPtr file_provider; - Poco::Logger * log; + LoggerPtr log; }; class StableDiskDelegator : private boost::noncopyable @@ -453,7 +454,7 @@ class StoragePathPool FileProviderPtr file_provider; - Poco::Logger * log; + LoggerPtr log; }; } // namespace DB diff --git a/dbms/src/Storages/Transaction/RegionPersister.cpp b/dbms/src/Storages/Transaction/RegionPersister.cpp index b5b8b62b7ca..77901922e2f 100644 --- a/dbms/src/Storages/Transaction/RegionPersister.cpp +++ b/dbms/src/Storages/Transaction/RegionPersister.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -42,11 +43,11 @@ extern const char force_disable_region_persister_compatible_mode[]; void RegionPersister::drop(RegionID region_id, const RegionTaskLock &) { - if (page_storage) + if (page_writer) { DB::WriteBatch wb_v2{ns_id}; wb_v2.delPage(region_id); - page_storage->write(std::move(wb_v2), global_context.getWriteLimiter()); + page_writer->write(std::move(wb_v2), global_context.getWriteLimiter()); } else { @@ -65,7 +66,7 @@ void RegionPersister::computeRegionWriteBuffer(const Region & region, RegionCach if (unlikely(region_size > static_cast(std::numeric_limits::max()))) { LOG_FMT_WARNING( - &Poco::Logger::get("RegionPersister"), + Logger::get("RegionPersister"), "Persisting big region: {} with data info: {}, serialized size {}", region.toString(), region.dataInfo(), @@ -101,9 +102,9 @@ void RegionPersister::doPersist(RegionCacheWriteElement & region_write_buffer, c std::lock_guard lock(mutex); - if (page_storage) + if (page_reader) { - auto entry = page_storage->getEntry(ns_id, region_id, nullptr); + auto entry = page_reader->getPageEntry(region_id); if (entry.isValid() && entry.tag > applied_index) return; } @@ -121,11 +122,11 @@ void RegionPersister::doPersist(RegionCacheWriteElement & region_write_buffer, c } auto read_buf = buffer.tryGetReadBuffer(); - if (page_storage) + if (page_writer) { DB::WriteBatch wb{ns_id}; wb.putPage(region_id, applied_index, read_buf, region_size); - page_storage->write(std::move(wb), global_context.getWriteLimiter()); + page_writer->write(std::move(wb), global_context.getWriteLimiter()); } else { @@ -138,9 +139,19 @@ void RegionPersister::doPersist(RegionCacheWriteElement & region_write_buffer, c RegionPersister::RegionPersister(Context & global_context_, const RegionManager & region_manager_) : global_context(global_context_) , region_manager(region_manager_) - , log(&Poco::Logger::get("RegionPersister")) + , log(Logger::get("RegionPersister")) {} +PageStorage::Config RegionPersister::getPageStorageSettings() const +{ + if (!page_writer) + { + throw Exception("Not support for PS v1", ErrorCodes::LOGICAL_ERROR); + } + + return page_writer->getSettings(); +} + PS::V1::PageStorage::Config getV1PSConfig(const PS::V2::PageStorage::Config & config) { PS::V1::PageStorage::Config c; @@ -165,21 +176,11 @@ RegionMap RegionPersister::restore(const TiFlashRaftProxyHelper * proxy_helper, auto & path_pool = global_context.getPathPool(); auto delegator = path_pool.getPSDiskDelegatorRaft(); auto provider = global_context.getFileProvider(); - // If the GlobalStoragePool is initialized, then use v3 format - bool use_v3_format = global_context.getGlobalStoragePool() != nullptr; - if (use_v3_format) - { - mergeConfigFromSettings(global_context.getSettingsRef(), config); + auto run_mode = global_context.getPageStorageRunMode(); - LOG_FMT_INFO(log, "RegionPersister running in v3 mode"); - page_storage = std::make_unique( // - "RegionPersister", - delegator, - config, - provider); - page_storage->restore(); - } - else + switch (run_mode) + { + case PageStorageRunMode::ONLY_V2: { // If there is no PageFile with basic version binary format, use version 2 of PageStorage. auto detect_binary_version = DB::PS::V2::PageStorage::getMaxDataVersion(provider, delegator); @@ -193,13 +194,14 @@ RegionMap RegionPersister::restore(const TiFlashRaftProxyHelper * proxy_helper, mergeConfigFromSettings(global_context.getSettingsRef(), config); config.num_write_slots = 4; // extend write slots to 4 at least - LOG_FMT_INFO(log, "RegionPersister running in v2 mode"); - page_storage = std::make_unique( + auto page_storage_v2 = std::make_shared( "RegionPersister", delegator, config, provider); - page_storage->restore(); + page_storage_v2->restore(); + page_writer = std::make_shared(run_mode, page_storage_v2, /*storage_v3_*/ nullptr); + page_reader = std::make_shared(run_mode, ns_id, page_storage_v2, /*storage_v3_*/ nullptr, /*readlimiter*/ global_context.getReadLimiter()); } else { @@ -211,20 +213,79 @@ RegionMap RegionPersister::restore(const TiFlashRaftProxyHelper * proxy_helper, c, provider); } + break; } + case PageStorageRunMode::ONLY_V3: + { + mergeConfigFromSettings(global_context.getSettingsRef(), config); + + auto page_storage_v3 = std::make_shared( // + "RegionPersister", + path_pool.getPSDiskDelegatorGlobalMulti("kvstore"), + config, + provider); + page_storage_v3->restore(); + page_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, page_storage_v3); + page_reader = std::make_shared(run_mode, ns_id, /*storage_v2_*/ nullptr, page_storage_v3, global_context.getReadLimiter()); + break; + } + case PageStorageRunMode::MIX_MODE: + { + auto page_storage_v2 = std::make_shared( + "RegionPersister", + delegator, + config, + provider); + // V3 should not used getPSDiskDelegatorRaft + // Because V2 will delete all invalid(unrecognized) file when it restore + auto page_storage_v3 = std::make_shared( // + "RegionPersister", + path_pool.getPSDiskDelegatorGlobalMulti("kvstore"), + config, + provider); + + page_storage_v2->restore(); + page_storage_v3->restore(); + + if (page_storage_v2->getNumberOfPages() == 0) + { + page_storage_v2 = nullptr; + run_mode = PageStorageRunMode::ONLY_V3; + page_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, page_storage_v3); + page_reader = std::make_shared(run_mode, ns_id, /*storage_v2_*/ nullptr, page_storage_v3, global_context.getReadLimiter()); + } + else + { + page_writer = std::make_shared(run_mode, page_storage_v2, page_storage_v3); + page_reader = std::make_shared(run_mode, ns_id, page_storage_v2, page_storage_v3, global_context.getReadLimiter()); + } + break; + } + } + + LOG_FMT_INFO(log, "RegionPersister running. Current Run Mode is {}", static_cast(run_mode)); } RegionMap regions; - if (page_storage) + if (page_reader) { auto acceptor = [&](const DB::Page & page) { + // We will traverse the pages in V3 before traverse the pages in V2 When we used MIX MODE + // If we found the page_id has been restored, just skip it. + if (const auto it = regions.find(page.page_id); it != regions.end()) + { + LOG_FMT_INFO(log, "Already exist [page_id={}], skip it.", page.page_id); + return; + } + ReadBufferFromMemory buf(page.data.begin(), page.data.size()); auto region = Region::deserialize(buf, proxy_helper); if (page.page_id != region->id()) throw Exception("region id and page id not match!", ErrorCodes::LOGICAL_ERROR); + regions.emplace(page.page_id, region); }; - page_storage->traverse(acceptor, nullptr); + page_reader->traverse(acceptor); } else { @@ -243,11 +304,11 @@ RegionMap RegionPersister::restore(const TiFlashRaftProxyHelper * proxy_helper, bool RegionPersister::gc() { - if (page_storage) + if (page_writer) { PageStorage::Config config = getConfigFromSettings(global_context.getSettingsRef()); - page_storage->reloadSettings(config); - return page_storage->gc(false, nullptr, nullptr); + page_writer->reloadSettings(config); + return page_writer->gc(false, nullptr, nullptr); } else return stable_page_storage->gc(); diff --git a/dbms/src/Storages/Transaction/RegionPersister.h b/dbms/src/Storages/Transaction/RegionPersister.h index 9341ded6f76..a4f611cbdbb 100644 --- a/dbms/src/Storages/Transaction/RegionPersister.h +++ b/dbms/src/Storages/Transaction/RegionPersister.h @@ -14,11 +14,11 @@ #pragma once +#include #include #include #include #include -#include namespace DB { @@ -55,6 +55,8 @@ class RegionPersister final : private boost::noncopyable using RegionCacheWriteElement = std::tuple; static void computeRegionWriteBuffer(const Region & region, RegionCacheWriteElement & region_write_buffer); + PageStorage::Config getPageStorageSettings() const; + #ifndef DBMS_PUBLIC_GTEST private: #endif @@ -67,12 +69,14 @@ class RegionPersister final : private boost::noncopyable #endif Context & global_context; - PageStoragePtr page_storage; + PageWriterPtr page_writer; + PageReaderPtr page_reader; + std::shared_ptr stable_page_storage; NamespaceId ns_id = KVSTORE_NAMESPACE_ID; const RegionManager & region_manager; std::mutex mutex; - Poco::Logger * log; + LoggerPtr log; }; } // namespace DB diff --git a/dbms/src/Storages/Transaction/tests/gtest_region_persister.cpp b/dbms/src/Storages/Transaction/tests/gtest_region_persister.cpp index 55813edcbeb..16a35f42da1 100644 --- a/dbms/src/Storages/Transaction/tests/gtest_region_persister.cpp +++ b/dbms/src/Storages/Transaction/tests/gtest_region_persister.cpp @@ -309,7 +309,8 @@ try // Force to run in compatible mode FailPointHelper::enableFailPoint(FailPoints::force_enable_region_persister_compatible_mode); persister.restore(nullptr, config); - ASSERT_EQ(persister.page_storage, nullptr); + ASSERT_EQ(persister.page_writer, nullptr); + ASSERT_EQ(persister.page_reader, nullptr); ASSERT_NE(persister.stable_page_storage, nullptr); for (size_t i = 0; i < region_num; ++i) @@ -330,7 +331,8 @@ try RegionPersister persister(ctx, region_manager); // restore normally, should run in compatible mode. RegionMap new_regions = persister.restore(nullptr, config); - ASSERT_EQ(persister.page_storage, nullptr); + ASSERT_EQ(persister.page_writer, nullptr); + ASSERT_EQ(persister.page_reader, nullptr); ASSERT_NE(persister.stable_page_storage, nullptr); // Try to read for (size_t i = 0; i < region_num; ++i) @@ -349,7 +351,8 @@ try // Force to run in normal mode FailPointHelper::enableFailPoint(FailPoints::force_disable_region_persister_compatible_mode); RegionMap new_regions = persister.restore(nullptr, config); - ASSERT_NE(persister.page_storage, nullptr); + ASSERT_NE(persister.page_writer, nullptr); + ASSERT_NE(persister.page_reader, nullptr); ASSERT_EQ(persister.stable_page_storage, nullptr); // Try to read for (size_t i = 0; i < region_num; ++i) @@ -379,7 +382,8 @@ try RegionPersister persister(ctx, region_manager); // Restore normally, should run in normal mode. RegionMap new_regions = persister.restore(nullptr, config); - ASSERT_NE(persister.page_storage, nullptr); + ASSERT_NE(persister.page_writer, nullptr); + ASSERT_NE(persister.page_reader, nullptr); ASSERT_EQ(persister.stable_page_storage, nullptr); // Try to read for (size_t i = 0; i < region_num + region_num_under_nromal_mode; ++i) diff --git a/dbms/src/TestUtils/TiFlashTestEnv.cpp b/dbms/src/TestUtils/TiFlashTestEnv.cpp index 870db4217c4..8ec391c03d4 100644 --- a/dbms/src/TestUtils/TiFlashTestEnv.cpp +++ b/dbms/src/TestUtils/TiFlashTestEnv.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -66,6 +67,11 @@ void TiFlashTestEnv::initializeGlobalContext(Strings testdata_path, bool enable_ true, global_context->getPathCapacity(), global_context->getFileProvider()); + + global_context->setPageStorageRunMode(enable_ps_v3 ? PageStorageRunMode::ONLY_V3 : PageStorageRunMode::ONLY_V2); + global_context->initializeGlobalStoragePoolIfNeed(global_context->getPathPool()); + LOG_FMT_INFO(Logger::get("TiFlashTestEnv"), "Storage mode : {}", static_cast(global_context->getPageStorageRunMode())); + TiFlashRaftConfig raft_config; raft_config.ignore_databases = {"default", "system"}; @@ -73,9 +79,6 @@ void TiFlashTestEnv::initializeGlobalContext(Strings testdata_path, bool enable_ raft_config.disable_bg_flush = true; global_context->createTMTContext(raft_config, pingcap::ClusterConfig()); - if (global_context->initializeGlobalStoragePoolIfNeed(global_context->getPathPool(), enable_ps_v3)) - LOG_FMT_INFO(&Poco::Logger::get("TiFlashTestEnv"), "PageStorage V3 enabled."); - global_context->setDeltaIndexManager(1024 * 1024 * 100 /*100MB*/); global_context->getTMTContext().restore(); From c49258b4192cbf7f4372ecceff05c41c621830cc Mon Sep 17 00:00:00 2001 From: Lloyd-Pottiger <60744015+Lloyd-Pottiger@users.noreply.github.com> Date: Thu, 12 May 2022 11:34:34 +0800 Subject: [PATCH 013/127] Add fail point pause_after_copr_streams_acquired_once (#4833) close pingcap/tiflash#4868 --- dbms/src/Common/FailPoint.cpp | 3 ++- dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dbms/src/Common/FailPoint.cpp b/dbms/src/Common/FailPoint.cpp index 2c641858e76..602a86642fb 100644 --- a/dbms/src/Common/FailPoint.cpp +++ b/dbms/src/Common/FailPoint.cpp @@ -91,7 +91,8 @@ std::unordered_map> FailPointHelper::f M(pause_until_dt_background_delta_merge) \ M(pause_before_apply_raft_cmd) \ M(pause_before_apply_raft_snapshot) \ - M(pause_until_apply_raft_snapshot) + M(pause_until_apply_raft_snapshot) \ + M(pause_after_copr_streams_acquired_once) #define APPLY_FOR_FAILPOINTS_WITH_CHANNEL(M) \ M(pause_when_reading_from_dt_stream) \ diff --git a/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp b/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp index 879a8435e0f..f5354994b44 100644 --- a/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp +++ b/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp @@ -51,6 +51,7 @@ extern const char region_exception_after_read_from_storage_all_error[]; extern const char pause_with_alter_locks_acquired[]; extern const char force_remote_read_for_batch_cop[]; extern const char pause_after_copr_streams_acquired[]; +extern const char pause_after_copr_streams_acquired_once[]; } // namespace FailPoints namespace @@ -295,6 +296,7 @@ void DAGStorageInterpreter::executeImpl(DAGPipeline & pipeline) /// Set the limits and quota for reading data, the speed and time of the query. setQuotaAndLimitsOnTableScan(context, pipeline); FAIL_POINT_PAUSE(FailPoints::pause_after_copr_streams_acquired); + FAIL_POINT_PAUSE(FailPoints::pause_after_copr_streams_acquired_once); /// handle timezone/duration cast for local and remote table scan. executeCastAfterTableScan(remote_read_streams_start_index, pipeline); From ace1369876f0b0b0b138bcbab09b54d9ac285abd Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Thu, 12 May 2022 12:20:34 +0800 Subject: [PATCH 014/127] Interpreter: Remove Table properties query (#4586) ref pingcap/tiflash#4860 --- dbms/src/Interpreters/InterpreterFactory.cpp | 9 -- .../InterpreterShowCreateQuery.cpp | 82 -------------- .../Interpreters/InterpreterShowCreateQuery.h | 49 --------- dbms/src/Parsers/ParserQueryWithOutput.cpp | 4 - .../Parsers/ParserTablePropertiesQuery.cpp | 103 ------------------ dbms/src/Parsers/ParserTablePropertiesQuery.h | 34 ------ dbms/src/Parsers/TablePropertiesQueriesASTs.h | 27 +---- 7 files changed, 1 insertion(+), 307 deletions(-) delete mode 100644 dbms/src/Interpreters/InterpreterShowCreateQuery.cpp delete mode 100644 dbms/src/Interpreters/InterpreterShowCreateQuery.h delete mode 100644 dbms/src/Parsers/ParserTablePropertiesQuery.cpp delete mode 100644 dbms/src/Parsers/ParserTablePropertiesQuery.h diff --git a/dbms/src/Interpreters/InterpreterFactory.cpp b/dbms/src/Interpreters/InterpreterFactory.cpp index 2dc63c13657..bb0d6e31467 100644 --- a/dbms/src/Interpreters/InterpreterFactory.cpp +++ b/dbms/src/Interpreters/InterpreterFactory.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -122,14 +121,6 @@ std::unique_ptr InterpreterFactory::get(ASTPtr & query, Context & { return std::make_unique(query, context); } - else if (typeid_cast(query.get())) - { - return std::make_unique(query, context); - } - else if (typeid_cast(query.get())) - { - return std::make_unique(query, context); - } else if (typeid_cast(query.get())) { return std::make_unique(query, context); diff --git a/dbms/src/Interpreters/InterpreterShowCreateQuery.cpp b/dbms/src/Interpreters/InterpreterShowCreateQuery.cpp deleted file mode 100644 index 5f0e9074314..00000000000 --- a/dbms/src/Interpreters/InterpreterShowCreateQuery.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace DB -{ -namespace ErrorCodes -{ -extern const int SYNTAX_ERROR; -extern const int THERE_IS_NO_QUERY; -} // namespace ErrorCodes - -BlockIO InterpreterShowCreateQuery::execute() -{ - BlockIO res; - res.in = executeImpl(); - return res; -} - - -Block InterpreterShowCreateQuery::getSampleBlock() -{ - return Block{{ColumnString::create(), - std::make_shared(), - "statement"}}; -} - - -BlockInputStreamPtr InterpreterShowCreateQuery::executeImpl() -{ - const auto & ast = dynamic_cast(*query_ptr); - - if (ast.temporary && !ast.database.empty()) - throw Exception("Temporary databases are not possible.", ErrorCodes::SYNTAX_ERROR); - - ASTPtr create_query; - if (ast.temporary) - create_query = context.getCreateExternalTableQuery(ast.table); - else if (ast.table.empty()) - create_query = context.getCreateDatabaseQuery(ast.database); - else - create_query = context.getCreateTableQuery(ast.database, ast.table); - - if (!create_query && ast.temporary) - throw Exception("Unable to show the create query of " + ast.table + ". Maybe it was created by the system.", ErrorCodes::THERE_IS_NO_QUERY); - - std::stringstream stream; - formatAST(*create_query, stream, false, true); - String res = stream.str(); - - MutableColumnPtr column = ColumnString::create(); - column->insert(res); - - return std::make_shared(Block{{std::move(column), - std::make_shared(), - "statement"}}); -} - -} // namespace DB diff --git a/dbms/src/Interpreters/InterpreterShowCreateQuery.h b/dbms/src/Interpreters/InterpreterShowCreateQuery.h deleted file mode 100644 index 50323477876..00000000000 --- a/dbms/src/Interpreters/InterpreterShowCreateQuery.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - - -namespace DB -{ -class Context; -class IAST; -using ASTPtr = std::shared_ptr; - - -/** Return single row with single column "statement" of type String with text of query to CREATE specified table. - */ -class InterpreterShowCreateQuery : public IInterpreter -{ -public: - InterpreterShowCreateQuery(const ASTPtr & query_ptr_, const Context & context_) - : query_ptr(query_ptr_) - , context(context_) - {} - - BlockIO execute() override; - - static Block getSampleBlock(); - -private: - ASTPtr query_ptr; - const Context & context; - - BlockInputStreamPtr executeImpl(); -}; - - -} // namespace DB diff --git a/dbms/src/Parsers/ParserQueryWithOutput.cpp b/dbms/src/Parsers/ParserQueryWithOutput.cpp index 7a1c36e9b84..0da4f217947 100644 --- a/dbms/src/Parsers/ParserQueryWithOutput.cpp +++ b/dbms/src/Parsers/ParserQueryWithOutput.cpp @@ -21,8 +21,6 @@ #include #include #include -#include - namespace DB { @@ -30,7 +28,6 @@ bool ParserQueryWithOutput::parseImpl(Pos & pos, ASTPtr & node, Expected & expec { ParserShowTablesQuery show_tables_p; ParserSelectWithUnionQuery select_p; - ParserTablePropertiesQuery table_p; ParserDescribeTableQuery describe_table_p; ParserShowProcesslistQuery show_processlist_p; ParserCreateQuery create_p; @@ -42,7 +39,6 @@ bool ParserQueryWithOutput::parseImpl(Pos & pos, ASTPtr & node, Expected & expec bool parsed = select_p.parse(pos, query, expected) || show_tables_p.parse(pos, query, expected) - || table_p.parse(pos, query, expected) || describe_table_p.parse(pos, query, expected) || show_processlist_p.parse(pos, query, expected) || create_p.parse(pos, query, expected) diff --git a/dbms/src/Parsers/ParserTablePropertiesQuery.cpp b/dbms/src/Parsers/ParserTablePropertiesQuery.cpp deleted file mode 100644 index 6402fdb8a14..00000000000 --- a/dbms/src/Parsers/ParserTablePropertiesQuery.cpp +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include - -#include -#include - -#include - - -namespace DB -{ - - -bool ParserTablePropertiesQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) -{ - ParserKeyword s_exists("EXISTS"); - ParserKeyword s_temporary("TEMPORARY"); - ParserKeyword s_describe("DESCRIBE"); - ParserKeyword s_desc("DESC"); - ParserKeyword s_show("SHOW"); - ParserKeyword s_create("CREATE"); - ParserKeyword s_database("DATABASE"); - ParserKeyword s_table("TABLE"); - ParserToken s_dot(TokenType::Dot); - ParserIdentifier name_p; - - ASTPtr database; - ASTPtr table; - std::shared_ptr query; - - bool parse_only_database_name = false; - - if (s_exists.ignore(pos, expected)) - { - query = std::make_shared(); - } - else if (s_show.ignore(pos, expected)) - { - if (!s_create.ignore(pos, expected)) - return false; - - if (s_database.ignore(pos, expected)) - { - parse_only_database_name = true; - query = std::make_shared(); - } - else - query = std::make_shared(); - } - else - { - return false; - } - - if (parse_only_database_name) - { - if (!name_p.parse(pos, database, expected)) - return false; - } - else - { - if (s_temporary.ignore(pos, expected)) - query->temporary = true; - - s_table.ignore(pos, expected); - - if (!name_p.parse(pos, table, expected)) - return false; - - if (s_dot.ignore(pos, expected)) - { - database = table; - if (!name_p.parse(pos, table, expected)) - return false; - } - } - - if (database) - query->database = typeid_cast(*database).name; - if (table) - query->table = typeid_cast(*table).name; - - node = query; - - return true; -} - - -} diff --git a/dbms/src/Parsers/ParserTablePropertiesQuery.h b/dbms/src/Parsers/ParserTablePropertiesQuery.h deleted file mode 100644 index eb36f177396..00000000000 --- a/dbms/src/Parsers/ParserTablePropertiesQuery.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include -#include -#include - - -namespace DB -{ - -/** Query (EXISTS | SHOW CREATE) [TABLE] [db.]name [FORMAT format] - */ -class ParserTablePropertiesQuery : public IParserBase -{ -protected: - const char * getName() const override { return "EXISTS or SHOW CREATE query"; } - bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override; -}; - -} diff --git a/dbms/src/Parsers/TablePropertiesQueriesASTs.h b/dbms/src/Parsers/TablePropertiesQueriesASTs.h index 1c2502c4259..570cabf46ca 100644 --- a/dbms/src/Parsers/TablePropertiesQueriesASTs.h +++ b/dbms/src/Parsers/TablePropertiesQueriesASTs.h @@ -19,25 +19,12 @@ namespace DB { - struct ASTExistsQueryIDAndQueryNames { static constexpr auto ID = "ExistsQuery"; static constexpr auto Query = "EXISTS TABLE"; }; -struct ASTShowCreateTableQueryIDAndQueryNames -{ - static constexpr auto ID = "ShowCreateTableQuery"; - static constexpr auto Query = "SHOW CREATE TABLE"; -}; - -struct ASTShowCreateDatabaseQueryIDAndQueryNames -{ - static constexpr auto ID = "ShowCreateDatabaseQuery"; - static constexpr auto Query = "SHOW CREATE DATABASE"; -}; - struct ASTDescribeQueryExistsQueryIDAndQueryNames { static constexpr auto ID = "DescribeQuery"; @@ -45,17 +32,6 @@ struct ASTDescribeQueryExistsQueryIDAndQueryNames }; using ASTExistsQuery = ASTQueryWithTableAndOutputImpl; -using ASTShowCreateTableQuery = ASTQueryWithTableAndOutputImpl; - -class ASTShowCreateDatabaseQuery : public ASTQueryWithTableAndOutputImpl -{ -protected: - void formatQueryImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const override - { - settings.ostr << (settings.hilite ? hilite_keyword : "") << ASTShowCreateDatabaseQueryIDAndQueryNames::Query - << " " << (settings.hilite ? hilite_none : "") << backQuoteIfNeed(database); - } -}; class ASTDescribeQuery : public ASTQueryWithOutput { @@ -84,7 +60,6 @@ class ASTDescribeQuery : public ASTQueryWithOutput << "DESCRIBE TABLE " << (settings.hilite ? hilite_none : ""); table_expression->formatImpl(settings, state, frame); } - }; -} +} // namespace DB From efc93ad1b7279491b797f0ebd07eee6abd1b1874 Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Thu, 12 May 2022 15:44:35 +0800 Subject: [PATCH 015/127] Interpreter: Remove delete query (#4594) ref pingcap/tiflash#4860 --- .../Interpreters/InterpreterDeleteQuery.cpp | 128 ---------------- .../src/Interpreters/InterpreterDeleteQuery.h | 42 ------ dbms/src/Interpreters/InterpreterFactory.cpp | 7 - dbms/src/Parsers/ASTDeleteQuery.cpp | 48 ------ dbms/src/Parsers/ASTDeleteQuery.h | 58 -------- dbms/src/Parsers/ParserDeleteQuery.cpp | 140 ------------------ dbms/src/Parsers/ParserDeleteQuery.h | 35 ----- dbms/src/Parsers/ParserQuery.cpp | 3 - 8 files changed, 461 deletions(-) delete mode 100644 dbms/src/Interpreters/InterpreterDeleteQuery.cpp delete mode 100644 dbms/src/Interpreters/InterpreterDeleteQuery.h delete mode 100644 dbms/src/Parsers/ASTDeleteQuery.cpp delete mode 100644 dbms/src/Parsers/ASTDeleteQuery.h delete mode 100644 dbms/src/Parsers/ParserDeleteQuery.cpp delete mode 100644 dbms/src/Parsers/ParserDeleteQuery.h diff --git a/dbms/src/Interpreters/InterpreterDeleteQuery.cpp b/dbms/src/Interpreters/InterpreterDeleteQuery.cpp deleted file mode 100644 index 5cd909de050..00000000000 --- a/dbms/src/Interpreters/InterpreterDeleteQuery.cpp +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ProfileEvents -{ -extern const Event DeleteQuery; -} - -namespace DB -{ -namespace ErrorCodes -{ -extern const int NO_SUCH_COLUMN_IN_TABLE; -extern const int READONLY; -extern const int ILLEGAL_COLUMN; -extern const int LOGICAL_ERROR; -} // namespace ErrorCodes - -InterpreterDeleteQuery::InterpreterDeleteQuery(const ASTPtr & query_ptr_, const Context & context_, bool allow_materialized_) - : query_ptr(query_ptr_) - , context(context_) - , allow_materialized(allow_materialized_) -{ - ProfileEvents::increment(ProfileEvents::DeleteQuery); -} - -BlockIO InterpreterDeleteQuery::execute() -{ - ASTDeleteQuery & query = typeid_cast(*query_ptr); - checkAccess(query); - - StoragePtr table = context.getTable(query.database, query.table); - if (!table->supportsModification()) - throw Exception("Table engine " + table->getName() + " does not support Delete."); - - auto table_lock = table->lockStructureForShare(context.getCurrentQueryId()); - - NamesAndTypesList required_columns = table->getColumns().getAllPhysical(); - - BlockOutputStreamPtr out; - - out = std::make_shared(query.database, query.table, table, context, query_ptr, false); - - out = std::make_shared( - out, - table->getSampleBlockNoHidden(), - required_columns, - table->getColumns().defaults, - context); - - out = std::make_shared( - out, - context.getSettingsRef().min_insert_block_size_rows, - context.getSettingsRef().min_insert_block_size_bytes); - - auto out_wrapper = std::make_shared(out); - out_wrapper->setProcessListElement(context.getProcessListElement()); - out = std::move(out_wrapper); - - BlockIO res; - res.out = std::move(out); - - if (!query.where) - throw Exception("Delete query must have WHERE.", ErrorCodes::LOGICAL_ERROR); - - InterpreterSelectQuery interpreter_select(query.select, context); - - res.in = interpreter_select.execute().in; - - res.in = std::make_shared(context, res.in, res.out->getHeader(), ConvertingBlockInputStream::MatchColumnsMode::Position); - res.in = std::make_shared(res.in, res.out); - - res.out = nullptr; - - if (!allow_materialized) - { - Block in_header = res.in->getHeader(); - for (const auto & name_type : table->getColumns().materialized) - if (in_header.has(name_type.name)) - throw Exception("Cannot insert column " + name_type.name + ", because it is MATERIALIZED column.", ErrorCodes::ILLEGAL_COLUMN); - } - - return res; -} - -void InterpreterDeleteQuery::checkAccess(const ASTDeleteQuery & query) -{ - const Settings & settings = context.getSettingsRef(); - auto readonly = settings.readonly; - - if (!readonly || (query.database.empty() && context.tryGetExternalTable(query.table) && readonly >= 2)) - { - return; - } - - throw Exception("Cannot insert into table in readonly mode", ErrorCodes::READONLY); -} - -} // namespace DB diff --git a/dbms/src/Interpreters/InterpreterDeleteQuery.h b/dbms/src/Interpreters/InterpreterDeleteQuery.h deleted file mode 100644 index e912f2d6afa..00000000000 --- a/dbms/src/Interpreters/InterpreterDeleteQuery.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include -#include -#include -#include -#include - -namespace DB -{ -/** Interprets the DELETE query. - */ -class InterpreterDeleteQuery : public IInterpreter -{ -public: - InterpreterDeleteQuery(const ASTPtr & query_ptr_, const Context & context_, bool allow_materialized_ = false); - - BlockIO execute() override; - -private: - void checkAccess(const ASTDeleteQuery & query); - - ASTPtr query_ptr; - const Context & context; - bool allow_materialized; -}; - -} // namespace DB diff --git a/dbms/src/Interpreters/InterpreterFactory.cpp b/dbms/src/Interpreters/InterpreterFactory.cpp index bb0d6e31467..0e6e6fbdae3 100644 --- a/dbms/src/Interpreters/InterpreterFactory.cpp +++ b/dbms/src/Interpreters/InterpreterFactory.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -34,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -134,11 +132,6 @@ std::unique_ptr InterpreterFactory::get(ASTPtr & query, Context & throwIfReadOnly(context); return std::make_unique(query, context); } - else if (typeid_cast(query.get())) - { - bool allow_materialized = static_cast(context.getSettingsRef().insert_allow_materialized_columns); - return std::make_unique(query, context, allow_materialized); - } else if (typeid_cast(query.get())) { return std::make_unique(query, context); diff --git a/dbms/src/Parsers/ASTDeleteQuery.cpp b/dbms/src/Parsers/ASTDeleteQuery.cpp deleted file mode 100644 index 9b3e2c0ff1b..00000000000 --- a/dbms/src/Parsers/ASTDeleteQuery.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include - - -namespace DB -{ - -void ASTDeleteQuery::formatImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const -{ - frame.need_parens = false; - std::string indent_str = settings.one_line ? "" : std::string(4 * frame.indent, ' '); - - settings.ostr - << (settings.hilite ? hilite_keyword : "") - << "DELETE FROM " - << (settings.hilite ? hilite_none : "") - << (!database.empty() ? backQuoteIfNeed(database) + "." : "") - << backQuoteIfNeed(table); - - if (partition_expression_list) - { - settings.ostr << (settings.hilite ? hilite_keyword : "") << settings.nl_or_ws << - indent_str << "PARTITION " << (settings.hilite ? hilite_none : ""); - partition_expression_list->formatImpl(settings, state, frame); - } - - if (where) - { - settings.ostr << " WHERE "; - where->formatImpl(settings, state, frame); - } -} - -} diff --git a/dbms/src/Parsers/ASTDeleteQuery.h b/dbms/src/Parsers/ASTDeleteQuery.h deleted file mode 100644 index cfd1cf227be..00000000000 --- a/dbms/src/Parsers/ASTDeleteQuery.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - - -namespace DB -{ - - -/** DELETE query - */ -class ASTDeleteQuery : public IAST -{ -public: - String database; - String table; - ASTPtr partition_expression_list; - ASTPtr where; - - // Just for execute. - ASTPtr select; - - /** Get the text that identifies this element. */ - String getID() const override { return "DeleteQuery_" + database + "_" + table; }; - - ASTPtr clone() const override - { - auto res = std::make_shared(*this); - res->children.clear(); - - if (where) - { - res->where = where->clone(); - res->children.push_back(res->where); - } - - return res; - } - -protected: - void formatImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override; -}; - -} diff --git a/dbms/src/Parsers/ParserDeleteQuery.cpp b/dbms/src/Parsers/ParserDeleteQuery.cpp deleted file mode 100644 index 03232536d73..00000000000 --- a/dbms/src/Parsers/ParserDeleteQuery.cpp +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - - -namespace DB -{ - -namespace ErrorCodes -{ - extern const int SYNTAX_ERROR; -} - - -bool ParserDeleteQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) -{ - ParserKeyword s_delete_from("DELETE FROM"); - ParserToken s_dot(TokenType::Dot); - ParserKeyword s_partition("PARTITION"); - ParserKeyword s_where("WHERE"); - ParserIdentifier name_p; - - ASTPtr database; - ASTPtr table; - ASTPtr where; - - ParserExpressionWithOptionalAlias exp_elem(false); - - if (!s_delete_from.ignore(pos, expected)) - return false; - - if (!name_p.parse(pos, table, expected)) - return false; - - if (s_dot.ignore(pos, expected)) - { - database = table; - if (!name_p.parse(pos, table, expected)) - return false; - } - - std::shared_ptr query = std::make_shared(); - node = query; - - /// PARTITION p or PARTITION (p1, p2, ...) - if (s_partition.ignore(pos, expected)) - { - if (!ParserPartition().parse(pos, query->partition_expression_list, expected)) - return false; - } - - if (!s_where.ignore(pos, expected)) - return false; - - if (!exp_elem.parse(pos, where, expected)) - return false; - - if (database) - query->database = typeid_cast(*database).name; - - query->table = typeid_cast(*table).name; - - // TODO: Support syntax without 'where' - - if (where) - { - query->where = where; - query->children.push_back(where); - - std::shared_ptr select_query = std::make_shared(); - - std::shared_ptr asterisk = std::make_shared(); - std::shared_ptr table_columns = std::make_shared(); - table_columns->children.push_back(asterisk); - - select_query->select_expression_list = table_columns; - select_query->children.push_back(table_columns); - - auto table_expr = std::make_shared(); - table_expr->database_and_table_name = - std::make_shared( - query->database.size() ? query->database + '.' + query->table : query->table, - ASTIdentifier::Table); - if(!query->database.empty()) - { - table_expr->database_and_table_name->children.emplace_back(std::make_shared(query->database, ASTIdentifier::Database)); - table_expr->database_and_table_name->children.emplace_back(std::make_shared(query->table, ASTIdentifier::Table)); - } - - auto table_element = std::make_shared(); - table_element->table_expression = table_expr; - table_element->children.emplace_back(table_expr); - - auto table_list = std::make_shared(); - table_list->children.emplace_back(table_element); - - select_query->tables = table_list; - select_query->children.push_back(table_list); - - select_query->where_expression = query->where; - select_query->children.push_back(query->where); - select_query->partition_expression_list = query->partition_expression_list; - - query->select = select_query; - query->children.push_back(select_query); - } - - return true; -} - - -} diff --git a/dbms/src/Parsers/ParserDeleteQuery.h b/dbms/src/Parsers/ParserDeleteQuery.h deleted file mode 100644 index d930b3b652d..00000000000 --- a/dbms/src/Parsers/ParserDeleteQuery.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - - -namespace DB -{ - - -/** Cases: - * - * DELETE FROM [db.]table WHERE ... - */ -class ParserDeleteQuery : public IParserBase -{ -protected: - const char * getName() const { return "DELETE query"; } - bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected); -}; - -} diff --git a/dbms/src/Parsers/ParserQuery.cpp b/dbms/src/Parsers/ParserQuery.cpp index 7325a3850b3..8394cf0c722 100644 --- a/dbms/src/Parsers/ParserQuery.cpp +++ b/dbms/src/Parsers/ParserQuery.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -34,7 +33,6 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) ParserInsertQuery insert_p(end); ParserUseQuery use_p; ParserSetQuery set_p; - ParserDeleteQuery delete_p; ParserDBGInvokeQuery dbginvoke_p; ParserSystemQuery system_p; ParserManageQuery manage_p; @@ -43,7 +41,6 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) || insert_p.parse(pos, node, expected) || use_p.parse(pos, node, expected) || set_p.parse(pos, node, expected) - || delete_p.parse(pos, node, expected) || dbginvoke_p.parse(pos, node, expected) || system_p.parse(pos, node, expected) || manage_p.parse(pos, node, expected); From cdaea0551b6b90e8dffba7e9ea4fff4d10411659 Mon Sep 17 00:00:00 2001 From: hehechen Date: Fri, 13 May 2022 07:42:35 +0800 Subject: [PATCH 016/127] restore global storage pool in test cases (#4878) close pingcap/tiflash#4872 --- dbms/src/Interpreters/Context.cpp | 6 ++---- .../Page/V3/tests/gtest_page_storage_mix_mode.cpp | 9 ++++----- dbms/src/TestUtils/TiFlashTestEnv.cpp | 1 + 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/dbms/src/Interpreters/Context.cpp b/dbms/src/Interpreters/Context.cpp index 25dffe263fd..a2088483c20 100644 --- a/dbms/src/Interpreters/Context.cpp +++ b/dbms/src/Interpreters/Context.cpp @@ -1649,10 +1649,8 @@ bool Context::initializeGlobalStoragePoolIfNeed(const PathPool & path_pool) auto lock = getLock(); if (shared->global_storage_pool) { - // Can't init GlobalStoragePool twice. - // Because we won't remove the gc task in BackGroundPool - // Also won't remove it from ~GlobalStoragePool() - throw Exception("GlobalStoragePool has already been initialized.", ErrorCodes::LOGICAL_ERROR); + // GlobalStoragePool may be initialized many times in some test cases for restore. + LOG_WARNING(shared->log, "GlobalStoragePool has already been initialized."); } if (shared->storage_run_mode == PageStorageRunMode::MIX_MODE || shared->storage_run_mode == PageStorageRunMode::ONLY_V3) diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp index d3fdafe57e8..2a5714de027 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp @@ -43,17 +43,16 @@ class PageStorageMixedTest : public DB::base::TiFlashStorageTestBasic storage_path_pool_v3 = std::make_unique(Strings{path}, Strings{path}, Strings{}, std::make_shared(0, paths, caps, Strings{}, caps), global_context.getFileProvider(), true); global_context.setPageStorageRunMode(PageStorageRunMode::MIX_MODE); - if (!global_context.getGlobalStoragePool()) - global_context.initializeGlobalStoragePoolIfNeed(*storage_path_pool_v3); } void SetUp() override { + auto & global_context = DB::tests::TiFlashTestEnv::getGlobalContext(); + global_context.setPageStorageRunMode(PageStorageRunMode::MIX_MODE); TiFlashStorageTestBasic::SetUp(); const auto & path = getTemporaryPath(); createIfNotExist(path); - auto & global_context = DB::tests::TiFlashTestEnv::getGlobalContext(); std::vector caps = {}; Strings paths = {path}; @@ -75,7 +74,7 @@ class PageStorageMixedTest : public DB::base::TiFlashStorageTestBasic PageStorageRunMode reloadMixedStoragePool() { - DB::tests::TiFlashTestEnv::getContext().setPageStorageRunMode(PageStorageRunMode::MIX_MODE); + db_context->setPageStorageRunMode(PageStorageRunMode::MIX_MODE); PageStorageRunMode run_mode = storage_pool_mix->restore(); page_writer_mix = storage_pool_mix->logWriter(); page_reader_mix = storage_pool_mix->logReader(); @@ -84,7 +83,7 @@ class PageStorageMixedTest : public DB::base::TiFlashStorageTestBasic void reloadV2StoragePool() { - DB::tests::TiFlashTestEnv::getContext().setPageStorageRunMode(PageStorageRunMode::ONLY_V2); + db_context->setPageStorageRunMode(PageStorageRunMode::ONLY_V2); storage_pool_v2->restore(); page_writer_v2 = storage_pool_v2->logWriter(); page_reader_v2 = storage_pool_v2->logReader(); diff --git a/dbms/src/TestUtils/TiFlashTestEnv.cpp b/dbms/src/TestUtils/TiFlashTestEnv.cpp index 8ec391c03d4..7e8bd0da319 100644 --- a/dbms/src/TestUtils/TiFlashTestEnv.cpp +++ b/dbms/src/TestUtils/TiFlashTestEnv.cpp @@ -95,6 +95,7 @@ Context TiFlashTestEnv::getContext(const DB::Settings & settings, Strings testda context.setPath(root_path); auto paths = getPathPool(testdata_path); context.setPathPool(paths.first, paths.second, Strings{}, true, context.getPathCapacity(), context.getFileProvider()); + global_context->initializeGlobalStoragePoolIfNeed(context.getPathPool()); context.getSettingsRef() = settings; return context; } From 2dc22fd2c532ae549ab72a450765c3f433d64f04 Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Fri, 13 May 2022 08:24:35 +0800 Subject: [PATCH 017/127] Interpreter: Remove system query (#4590) ref pingcap/tiflash#4860 --- dbms/src/Interpreters/InterpreterFactory.cpp | 8 +- .../Interpreters/InterpreterSystemQuery.cpp | 131 ------------------ .../src/Interpreters/InterpreterSystemQuery.h | 39 ------ dbms/src/Parsers/ASTSystemQuery.cpp | 84 ----------- dbms/src/Parsers/ASTSystemQuery.h | 67 --------- dbms/src/Parsers/ParserQuery.cpp | 3 - dbms/src/Parsers/ParserSystemQuery.cpp | 70 ---------- dbms/src/Parsers/ParserSystemQuery.h | 30 ---- 8 files changed, 1 insertion(+), 431 deletions(-) delete mode 100644 dbms/src/Interpreters/InterpreterSystemQuery.cpp delete mode 100644 dbms/src/Interpreters/InterpreterSystemQuery.h delete mode 100644 dbms/src/Parsers/ASTSystemQuery.cpp delete mode 100644 dbms/src/Parsers/ASTSystemQuery.h delete mode 100644 dbms/src/Parsers/ParserSystemQuery.cpp delete mode 100644 dbms/src/Parsers/ParserSystemQuery.h diff --git a/dbms/src/Interpreters/InterpreterFactory.cpp b/dbms/src/Interpreters/InterpreterFactory.cpp index 0e6e6fbdae3..8240926f1a3 100644 --- a/dbms/src/Interpreters/InterpreterFactory.cpp +++ b/dbms/src/Interpreters/InterpreterFactory.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -42,7 +41,6 @@ #include #include #include -#include #include #include @@ -136,11 +134,7 @@ std::unique_ptr InterpreterFactory::get(ASTPtr & query, Context & { return std::make_unique(query, context); } - else if (typeid_cast(query.get())) - { - throwIfReadOnly(context); - return std::make_unique(query, context); - } + else if (typeid_cast(query.get())) { throwIfReadOnly(context); diff --git a/dbms/src/Interpreters/InterpreterSystemQuery.cpp b/dbms/src/Interpreters/InterpreterSystemQuery.cpp deleted file mode 100644 index 32437fcc3fd..00000000000 --- a/dbms/src/Interpreters/InterpreterSystemQuery.cpp +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include -#include -#include - -#include - - -namespace DB -{ -namespace ErrorCodes -{ -extern const int BAD_ARGUMENTS; -extern const int CANNOT_KILL; -extern const int NOT_IMPLEMENTED; -} // namespace ErrorCodes - - -namespace -{ -ExecutionStatus getOverallExecutionStatusOfCommands() -{ - return ExecutionStatus(0); -} - -/// Consequently execute all commands and genreates final exception message for failed commands -template -ExecutionStatus getOverallExecutionStatusOfCommands(Callable && command, Callables &&... commands) -{ - ExecutionStatus status_head(0); - try - { - command(); - } - catch (...) - { - status_head = ExecutionStatus::fromCurrentException(); - } - - ExecutionStatus status_tail = getOverallExecutionStatusOfCommands(std::forward(commands)...); - - auto res_status = status_head.code != 0 ? status_head.code : status_tail.code; - auto res_message = status_head.message + (status_tail.message.empty() ? "" : ("\n" + status_tail.message)); - - return ExecutionStatus(res_status, res_message); -} - -} // namespace - - -InterpreterSystemQuery::InterpreterSystemQuery(const ASTPtr & query_ptr_, Context & context_) - : query_ptr(query_ptr_) - , context(context_) -{} - - -BlockIO InterpreterSystemQuery::execute() -{ - auto & query = typeid_cast(*query_ptr); - - using Type = ASTSystemQuery::Type; - - switch (query.type) - { - case Type::SHUTDOWN: - if (kill(0, SIGTERM)) - throwFromErrno("System call kill(0, SIGTERM) failed", ErrorCodes::CANNOT_KILL); - break; - case Type::KILL: - if (kill(0, SIGKILL)) - throwFromErrno("System call kill(0, SIGKILL) failed", ErrorCodes::CANNOT_KILL); - break; - case Type::DROP_DNS_CACHE: - DNSCache::instance().drop(); - break; - case Type::DROP_MARK_CACHE: - context.dropMarkCache(); - break; - case Type::DROP_UNCOMPRESSED_CACHE: - context.dropUncompressedCache(); - break; - case Type::RELOAD_DICTIONARY: - context.getExternalDictionaries().reloadDictionary(query.target_dictionary); - break; - case Type::RELOAD_DICTIONARIES: - { - auto status = getOverallExecutionStatusOfCommands( - [&] { context.getExternalDictionaries().reload(); }, - [&] { context.getEmbeddedDictionaries().reload(); }); - if (status.code != 0) - throw Exception(status.message, status.code); - break; - } - case Type::RELOAD_CONFIG: - context.reloadConfig(); - break; - case Type::STOP_LISTEN_QUERIES: - case Type::START_LISTEN_QUERIES: - case Type::RESTART_REPLICAS: - case Type::SYNC_REPLICA: - case Type::STOP_MERGES: - case Type::START_MERGES: - case Type::STOP_REPLICATION_QUEUES: - case Type::START_REPLICATION_QUEUES: - throw Exception(String(ASTSystemQuery::typeToString(query.type)) + " is not supported yet", ErrorCodes::NOT_IMPLEMENTED); - default: - throw Exception("Unknown type of SYSTEM query", ErrorCodes::BAD_ARGUMENTS); - } - - return BlockIO(); -} - - -} // namespace DB diff --git a/dbms/src/Interpreters/InterpreterSystemQuery.h b/dbms/src/Interpreters/InterpreterSystemQuery.h deleted file mode 100644 index 2f5c30fc480..00000000000 --- a/dbms/src/Interpreters/InterpreterSystemQuery.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once -#include - - -namespace DB -{ -class Context; -class IAST; -using ASTPtr = std::shared_ptr; - - -class InterpreterSystemQuery : public IInterpreter -{ -public: - InterpreterSystemQuery(const ASTPtr & query_ptr_, Context & context_); - - BlockIO execute() override; - -private: - ASTPtr query_ptr; - Context & context; -}; - - -} // namespace DB diff --git a/dbms/src/Parsers/ASTSystemQuery.cpp b/dbms/src/Parsers/ASTSystemQuery.cpp deleted file mode 100644 index b1a2dbbc752..00000000000 --- a/dbms/src/Parsers/ASTSystemQuery.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include - - -namespace DB -{ - - -namespace ErrorCodes -{ - extern const int BAD_TYPE_OF_FIELD; - extern const int NOT_IMPLEMENTED; -} - - -const char * ASTSystemQuery::typeToString(Type type) -{ - switch (type) - { - case Type::SHUTDOWN: - return "SHUTDOWN"; - case Type::KILL: - return "KILL"; - case Type::DROP_DNS_CACHE: - return "DROP DNS CACHE"; - case Type::DROP_MARK_CACHE: - return "DROP MARK CACHE"; - case Type::DROP_UNCOMPRESSED_CACHE: - return "DROP UNCOMPRESSED CACHE"; - case Type::STOP_LISTEN_QUERIES: - return "STOP LISTEN QUERIES"; - case Type::START_LISTEN_QUERIES: - return "START LISTEN QUERIES"; - case Type::RESTART_REPLICAS: - return "RESTART REPLICAS"; - case Type::SYNC_REPLICA: - return "SYNC REPLICA"; - case Type::RELOAD_DICTIONARY: - return "RELOAD DICTIONARY"; - case Type::RELOAD_DICTIONARIES: - return "RELOAD DICTIONARIES"; - case Type::RELOAD_CONFIG: - return "RELOAD CONFIG"; - case Type::STOP_MERGES: - return "STOP MERGES"; - case Type::START_MERGES: - return "START MERGES"; - case Type::STOP_REPLICATION_QUEUES: - return "STOP REPLICATION QUEUES"; - case Type::START_REPLICATION_QUEUES: - return "START REPLICATION QUEUES"; - default: - throw Exception("Unknown SYSTEM query command", ErrorCodes::BAD_TYPE_OF_FIELD); - } -} - - -void ASTSystemQuery::formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const -{ - settings.ostr << (settings.hilite ? hilite_keyword : "") << "SYSTEM " << (settings.hilite ? hilite_none : ""); - settings.ostr << typeToString(type); - - if (type == Type::RELOAD_DICTIONARY) - settings.ostr << " " << backQuoteIfNeed(target_dictionary); - else if (type == Type::SYNC_REPLICA) - throw Exception("SYNC_REPLICA isn't supported yet", ErrorCodes::NOT_IMPLEMENTED); -} - - -} diff --git a/dbms/src/Parsers/ASTSystemQuery.h b/dbms/src/Parsers/ASTSystemQuery.h deleted file mode 100644 index 06841542114..00000000000 --- a/dbms/src/Parsers/ASTSystemQuery.h +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - - -namespace DB -{ - -class ASTSystemQuery : public IAST -{ -public: - - enum class Type - { - UNKNOWN, - SHUTDOWN, - KILL, - DROP_DNS_CACHE, - DROP_MARK_CACHE, - DROP_UNCOMPRESSED_CACHE, - STOP_LISTEN_QUERIES, - START_LISTEN_QUERIES, - RESTART_REPLICAS, - SYNC_REPLICA, - RELOAD_DICTIONARY, - RELOAD_DICTIONARIES, - RELOAD_CONFIG, - STOP_MERGES, - START_MERGES, - STOP_REPLICATION_QUEUES, - START_REPLICATION_QUEUES, - END - }; - - static const char * typeToString(Type type); - - Type type = Type::UNKNOWN; - - String target_dictionary; - //String target_replica_database; - //String target_replica_table; - - String getID() const override { return "SYSTEM query"; }; - - ASTPtr clone() const override { return std::make_shared(*this); } - -protected: - - void formatImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override; -}; - - -} diff --git a/dbms/src/Parsers/ParserQuery.cpp b/dbms/src/Parsers/ParserQuery.cpp index 8394cf0c722..8c506763315 100644 --- a/dbms/src/Parsers/ParserQuery.cpp +++ b/dbms/src/Parsers/ParserQuery.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include namespace DB @@ -34,7 +33,6 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) ParserUseQuery use_p; ParserSetQuery set_p; ParserDBGInvokeQuery dbginvoke_p; - ParserSystemQuery system_p; ParserManageQuery manage_p; bool res = query_with_output_p.parse(pos, node, expected) @@ -42,7 +40,6 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) || use_p.parse(pos, node, expected) || set_p.parse(pos, node, expected) || dbginvoke_p.parse(pos, node, expected) - || system_p.parse(pos, node, expected) || manage_p.parse(pos, node, expected); return res; diff --git a/dbms/src/Parsers/ParserSystemQuery.cpp b/dbms/src/Parsers/ParserSystemQuery.cpp deleted file mode 100644 index eca26bd8122..00000000000 --- a/dbms/src/Parsers/ParserSystemQuery.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include -#include - - -namespace ErrorCodes -{ - extern const int NOT_IMPLEMENTED; -} - - -namespace DB -{ - - -bool ParserSystemQuery::parseImpl(IParser::Pos & pos, ASTPtr & node, Expected & expected) -{ - if (!ParserKeyword{"SYSTEM"}.ignore(pos)) - return false; - - using Type = ASTSystemQuery::Type; - - auto res = std::make_shared(); - - bool found = false; - for (int i = static_cast(Type::UNKNOWN) + 1; i < static_cast(Type::END); ++i) - { - Type t = static_cast(i); - if (ParserKeyword{ASTSystemQuery::typeToString(t)}.ignore(pos)) - { - res->type = t; - found = true; - } - } - - if (!found) - return false; - - if (res->type == Type::RELOAD_DICTIONARY) - { - if (!parseIdentifierOrStringLiteral(pos, expected, res->target_dictionary)) - return false; - } - else if (res->type == Type::SYNC_REPLICA) - { - throw Exception("SYNC REPLICA is not supported yet", ErrorCodes::NOT_IMPLEMENTED); - } - - node = std::move(res); - return true; -} - -} diff --git a/dbms/src/Parsers/ParserSystemQuery.h b/dbms/src/Parsers/ParserSystemQuery.h deleted file mode 100644 index 3e4539b8600..00000000000 --- a/dbms/src/Parsers/ParserSystemQuery.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once -#include - - -namespace DB -{ - - -class ParserSystemQuery : public IParserBase -{ -protected: - const char * getName() const override { return "SYSTEM query"; } - bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override; -}; - -} From 8bb062270bc3e8d8eb98d68254ed0a70ebb672b2 Mon Sep 17 00:00:00 2001 From: Wenxuan Date: Fri, 13 May 2022 13:50:35 +0800 Subject: [PATCH 018/127] misc: Add comments (#4863) ref pingcap/tiflash#4862 --- .../DeltaMerge/ColumnFile/ColumnFile.h | 3 +- .../DeltaMerge/ColumnFile/ColumnFileTiny.h | 9 ++--- .../DeltaMerge/Delta/DeltaValueSpace.h | 4 ++- .../Storages/DeltaMerge/Delta/MemTableSet.cpp | 8 +++-- .../Storages/DeltaMerge/Delta/MemTableSet.h | 8 ++++- .../Storages/DeltaMerge/DeltaMergeHelpers.h | 1 + .../Storages/DeltaMerge/DeltaMergeStore.cpp | 26 +++++++++++--- .../src/Storages/DeltaMerge/DeltaMergeStore.h | 28 +++++++++++++-- dbms/src/Storages/DeltaMerge/RowKeyRange.h | 26 +++++++++++--- dbms/src/Storages/DeltaMerge/Segment.h | 27 +++++++++++++-- dbms/src/Storages/IManageableStorage.h | 34 +++++++++---------- dbms/src/Storages/StorageDeltaMerge.cpp | 5 +++ dbms/src/Storages/StorageDeltaMerge.h | 4 +++ 13 files changed, 142 insertions(+), 41 deletions(-) diff --git a/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFile.h b/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFile.h index 00731068858..21b424dffce 100644 --- a/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFile.h +++ b/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFile.h @@ -114,7 +114,8 @@ class ColumnFile virtual ColumnFileReaderPtr getReader(const DMContext & context, const StorageSnapshotPtr & storage_snap, const ColumnDefinesPtr & col_defs) const = 0; - /// only ColumnInMemoryFile can be appendable + /// Note: Only ColumnFileInMemory can be appendable. Other ColumnFiles (i.e. ColumnFilePersisted) have + /// been persisted in the disk and their data will be immutable. virtual bool isAppendable() const { return false; } virtual void disableAppend() {} virtual bool append(DMContext & /*dm_context*/, const Block & /*data*/, size_t /*offset*/, size_t /*limit*/, size_t /*data_bytes*/) diff --git a/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileTiny.h b/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileTiny.h index 5c43bed8c28..efd4705c0f5 100644 --- a/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileTiny.h +++ b/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileTiny.h @@ -27,7 +27,6 @@ using ColumnTinyFilePtr = std::shared_ptr; /// It may be created in two ways: /// 1. created directly when writing to storage if the data is large enough /// 2. created when flushed `ColumnFileInMemory` to disk -/// And it may have cache data if the column file is small enough(The details are in the flush process). class ColumnFileTiny : public ColumnFilePersisted { friend class ColumnFileTinyReader; @@ -38,13 +37,15 @@ class ColumnFileTiny : public ColumnFilePersisted UInt64 rows = 0; UInt64 bytes = 0; - // The id of data page which stores the data of this pack. + /// The id of data page which stores the data of this pack. PageId data_page_id; /// The members below are not serialized. - // The cache data in memory. + + /// The cache data in memory. + /// Currently this field is unused. CachePtr cache; - // Used to map column id to column instance in a Block. + /// Used to map column id to column instance in a Block. ColIdToOffset colid_to_offset; private: diff --git a/dbms/src/Storages/DeltaMerge/Delta/DeltaValueSpace.h b/dbms/src/Storages/DeltaMerge/Delta/DeltaValueSpace.h index 4cde8b3e121..8f14682caa8 100644 --- a/dbms/src/Storages/DeltaMerge/Delta/DeltaValueSpace.h +++ b/dbms/src/Storages/DeltaMerge/Delta/DeltaValueSpace.h @@ -234,7 +234,9 @@ class DeltaValueSpace /// Flush the data of column files which haven't write to disk yet, and also save the metadata of column files. bool flush(DMContext & context); - /// Compacts fragment column files into bigger one, to save some IOPS during reading. + /// Compact fragment column files in the delta layer into bigger column files, to save some IOPS during reading. + /// It does not merge the delta into stable layer. + /// a.k.a. minor compaction. bool compact(DMContext & context); /// Create a constant snapshot for read. diff --git a/dbms/src/Storages/DeltaMerge/Delta/MemTableSet.cpp b/dbms/src/Storages/DeltaMerge/Delta/MemTableSet.cpp index 042f1bacf6c..bab8f352cad 100644 --- a/dbms/src/Storages/DeltaMerge/Delta/MemTableSet.cpp +++ b/dbms/src/Storages/DeltaMerge/Delta/MemTableSet.cpp @@ -33,7 +33,7 @@ namespace DM { void MemTableSet::appendColumnFileInner(const ColumnFilePtr & column_file) { - // If this column file's schema is identical to last_schema, then use the last_schema instance, + // If this column file's schema is identical to last_schema, then use the last_schema instance (instead of the one in `column_file`), // so that we don't have to serialize my_schema instance. if (auto * m_file = column_file->tryToInMemoryFile(); m_file) { @@ -54,6 +54,8 @@ void MemTableSet::appendColumnFileInner(const ColumnFilePtr & column_file) if (!column_files.empty()) { + // As we are now appending a new column file (which can be used for new appends), + // let's simply mark the last column file as not appendable. auto & last_column_file = column_files.back(); if (last_column_file->isAppendable()) last_column_file->disableAppend(); @@ -212,7 +214,7 @@ ColumnFileFlushTaskPtr MemTableSet::buildFlushTask(DMContext & context, size_t r if (column_files.empty()) return nullptr; - // make the last column file not appendable + // Mark the last ColumnFile not appendable, so that `appendToCache` will not reuse it and we will be safe to flush it to disk. if (column_files.back()->isAppendable()) column_files.back()->disableAppend(); @@ -224,6 +226,8 @@ ColumnFileFlushTaskPtr MemTableSet::buildFlushTask(DMContext & context, size_t r auto & task = flush_task->addColumnFile(column_file); if (auto * m_file = column_file->tryToInMemoryFile(); m_file) { + // If the ColumnFile is not yet persisted in the disk, it will contain block data. + // In this case, let's write the block data in the flush process as well. task.rows_offset = cur_rows_offset; task.deletes_offset = cur_deletes_offset; task.block_data = m_file->readDataForFlush(); diff --git a/dbms/src/Storages/DeltaMerge/Delta/MemTableSet.h b/dbms/src/Storages/DeltaMerge/Delta/MemTableSet.h index 295b358090e..4f0bc4f857e 100644 --- a/dbms/src/Storages/DeltaMerge/Delta/MemTableSet.h +++ b/dbms/src/Storages/DeltaMerge/Delta/MemTableSet.h @@ -88,8 +88,14 @@ class MemTableSet : public std::enable_shared_from_this /// The following methods returning false means this operation failed, caused by other threads could have done /// some updates on this instance. E.g. this instance have been abandoned. /// Caller should try again from the beginning. + + /// Append a ColumnFile into this MemTableSet. The ColumnFile may be flushed later. + /// Note that some ColumnFiles may not contain block data, but only a reference to the block data stored in disk. + /// See different ColumnFile implementations for details. void appendColumnFile(const ColumnFilePtr & column_file); + /// Append the block data into a ColumnFileInMemory (may be reused). + /// The ColumnFileInMemory will be stored in this MemTableSet and flushed later. void appendToCache(DMContext & dm_context, const Block & block, size_t offset, size_t limit); void appendDeleteRange(const RowKeyRange & delete_range); @@ -99,7 +105,7 @@ class MemTableSet : public std::enable_shared_from_this /// Create a constant snapshot for read. ColumnFileSetSnapshotPtr createSnapshot(const StorageSnapshotPtr & storage_snap); - /// Build a flush task which will try to flush all column files in MemTableSet now + /// Build a flush task which will try to flush all column files in this MemTableSet at this moment. ColumnFileFlushTaskPtr buildFlushTask(DMContext & context, size_t rows_offset, size_t deletes_offset, size_t flush_version); void removeColumnFilesInFlushTask(const ColumnFileFlushTask & flush_task); diff --git a/dbms/src/Storages/DeltaMerge/DeltaMergeHelpers.h b/dbms/src/Storages/DeltaMerge/DeltaMergeHelpers.h index dd9fa6e7d1d..77335e6d9f0 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaMergeHelpers.h +++ b/dbms/src/Storages/DeltaMerge/DeltaMergeHelpers.h @@ -223,6 +223,7 @@ inline bool hasColumn(const ColumnDefines & columns, const ColId & col_id) return false; } +/// Checks whether two blocks have the same schema. template inline bool isSameSchema(const Block & a, const Block & b) { diff --git a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp index 6b4339938af..2d53f85f516 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp +++ b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp @@ -578,7 +578,7 @@ void DeltaMergeStore::write(const Context & db_context, const DB::Settings & db_ const auto bytes = block.bytes(); { - // Sort by handle & version in ascending order. + // Sort the block by handle & version in ascending order. SortDescription sort; sort.emplace_back(EXTRA_HANDLE_COLUMN_NAME, 1, 0); sort.emplace_back(VERSION_COLUMN_NAME, 1, 0); @@ -594,6 +594,7 @@ void DeltaMergeStore::write(const Context & db_context, const DB::Settings & db_ const auto handle_column = block.getByName(EXTRA_HANDLE_COLUMN_NAME).column; auto rowkey_column = RowKeyColumnContainer(handle_column, is_common_handle); + // Write block by segments while (offset != rows) { RowKeyValueRef start_key = rowkey_column.getRowKeyValue(offset); @@ -604,6 +605,7 @@ void DeltaMergeStore::write(const Context & db_context, const DB::Settings & db_ // Keep trying until succeeded. while (true) { + // Find the segment according to current start_key SegmentPtr segment; { std::shared_lock lock(read_write_mutex); @@ -618,12 +620,16 @@ void DeltaMergeStore::write(const Context & db_context, const DB::Settings & db_ } FAIL_POINT_PAUSE(FailPoints::pause_when_writing_to_dt_store); + + // Do force merge or stop write if necessary. waitForWrite(dm_context, segment); if (segment->hasAbandoned()) continue; const auto & rowkey_range = segment->getRowKeyRange(); + // The [offset, rows - offset] can be exceeding the Segment's rowkey_range. Cut the range + // to fit the segment. auto [cur_offset, cur_limit] = rowkey_range.getPosRange(handle_column, offset, rows - offset); if (unlikely(cur_offset != offset)) throw Exception("cur_offset does not equal to offset", ErrorCodes::LOGICAL_ERROR); @@ -632,8 +638,8 @@ void DeltaMergeStore::write(const Context & db_context, const DB::Settings & db_ auto alloc_bytes = block.bytes(offset, limit); bool is_small = limit < dm_context->delta_cache_limit_rows / 4 && alloc_bytes < dm_context->delta_cache_limit_bytes / 4; - // Small column files are appended to Delta Cache, then flushed later. - // While large column files are directly written to PageStorage. + // For small column files, data is appended to MemTableSet, then flushed later. + // For large column files, data is directly written to PageStorage, while the ColumnFile entry is appended to MemTableSet. if (is_small) { if (segment->writeToCache(*dm_context, block, offset, limit)) @@ -651,6 +657,8 @@ void DeltaMergeStore::write(const Context & db_context, const DB::Settings & db_ wbs.rollbackWrittenLogAndData(); wbs.clear(); + // In this case we will construct a ColumnFile that does not contain block data in the memory. + // The block data has been written to PageStorage in wbs. write_column_file = ColumnFileTiny::writeColumnFile(*dm_context, block, offset, limit, wbs); wbs.writeLogAndData(); write_range = rowkey_range; @@ -1200,6 +1208,7 @@ void DeltaMergeStore::waitForWrite(const DMContextPtr & dm_context, const Segmen size_t delta_rows = segment->getDelta()->getRows(); size_t delta_bytes = segment->getDelta()->getBytes(); + // No need to stall the write stall if not exceeding the threshold of force merge. if (delta_rows < forceMergeDeltaRows(dm_context) && delta_bytes < forceMergeDeltaBytes(dm_context)) return; @@ -1216,16 +1225,23 @@ void DeltaMergeStore::waitForWrite(const DMContextPtr & dm_context, const Segmen size_t sleep_ms; if (delta_rows >= stop_write_delta_rows || delta_bytes >= stop_write_delta_bytes) + { + // For stop write (hard limit), wait until segment is updated (e.g. delta is merged). sleep_ms = std::numeric_limits::max(); + } else + { + // For force merge (soft limit), wait for a reasonable amount of time. + // It is possible that the segment is still not updated after the wait. sleep_ms = static_cast(segment_bytes) / k10mb * 1000 * wait_duration_factor; + } // checkSegmentUpdate could do foreground merge delta, so call it before sleep. checkSegmentUpdate(dm_context, segment, ThreadType::Write); size_t sleep_step = 50; - // The delta will be merged, only after this segment got abandoned. - // Because merge delta will replace the segment instance. + // Wait at most `sleep_ms` until the delta is merged. + // Merge delta will replace the segment instance, causing `segment->hasAbandoned() == true`. while (!segment->hasAbandoned() && sleep_ms > 0) { size_t ms = std::min(sleep_ms, sleep_step); diff --git a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h index c600ac9dbf8..a8c34073f50 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h +++ b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h @@ -374,10 +374,14 @@ class DeltaMergeStore : private boost::noncopyable void flushCache(const DMContextPtr & dm_context, const RowKeyRange & range); - /// Do merge delta for all segments. Only used for debug. + /// Merge delta into the stable layer for all segments. + /// + /// This function is called when using `MANAGE TABLE [TABLE] MERGE DELTA` from TiFlash Client. void mergeDeltaAll(const Context & context); - /// Compact fragment column files into bigger one. + + /// Compact the delta layer, merging multiple fragmented delta files into larger ones. + /// This is a minor compaction as it does not merge the delta into stable layer. void compact(const Context & context, const RowKeyRange & range); /// Iterator over all segments and apply gc jobs. @@ -424,13 +428,33 @@ class DeltaMergeStore : private boost::noncopyable return handle_define.id != EXTRA_HANDLE_COLUMN_ID; } + /// Try to stall the writing. It will suspend the current thread if flow control is necessary. + /// There are roughly two flow control mechanisms: + /// - Force Merge (1 GB by default, see force_merge_delta_rows|size): Wait for a small amount of time at most. + /// - Stop Write (2 GB by default, see stop_write_delta_rows|size): Wait until delta is merged. void waitForWrite(const DMContextPtr & context, const SegmentPtr & segment); + void waitForDeleteRange(const DMContextPtr & context, const SegmentPtr & segment); + /// Try to update the segment. "Update" means splitting the segment into two, merging two segments, merging the delta, etc. + /// If an update is really performed, the segment will be abandoned (with `segment->hasAbandoned() == true`). + /// See `segmentSplit`, `segmentMerge`, `segmentMergeDelta` for details. + /// + /// This may be called from multiple threads, e.g. at the foreground write moment, or in background threads. + /// A `thread_type` should be specified indicating the type of the thread calling this function. + /// Depend on the thread type, the "update" to do may be varied. void checkSegmentUpdate(const DMContextPtr & context, const SegmentPtr & segment, ThreadType thread_type); + /// Split the segment into two. + /// After splitting, the segment will be abandoned (with `segment->hasAbandoned() == true`) and the new two segments will be returned. SegmentPair segmentSplit(DMContext & dm_context, const SegmentPtr & segment, bool is_foreground); + + /// Merge two segments into one. + /// After merging, both segments will be abandoned (with `segment->hasAbandoned() == true`). void segmentMerge(DMContext & dm_context, const SegmentPtr & left, const SegmentPtr & right, bool is_foreground); + + /// Merge the delta (major compaction) in the segment. + /// After delta-merging, the segment will be abandoned (with `segment->hasAbandoned() == true`) and a new segment will be returned. SegmentPtr segmentMergeDelta( DMContext & dm_context, const SegmentPtr & segment, diff --git a/dbms/src/Storages/DeltaMerge/RowKeyRange.h b/dbms/src/Storages/DeltaMerge/RowKeyRange.h index 43b56b2a1c0..ef2afbfdb32 100644 --- a/dbms/src/Storages/DeltaMerge/RowKeyRange.h +++ b/dbms/src/Storages/DeltaMerge/RowKeyRange.h @@ -63,6 +63,7 @@ struct RowKeyValueRef RowKeyValue toRowKeyValue() const; }; +/// Stores the raw bytes of the RowKey. Normally the RowKey will be stored in a column. struct RowKeyValue { RowKeyValue() = default; @@ -112,7 +113,11 @@ struct RowKeyValue // Format as a hex string for debugging. The value will be converted to '?' if redact-log is on String toDebugString() const; - RowKeyValueRef toRowKeyValueRef() const { return RowKeyValueRef{is_common_handle, value->data(), value->size(), int_value}; } + inline RowKeyValueRef toRowKeyValueRef() const + { + return RowKeyValueRef{is_common_handle, value->data(), value->size(), int_value}; + } + DecodedTiKVKeyPtr toRegionKey(TableID table_id) const { // FIXME: move this to TiKVRecordFormat.h @@ -177,6 +182,8 @@ struct RowKeyValue using RowKeyValues = std::vector; +/// An optimized implementation that will try to compare IntHandle via comparing Int values directly. +/// For common handles, per-byte comparison will be still used. inline int compare(const RowKeyValueRef & a, const RowKeyValueRef & b) { if (unlikely(a.is_common_handle != b.is_common_handle)) @@ -214,6 +221,9 @@ inline int compare(const RowKeyValueRef & a, const RowKeyValueRef & b) } } +// TODO (wenxuan): The following compare operators can be simplified using +// boost::operator, or operator<=> when we upgrade to C++20. + inline int compare(const StringRef & a, const RowKeyValueRef & b) { RowKeyValueRef r_a{true, a.data, a.size, 0}; @@ -353,12 +363,13 @@ size_t lowerBound(const RowKeyColumnContainer & rowkey_column, size_t first, siz } } // namespace +/// A range denoted as [StartRowKey, EndRowKey). struct RowKeyRange { - // todo use template to refine is_common_handle + // TODO: use template to refine is_common_handle bool is_common_handle; - /// start and end in RowKeyRange are always meaningful - /// it is assumed that start value is included and end value is excluded. + + // start and end in RowKeyRange are always meaningful. RowKeyValue start; RowKeyValue end; size_t rowkey_column_size; @@ -440,6 +451,7 @@ struct RowKeyRange } } + /// Create a RowKeyRange that covers all key space. static RowKeyRange newAll(bool is_common_handle, size_t rowkey_column_size) { if (is_common_handle) @@ -456,6 +468,7 @@ struct RowKeyRange } } + /// Create a RowKeyRange that covers no data at all. static RowKeyRange newNone(bool is_common_handle, size_t rowkey_column_size) { if (is_common_handle) @@ -594,10 +607,13 @@ struct RowKeyRange return check(first) && check(last_include); } + /// Check whether thisRange.Start <= key inline bool checkStart(const RowKeyValueRef & value) const { return compare(getStart(), value) <= 0; } + /// Check whether key < thisRange.End inline bool checkEnd(const RowKeyValueRef & value) const { return compare(value, getEnd()) < 0; } + /// Check whether the key is included in this range. inline bool check(const RowKeyValueRef & value) const { return checkStart(value) && checkEnd(value); } inline RowKeyValueRef getStart() const { return start.toRowKeyValueRef(); } @@ -625,7 +641,7 @@ struct RowKeyRange return {start_key, end_key}; } - /// return + /// Clip the according to this range, and return the clipped . std::pair getPosRange(const ColumnPtr & column, const size_t offset, const size_t limit) const { RowKeyColumnContainer rowkey_column(column, is_common_handle); diff --git a/dbms/src/Storages/DeltaMerge/Segment.h b/dbms/src/Storages/DeltaMerge/Segment.h index 0d048011e18..3ad29ee14a5 100644 --- a/dbms/src/Storages/DeltaMerge/Segment.h +++ b/dbms/src/Storages/DeltaMerge/Segment.h @@ -135,9 +135,16 @@ class Segment : private boost::noncopyable void serialize(WriteBatch & wb); + /// Attach a new ColumnFile into the Segment. The ColumnFile will be added to MemFileSet and flushed to disk later. + /// The block data of the passed in ColumnFile should be placed on disk before calling this function. + /// To write new block data, you can use `writeToCache`. bool writeToDisk(DMContext & dm_context, const ColumnFilePtr & column_file); + + /// Write a block of data into the MemTableSet part of the Segment. The data will be flushed to disk later. bool writeToCache(DMContext & dm_context, const Block & block, size_t offset, size_t limit); - bool write(DMContext & dm_context, const Block & block); // For test only + + /// For test only. + bool write(DMContext & dm_context, const Block & block); bool write(DMContext & dm_context, const RowKeyRange & delete_range); bool ingestColumnFiles(DMContext & dm_context, const RowKeyRange & range, const ColumnFiles & column_files, bool clear_data_in_range); @@ -219,7 +226,12 @@ class Segment : private boost::noncopyable WriteBatches & wbs, const StableValueSpacePtr & merged_stable); + /// Merge the delta (major compaction) and return the new segment. + /// + /// Note: This is only a shortcut function used in tests. + /// Normally you should call `prepareMergeDelta`, `applyMergeDelta` instead. SegmentPtr mergeDelta(DMContext & dm_context, const ColumnDefinesPtr & schema_snap) const; + StableValueSpacePtr prepareMergeDelta( DMContext & dm_context, const ColumnDefinesPtr & schema_snap, @@ -237,6 +249,8 @@ class Segment : private boost::noncopyable bool flushCache(DMContext & dm_context); void placeDeltaIndex(DMContext & dm_context); + /// Compact the delta layer, merging fragment column files into bigger column files. + /// It does not merge the delta into stable layer. bool compactDelta(DMContext & dm_context); size_t getEstimatedRows() const { return delta->getRows() + stable->getRows(); } @@ -266,11 +280,18 @@ class Segment : private boost::noncopyable return lock; } + /// Marks this segment as abandoned. + /// Note: Segment member functions never abandon the segment itself. + /// The abandon state is usually triggered by the DeltaMergeStore. void abandon(DMContext & context) { LOG_FMT_DEBUG(log, "Abandon segment [{}]", segment_id); delta->abandon(context); } + + /// Returns whether this segment has been marked as abandoned. + /// Note: Segment member functions never abandon the segment itself. + /// The abandon state is usually triggered by the DeltaMergeStore. bool hasAbandoned() { return delta->hasAbandoned(); } bool isSplitForbidden() { return split_forbidden; } @@ -372,7 +393,9 @@ class Segment : private boost::noncopyable bool relevant_place) const; private: - const UInt64 epoch; // After split / merge / merge delta, epoch got increased by 1. + /// The version of this segment. After split / merge / merge delta, epoch got increased by 1. + const UInt64 epoch; + RowKeyRange rowkey_range; bool is_common_handle; size_t rowkey_column_size; diff --git a/dbms/src/Storages/IManageableStorage.h b/dbms/src/Storages/IManageableStorage.h index ff2428f4533..23dc4cc447a 100644 --- a/dbms/src/Storages/IManageableStorage.h +++ b/dbms/src/Storages/IManageableStorage.h @@ -76,10 +76,10 @@ class IManageableStorage : public IStorage virtual void deleteRows(const Context &, size_t /*rows*/) { throw Exception("Unsupported"); } - // `limit` is the max number of segments to gc, return value is the number of segments gced + /// `limit` is the max number of segments to gc, return value is the number of segments gced virtual UInt64 onSyncGc(Int64 /*limit*/) { throw Exception("Unsupported"); } - // Return true is data dir exist + /// Return true is data dir exist virtual bool initStoreIfDataDirExist() { throw Exception("Unsupported"); } virtual void mergeDelta(const Context &) { throw Exception("Unsupported"); } @@ -90,7 +90,7 @@ class IManageableStorage : public IStorage virtual String getDatabaseName() const = 0; - // Update tidb table info in memory. + /// Update tidb table info in memory. virtual void setTableInfo(const TiDB::TableInfo & table_info_) = 0; virtual const TiDB::TableInfo & getTableInfo() const = 0; @@ -99,8 +99,8 @@ class IManageableStorage : public IStorage Timestamp getTombstone() const { return tombstone; } void setTombstone(Timestamp tombstone_) { IManageableStorage::tombstone = tombstone_; } - // Apply AlterCommands synced from TiDB should use `alterFromTiDB` instead of `alter(...)` - // Once called, table_info is guaranteed to be persisted, regardless commands being empty or not. + /// Apply AlterCommands synced from TiDB should use `alterFromTiDB` instead of `alter(...)` + /// Once called, table_info is guaranteed to be persisted, regardless commands being empty or not. virtual void alterFromTiDB( const TableLockHolder &, const AlterCommands & commands, @@ -110,16 +110,14 @@ class IManageableStorage : public IStorage const Context & context) = 0; - /** Rename the table. - * - * Renaming a name in a file with metadata, the name in the list of tables in the RAM, is done separately. - * Different from `IStorage::rename`, storage's data path do not contain database name, nothing to do with data path, `new_path_to_db` is ignored. - * But `getDatabaseName` and `getTableInfo` means we usally store database name / TiDB table info as member in storage, - * we need to update database name with `new_database_name`, and table name in tidb table info with `new_display_table_name`. - * - * Called when the table structure is locked for write. - * TODO: For TiFlash, we can rename without any lock on data? - */ + /// Rename the table. + /// + /// Renaming a name in a file with metadata, the name in the list of tables in the RAM, is done separately. + /// Different from `IStorage::rename`, storage's data path do not contain database name, nothing to do with data path, `new_path_to_db` is ignored. + /// But `getDatabaseName` and `getTableInfo` means we usually store database name / TiDB table info as member in storage, + /// we need to update database name with `new_database_name`, and table name in tidb table info with `new_display_table_name`. + /// + /// Called when the table structure is locked for write. virtual void rename( const String & new_path_to_db, const String & new_database_name, @@ -160,9 +158,9 @@ class IManageableStorage : public IStorage virtual size_t getRowKeyColumnSize() const { return 1; } - // when `need_block` is true, it will try return a cached block corresponding to DecodingStorageSchemaSnapshotConstPtr, - // and `releaseDecodingBlock` need to be called when the block is free - // when `need_block` is false, it will just return an nullptr + /// when `need_block` is true, it will try return a cached block corresponding to DecodingStorageSchemaSnapshotConstPtr, + /// and `releaseDecodingBlock` need to be called when the block is free + /// when `need_block` is false, it will just return an nullptr virtual std::pair getSchemaSnapshotAndBlockForDecoding(bool /* need_block */) { throw Exception("Method getDecodingSchemaSnapshot is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); diff --git a/dbms/src/Storages/StorageDeltaMerge.cpp b/dbms/src/Storages/StorageDeltaMerge.cpp index f2aa227d29c..c61842e4353 100644 --- a/dbms/src/Storages/StorageDeltaMerge.cpp +++ b/dbms/src/Storages/StorageDeltaMerge.cpp @@ -414,6 +414,10 @@ class DMBlockOutputStream : public IBlockOutputStream void write(const Block & block) override try { + // When dt_insert_max_rows (Max rows of insert blocks when write into DeltaTree Engine, default = 0) is specified, + // the insert block will be splited into multiples. + // Currently dt_insert_max_rows is only used for performance tests. + if (db_settings.dt_insert_max_rows == 0) { Block to_write = decorator(block); @@ -467,6 +471,7 @@ void StorageDeltaMerge::write(Block & block, const Settings & settings) #ifndef NDEBUG { // Do some check under DEBUG mode to ensure all block are written with column id properly set. + // In this way we can catch the case that upstream raft log contains problematic data written from TiDB. auto header = store->getHeader(); bool ok = true; String name; diff --git a/dbms/src/Storages/StorageDeltaMerge.h b/dbms/src/Storages/StorageDeltaMerge.h index 560f365e747..b9598b77a86 100644 --- a/dbms/src/Storages/StorageDeltaMerge.h +++ b/dbms/src/Storages/StorageDeltaMerge.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,9 @@ class StorageDeltaMerge void flushCache(const Context & context, const DM::RowKeyRange & range_to_flush) override; + /// Merge delta into the stable layer for all segments. + /// + /// This function is called when using `MANAGE TABLE [TABLE] MERGE DELTA` from TiFlash Client. void mergeDelta(const Context & context) override; void deleteRange(const DM::RowKeyRange & range_to_delete, const Settings & settings); From 5985ad676b897bfead1aa21238129ddc5fa6490c Mon Sep 17 00:00:00 2001 From: Calvin Neo Date: Fri, 13 May 2022 14:50:35 +0800 Subject: [PATCH 019/127] update pprof for proxy (#4871) ref pingcap/tiflash#4618 --- contrib/tiflash-proxy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tiflash-proxy b/contrib/tiflash-proxy index a03434d3758..e46e88799f3 160000 --- a/contrib/tiflash-proxy +++ b/contrib/tiflash-proxy @@ -1 +1 @@ -Subproject commit a03434d3758bee4fa335ce87da5f772eebe8f9cc +Subproject commit e46e88799f383bbd10d6508da74a625053ab78e5 From c0d2d56f177d8844d952d8524da38b16e14c953d Mon Sep 17 00:00:00 2001 From: Meng Xin Date: Fri, 13 May 2022 15:46:36 +0800 Subject: [PATCH 020/127] add DB config max_threads by grpc context (#4795) close pingcap/tiflash#4794 --- dbms/src/Flash/FlashService.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dbms/src/Flash/FlashService.cpp b/dbms/src/Flash/FlashService.cpp index 4e2a2ae88bc..c66d6f480bb 100644 --- a/dbms/src/Flash/FlashService.cpp +++ b/dbms/src/Flash/FlashService.cpp @@ -347,7 +347,8 @@ ::grpc::Status FlashService::CancelMPPTask( String getClientMetaVarWithDefault(const grpc::ServerContext * grpc_context, const String & name, const String & default_val) { if (auto it = grpc_context->client_metadata().find(name); it != grpc_context->client_metadata().end()) - return it->second.data(); + return String(it->second.data(), it->second.size()); + return default_val; } @@ -387,6 +388,14 @@ std::tuple FlashService::createDBContext(const grpc::S { context->setSetting("dag_records_per_chunk", dag_records_per_chunk_str); } + + String max_threads = getClientMetaVarWithDefault(grpc_context, "tidb_max_tiflash_threads", ""); + if (!max_threads.empty()) + { + context->setSetting("max_threads", max_threads); + LOG_FMT_INFO(log, "set context setting max_threads to {}", max_threads); + } + context->setSetting("enable_async_server", is_async ? "true" : "false"); context->setSetting("enable_local_tunnel", enable_local_tunnel ? "true" : "false"); context->setSetting("enable_async_grpc_client", enable_async_grpc_client ? "true" : "false"); From 9e46e67120d4446a3eae2debf1c9db8604bbbf59 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Fri, 13 May 2022 17:16:36 +0800 Subject: [PATCH 021/127] Add a force transfrom meta from v2 to v3 (#4873) ref pingcap/tiflash#3594 --- dbms/src/Storages/DeltaMerge/StoragePool.cpp | 64 ++++++++++++++++++++ dbms/src/Storages/DeltaMerge/StoragePool.h | 3 + dbms/src/Storages/Page/Page.h | 11 ++++ dbms/src/Storages/Page/PageStorage.cpp | 33 +++++++--- dbms/src/Storages/Page/PageStorage.h | 5 +- dbms/src/Storages/Page/V3/PageEntry.h | 2 +- 6 files changed, 107 insertions(+), 11 deletions(-) diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.cpp b/dbms/src/Storages/DeltaMerge/StoragePool.cpp index 2979a75e192..f0c3db1e4e8 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.cpp +++ b/dbms/src/Storages/DeltaMerge/StoragePool.cpp @@ -237,6 +237,52 @@ StoragePool::StoragePool(Context & global_ctx, NamespaceId ns_id_, StoragePathPo } } +void StoragePool::forceTransformMetaV2toV3() +{ + assert(meta_storage_v2 != nullptr); + assert(meta_storage_v3 != nullptr); + auto meta_transform_storage_writer = std::make_shared(run_mode, meta_storage_v2, meta_storage_v3); + auto meta_transform_storage_reader = std::make_shared(run_mode, ns_id, meta_storage_v2, meta_storage_v3, nullptr); + + Pages pages_transform = {}; + auto meta_transform_acceptor = [&](const DB::Page & page) { + pages_transform.emplace_back(page); + }; + + meta_transform_storage_reader->traverse(meta_transform_acceptor, /*only_v2*/ true, /*only_v3*/ false); + + WriteBatch write_batch_transform{ns_id}; + WriteBatch write_batch_del_v2{ns_id}; + + for (const auto & page_transform : pages_transform) + { + // Check pages have not contain field offset + // Also get the tag of page_id + const auto & page_transform_entry = meta_transform_storage_reader->getPageEntry(page_transform.page_id); + if (!page_transform_entry.field_offsets.empty()) + { + throw Exception(fmt::format("Can't transfrom meta from V2 to V3, [page_id={}] {}", // + page_transform.page_id, + page_transform_entry.toDebugString()), + ErrorCodes::LOGICAL_ERROR); + } + + write_batch_transform.putPage(page_transform.page_id, // + page_transform_entry.tag, + std::make_shared(page_transform.data.begin(), + page_transform.data.size()), + page_transform.data.size()); + // Record del for V2 + write_batch_del_v2.delPage(page_transform.page_id); + } + + // Will rewrite into V3. + meta_transform_storage_writer->write(std::move(write_batch_transform), nullptr); + + // DEL must call after rewrite. + meta_transform_storage_writer->writeIntoV2(std::move(write_batch_del_v2), nullptr); +} + PageStorageRunMode StoragePool::restore() { const auto & global_storage_pool = global_context.getGlobalStoragePool(); @@ -273,6 +319,24 @@ PageStorageRunMode StoragePool::restore() auto v2_data_max_ids = data_storage_v2->restore(); auto v2_meta_max_ids = meta_storage_v2->restore(); + // The pages on data and log can be rewritten to V3 and the old pages on V2 are deleted by `delta merge`. + // However, the pages on meta V2 can not be deleted. As the pages in meta are small, we perform a forceTransformMetaV2toV3 to convert pages before all. + if (const auto & meta_remain_pages = meta_storage_v2->getNumberOfPages(); meta_remain_pages != 0) + { + LOG_FMT_INFO(logger, "Current meta transform to V3 begin, [ns_id={}] [pages_before_transform={}]", ns_id, meta_remain_pages); + forceTransformMetaV2toV3(); + const auto & meta_remain_pages_after_transform = meta_storage_v2->getNumberOfPages(); + LOG_FMT_INFO(logger, "Current meta transform to V3 finished. [ns_id={}] [done={}] [pages_before_transform={}], [pages_after_transform={}]", // + ns_id, + meta_remain_pages_after_transform == 0, + meta_remain_pages, + meta_remain_pages_after_transform); + } + else + { + LOG_FMT_INFO(logger, "Current meta translate already done before restored.[ns_id={}] ", ns_id); + } + assert(v2_log_max_ids.size() == 1); assert(v2_data_max_ids.size() == 1); assert(v2_meta_max_ids.size() == 1); diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.h b/dbms/src/Storages/DeltaMerge/StoragePool.h index 5f439899a84..14dac73b667 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.h +++ b/dbms/src/Storages/DeltaMerge/StoragePool.h @@ -195,6 +195,9 @@ class StoragePool : private boost::noncopyable private: #endif bool doV2Gc(const Settings & settings); + + void forceTransformMetaV2toV3(); + #ifndef DBMS_PUBLIC_GTEST private: #endif diff --git a/dbms/src/Storages/Page/Page.h b/dbms/src/Storages/Page/Page.h index 0217fd82ccf..b54b25033dd 100644 --- a/dbms/src/Storages/Page/Page.h +++ b/dbms/src/Storages/Page/Page.h @@ -126,6 +126,17 @@ struct PageEntry return std::make_pair(file_id, level); } + String toDebugString() const + { + return fmt::format("PageEntry{{file: {}, offset: 0x{:X}, size: {}, checksum: 0x{:X}, tag: {}, field_offsets_size: {}}}", + file_id, + offset, + size, + checksum, + tag, + field_offsets.size()); + } + size_t getFieldSize(size_t index) const { if (unlikely(index >= field_offsets.size())) diff --git a/dbms/src/Storages/Page/PageStorage.cpp b/dbms/src/Storages/Page/PageStorage.cpp index cf8a9698a55..4a8dca05a71 100644 --- a/dbms/src/Storages/Page/PageStorage.cpp +++ b/dbms/src/Storages/Page/PageStorage.cpp @@ -66,7 +66,7 @@ class PageReaderImpl : private boost::noncopyable // Get some statistics of all living snapshots and the oldest living snapshot. virtual SnapshotsStatistics getSnapshotsStat() const = 0; - virtual void traverse(const std::function & acceptor) const = 0; + virtual void traverse(const std::function & acceptor, bool only_v2, bool only_v3) const = 0; }; @@ -132,7 +132,7 @@ class PageReaderImplNormal : public PageReaderImpl return storage->getSnapshotsStat(); } - void traverse(const std::function & acceptor) const override + void traverse(const std::function & acceptor, bool /*only_v2*/, bool /*only_v3*/) const override { storage->traverse(acceptor, nullptr); } @@ -294,12 +294,30 @@ class PageReaderImplMixed : public PageReaderImpl return statistics_total; } - void traverse(const std::function & acceptor) const override + void traverse(const std::function & acceptor, bool only_v2, bool only_v3) const override { // Used by RegionPersister::restore // Must traverse storage_v3 before storage_v2 - storage_v3->traverse(acceptor, toConcreteV3Snapshot()); - storage_v2->traverse(acceptor, toConcreteV2Snapshot()); + if (only_v3 && only_v2) + { + throw Exception("Can't enable both only_v2 and only_v3", ErrorCodes::LOGICAL_ERROR); + } + + if (only_v3) + { + storage_v3->traverse(acceptor, toConcreteV3Snapshot()); + } + else if (only_v2) + { + storage_v2->traverse(acceptor, toConcreteV2Snapshot()); + } + else + { + // Used by RegionPersister::restore + // Must traverse storage_v3 before storage_v2 + storage_v3->traverse(acceptor, toConcreteV3Snapshot()); + storage_v2->traverse(acceptor, toConcreteV2Snapshot()); + } } private: @@ -406,9 +424,9 @@ SnapshotsStatistics PageReader::getSnapshotsStat() const return impl->getSnapshotsStat(); } -void PageReader::traverse(const std::function & acceptor) const +void PageReader::traverse(const std::function & acceptor, bool only_v2, bool only_v3) const { - impl->traverse(acceptor); + impl->traverse(acceptor, only_v2, only_v3); } /********************** @@ -437,7 +455,6 @@ void PageWriter::write(WriteBatch && write_batch, WriteLimiterPtr write_limiter) } } - void PageWriter::writeIntoV2(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const { storage_v2->write(std::move(write_batch), write_limiter); diff --git a/dbms/src/Storages/Page/PageStorage.h b/dbms/src/Storages/Page/PageStorage.h index fe8946549cd..5fc69c29364 100644 --- a/dbms/src/Storages/Page/PageStorage.h +++ b/dbms/src/Storages/Page/PageStorage.h @@ -373,7 +373,7 @@ class PageReader : private boost::noncopyable // Get some statistics of all living snapshots and the oldest living snapshot. SnapshotsStatistics getSnapshotsStat() const; - void traverse(const std::function & acceptor) const; + void traverse(const std::function & acceptor, bool only_v2 = false, bool only_v3 = false) const; private: std::unique_ptr impl; @@ -394,9 +394,10 @@ class PageWriter : private boost::noncopyable friend class RegionPersister; -private: + // Only used for META and KVStore write del. void writeIntoV2(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const; +private: void writeIntoV3(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const; void writeIntoMixMode(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const; diff --git a/dbms/src/Storages/Page/V3/PageEntry.h b/dbms/src/Storages/Page/V3/PageEntry.h index bcefc0e845b..56411f87faf 100644 --- a/dbms/src/Storages/Page/V3/PageEntry.h +++ b/dbms/src/Storages/Page/V3/PageEntry.h @@ -87,7 +87,7 @@ inline PageIdV3Internal buildV3Id(NamespaceId n_id, PageId p_id) inline String toDebugString(const PageEntryV3 & entry) { - return fmt::format("PageEntry{{file: {}, offset: 0x{:X}, size: {}, checksum: 0x{:X}, tag: {}, field_offsets_size: {}}}", + return fmt::format("PageEntryV3{{file: {}, offset: 0x{:X}, size: {}, checksum: 0x{:X}, tag: {}, field_offsets_size: {}}}", entry.file_id, entry.offset, entry.size, From 9bfb02f472dac05de169486b04c481f15f480d2b Mon Sep 17 00:00:00 2001 From: yibin Date: Fri, 13 May 2022 18:18:36 +0800 Subject: [PATCH 022/127] Add to_seconds support for tiflash (#4866) close pingcap/tiflash#4679 --- dbms/src/Common/MyTime.cpp | 17 +++- dbms/src/Common/MyTime.h | 6 ++ dbms/src/Flash/Coprocessor/DAGUtils.cpp | 2 +- dbms/src/Functions/FunctionsDateTime.cpp | 1 + dbms/src/Functions/FunctionsDateTime.h | 37 ++++++++ dbms/src/Functions/tests/gtest_toseconds.cpp | 99 ++++++++++++++++++++ tests/fullstack-test/expr/to_seconds.test | 61 ++++++++++++ 7 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 dbms/src/Functions/tests/gtest_toseconds.cpp create mode 100644 tests/fullstack-test/expr/to_seconds.test diff --git a/dbms/src/Common/MyTime.cpp b/dbms/src/Common/MyTime.cpp index 1b957e65484..420709ec129 100644 --- a/dbms/src/Common/MyTime.cpp +++ b/dbms/src/Common/MyTime.cpp @@ -992,6 +992,15 @@ int calcDayNum(int year, int month, int day) return delsum + year / 4 - temp; } +UInt64 calcSeconds(int year, int month, int day, int hour, int minute, int second) +{ + if (year == 0 && month == 0) + return 0; + Int32 current_days = calcDayNum(year, month, day); + return current_days * MyTimeBase::SECOND_IN_ONE_DAY + hour * MyTimeBase::SECOND_IN_ONE_HOUR + + minute * MyTimeBase::SECOND_IN_ONE_MINUTE + second; +} + size_t maxFormattedDateTimeStringLength(const String & format) { size_t result = 0; @@ -1142,7 +1151,7 @@ UInt64 addSeconds(UInt64 t, Int64 delta) return t; } MyDateTime my_time(t); - Int64 current_second = my_time.hour * 3600 + my_time.minute * 60 + my_time.second; + Int64 current_second = my_time.hour * MyTimeBase::SECOND_IN_ONE_HOUR + my_time.minute * MyTimeBase::SECOND_IN_ONE_MINUTE + my_time.second; current_second += delta; if (current_second >= 0) { @@ -1161,9 +1170,9 @@ UInt64 addSeconds(UInt64 t, Int64 delta) current_second += days * MyTimeBase::SECOND_IN_ONE_DAY; addDays(my_time, -days); } - my_time.hour = current_second / 3600; - my_time.minute = (current_second % 3600) / 60; - my_time.second = current_second % 60; + my_time.hour = current_second / MyTimeBase::SECOND_IN_ONE_HOUR; + my_time.minute = (current_second % MyTimeBase::SECOND_IN_ONE_HOUR) / MyTimeBase::SECOND_IN_ONE_MINUTE; + my_time.second = current_second % MyTimeBase::SECOND_IN_ONE_MINUTE; return my_time.toPackedUInt(); } diff --git a/dbms/src/Common/MyTime.h b/dbms/src/Common/MyTime.h index c146ad900f0..ecbaed60445 100644 --- a/dbms/src/Common/MyTime.h +++ b/dbms/src/Common/MyTime.h @@ -24,6 +24,9 @@ namespace DB struct MyTimeBase { static constexpr Int64 SECOND_IN_ONE_DAY = 86400; + static constexpr Int64 SECOND_IN_ONE_HOUR = 3600; + static constexpr Int64 SECOND_IN_ONE_MINUTE = 60; + // copied from https://github.com/pingcap/tidb/blob/master/types/time.go // Core time bit fields. @@ -193,6 +196,9 @@ std::pair roundTimeByFsp(time_t second, UInt64 nano_second, UInt int calcDayNum(int year, int month, int day); +// returns seconds since '0000-00-00' +UInt64 calcSeconds(int year, int month, int day, int hour, int minute, int second); + size_t maxFormattedDateTimeStringLength(const String & format); inline time_t getEpochSecond(const MyDateTime & my_time, const DateLUTImpl & time_zone) diff --git a/dbms/src/Flash/Coprocessor/DAGUtils.cpp b/dbms/src/Flash/Coprocessor/DAGUtils.cpp index a53216ff1bb..3be0ac4f55e 100644 --- a/dbms/src/Flash/Coprocessor/DAGUtils.cpp +++ b/dbms/src/Flash/Coprocessor/DAGUtils.cpp @@ -573,7 +573,7 @@ const std::unordered_map scalar_func_map({ //{tipb::ScalarFuncSig::TimeToSec, "cast"}, //{tipb::ScalarFuncSig::TimestampAdd, "cast"}, //{tipb::ScalarFuncSig::ToDays, "cast"}, - //{tipb::ScalarFuncSig::ToSeconds, "cast"}, + {tipb::ScalarFuncSig::ToSeconds, "tidbToSeconds"}, //{tipb::ScalarFuncSig::UTCTimeWithArg, "cast"}, //{tipb::ScalarFuncSig::UTCTimestampWithoutArg, "cast"}, //{tipb::ScalarFuncSig::Timestamp1Arg, "cast"}, diff --git a/dbms/src/Functions/FunctionsDateTime.cpp b/dbms/src/Functions/FunctionsDateTime.cpp index c3ef00b19e1..112a8050b4d 100644 --- a/dbms/src/Functions/FunctionsDateTime.cpp +++ b/dbms/src/Functions/FunctionsDateTime.cpp @@ -137,6 +137,7 @@ void registerFunctionsDateTime(FunctionFactory & factory) factory.registerFunction(); factory.registerFunction(); factory.registerFunction(); + factory.registerFunction(); factory.registerFunction(); factory.registerFunction(); diff --git a/dbms/src/Functions/FunctionsDateTime.h b/dbms/src/Functions/FunctionsDateTime.h index 3d15186b472..9ae4eeaaad9 100644 --- a/dbms/src/Functions/FunctionsDateTime.h +++ b/dbms/src/Functions/FunctionsDateTime.h @@ -3277,6 +3277,42 @@ struct TiDBWeekOfYearTransformerImpl } }; +template +struct TiDBToSecondsTransformerImpl +{ + static constexpr auto name = "tidbToSeconds"; + + static void execute(const Context & context, + const ColumnVector::Container & vec_from, + typename ColumnVector::Container & vec_to, + typename ColumnVector::Container & vec_null_map) + { + bool is_null = false; + for (size_t i = 0; i < vec_from.size(); ++i) + { + MyTimeBase val(vec_from[i]); + vec_to[i] = execute(context, val, is_null); + vec_null_map[i] = is_null; + is_null = false; + } + } + + static ToFieldType execute(const Context & context, const MyTimeBase & val, bool & is_null) + { + // TiDB returns normal value if one of month/day is zero for to_seconds function, while MySQL return null if either of them is zero. + // TiFlash aligns with MySQL to align the behavior with other functions like last_day. + if (val.month == 0 || val.day == 0) + { + context.getDAGContext()->handleInvalidTime( + fmt::format("Invalid time value: month({}) or day({}) is zero", val.month, val.day), + Errors::Types::WrongValue); + is_null = true; + return 0; + } + return static_cast(calcSeconds(val.year, val.month, val.day, val.hour, val.minute, val.second)); + } +}; + // Similar to FunctionDateOrDateTimeToSomething, but also handle nullable result and mysql sql mode. template class Transformer, bool return_nullable> class FunctionMyDateOrMyDateTimeToSomething : public IFunction @@ -3376,6 +3412,7 @@ using FunctionToLastDay = FunctionMyDateOrMyDateTimeToSomething; using FunctionToTiDBDayOfYear = FunctionMyDateOrMyDateTimeToSomething; using FunctionToTiDBWeekOfYear = FunctionMyDateOrMyDateTimeToSomething; +using FunctionToTiDBToSeconds = FunctionMyDateOrMyDateTimeToSomething; using FunctionToRelativeYearNum = FunctionDateOrDateTimeToSomething; using FunctionToRelativeQuarterNum = FunctionDateOrDateTimeToSomething; diff --git a/dbms/src/Functions/tests/gtest_toseconds.cpp b/dbms/src/Functions/tests/gtest_toseconds.cpp new file mode 100644 index 00000000000..aa1ecf87ee4 --- /dev/null +++ b/dbms/src/Functions/tests/gtest_toseconds.cpp @@ -0,0 +1,99 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include +#include +#include + +#include +#include + +namespace DB::tests +{ +class TestToSeconds : public DB::tests::FunctionTest +{ +}; + +TEST_F(TestToSeconds, TestAll) +try +{ + DAGContext * dag_context = context.getDAGContext(); + UInt64 ori_flags = dag_context->getFlags(); + dag_context->addFlag(TiDBSQLFlags::TRUNCATE_AS_WARNING); + /// ColumnVector(nullable) + const String func_name = "tidbToSeconds"; + static auto const nullable_datetime_type_ptr = makeNullable(std::make_shared(6)); + static auto const datetime_type_ptr = std::make_shared(6); + static auto const date_type_ptr = std::make_shared(); + auto data_col_ptr = createColumn>( + { + {}, // Null + MyDateTime(0, 0, 0, 0, 0, 0, 0).toPackedUInt(), + MyDateTime(0, 1, 1, 0, 0, 0, 0).toPackedUInt(), + MyDateTime(1969, 1, 2, 1, 1, 1, 1).toPackedUInt(), + MyDateTime(2000, 12, 31, 10, 10, 10, 700).toPackedUInt(), + MyDateTime(2022, 3, 13, 6, 7, 8, 9).toPackedUInt(), + }) + .column; + auto input_col = ColumnWithTypeAndName(data_col_ptr, nullable_datetime_type_ptr, "input"); + auto output_col = createColumn>({{}, {}, 86400, 62135773261ULL, 63145476610ULL, 63814370828ULL}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + + /// ColumnVector(non-null) + data_col_ptr = createColumn( + { + MyDateTime(0, 0, 0, 0, 0, 0, 0).toPackedUInt(), + MyDateTime(1969, 1, 2, 1, 1, 1, 1).toPackedUInt(), + MyDateTime(2000, 12, 31, 10, 10, 10, 700).toPackedUInt(), + MyDateTime(2022, 3, 13, 6, 7, 8, 9).toPackedUInt(), + }) + .column; + input_col = ColumnWithTypeAndName(data_col_ptr, datetime_type_ptr, "input"); + output_col = createColumn>({{}, 62135773261ULL, 63145476610ULL, 63814370828ULL}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + + /// ColumnConst(non-null) + input_col = ColumnWithTypeAndName(createConstColumn(1, MyDateTime(2022, 3, 13, 6, 7, 8, 9).toPackedUInt()).column, datetime_type_ptr, "input"); + output_col = createConstColumn>(1, {63814370828ULL}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + + /// ColumnConst(nullable) + input_col = ColumnWithTypeAndName(createConstColumn>(1, MyDateTime(2022, 3, 13, 6, 7, 8, 9).toPackedUInt()).column, nullable_datetime_type_ptr, "input"); + output_col = createConstColumn>(1, {63814370828ULL}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + + /// ColumnConst(nullable(null)) + input_col = ColumnWithTypeAndName(createConstColumn>(1, {}).column, nullable_datetime_type_ptr, "input"); + output_col = createConstColumn>(1, {}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + + /// MyDate ColumnVector(non-null) + data_col_ptr = createColumn( + { + MyDate(0000, 0, 1).toPackedUInt(), + MyDate(0000, 1, 1).toPackedUInt(), + MyDate(1969, 1, 1).toPackedUInt(), + MyDate(2000, 12, 1).toPackedUInt(), + MyDate(2022, 3, 14).toPackedUInt(), + }) + .column; + input_col = ColumnWithTypeAndName(data_col_ptr, date_type_ptr, "input"); + output_col = createColumn>({{}, 86400, 62135683200ULL, 63142848000ULL, 63814435200ULL}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + dag_context->setFlags(ori_flags); +} +CATCH + +} // namespace DB::tests diff --git a/tests/fullstack-test/expr/to_seconds.test b/tests/fullstack-test/expr/to_seconds.test new file mode 100644 index 00000000000..3aabbede5d0 --- /dev/null +++ b/tests/fullstack-test/expr/to_seconds.test @@ -0,0 +1,61 @@ +# Copyright 2022 PingCAP, Ltd. +# +# 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. + +mysql> drop table if exists test.t1; +mysql> create table test.t1(c1 varchar(100), c2 datetime, c3 date); +mysql> insert into test.t1 values('', '1999-10-10 10:10:10.123', '1999-01-10'), ('200', '1999-02-10 10:10:10.123', '1999-11-10'), ('1999-01-10', '1999-10-10 10:10:10.123', '1999-01-10'); +# leap year +mysql> insert into test.t1 values('2000-2-10', '2000-2-10 10:10:10', '2000-2-10'); +# non leap year +mysql> insert into test.t1 values('2001-2-10', '2001-2-10 10:10:10', '2001-2-10'); +# zero day +mysql> insert into test.t1 values('2000-2-0', '2000-2-10 10:10:10', '2000-2-10'); +mysql> alter table test.t1 set tiflash replica 1; +func> wait_table test t1 +#mysql> set @@tidb_isolation_read_engines='tiflash'; set @@tidb_enforce_mpp = 1; select c1, to_seconds(c1) from test.t1 order by 1; +#+------------+----------------+ +#| c1 | to_seconds(c1) | +#+------------+----------------+ +#| | NULL | +#| 1999-01-10 | 63083145600 | +#| 200 | NULL | +#| 2000-2-0 | NULL | +#| 2000-2-10 | 63117360000 | +#| 2001-2-10 | 63148982400 | +#+------------+----------------+ +mysql> set @@tidb_isolation_read_engines='tiflash'; set @@tidb_enforce_mpp = 1; select c2, to_seconds(c2) from test.t1 order by 1; ++---------------------+----------------+ +| c2 | to_seconds(c2) | ++---------------------+----------------+ +| 1999-02-10 10:10:10 | 63085860610 | +| 1999-10-10 10:10:10 | 63106769410 | +| 1999-10-10 10:10:10 | 63106769410 | +| 2000-02-10 10:10:10 | 63117396610 | +| 2000-02-10 10:10:10 | 63117396610 | +| 2001-02-10 10:10:10 | 63149019010 | ++---------------------+----------------+ +mysql> set @@tidb_isolation_read_engines='tiflash'; set @@tidb_enforce_mpp = 1; select c3, to_seconds(c3) from test.t1 order by 1; ++------------+----------------+ +| c3 | to_seconds(c3) | ++------------+----------------+ +| 1999-01-10 | 63083145600 | +| 1999-01-10 | 63083145600 | +| 1999-11-10 | 63109411200 | +| 2000-02-10 | 63117360000 | +| 2000-02-10 | 63117360000 | +| 2001-02-10 | 63148982400 | ++------------+----------------+ + +mysql> drop table if exists test.t1; + From 65bdafa98fd314a53920d1d9cda3d2a6f9b43884 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Fri, 13 May 2022 19:12:35 +0800 Subject: [PATCH 023/127] Support big page PUT into blobstore. (#4882) ref pingcap/tiflash#3594 --- dbms/src/Storages/Page/V3/BlobStore.cpp | 217 +++++++++++---- dbms/src/Storages/Page/V3/BlobStore.h | 39 ++- .../Storages/Page/V3/spacemap/SpaceMap.cpp | 23 +- dbms/src/Storages/Page/V3/spacemap/SpaceMap.h | 6 +- .../Storages/Page/V3/spacemap/SpaceMapBig.h | 151 +++++++++++ .../Page/V3/spacemap/SpaceMapSTDMap.h | 4 + .../Page/V3/tests/gtest_blob_store.cpp | 247 +++++++++++++++++- 7 files changed, 616 insertions(+), 71 deletions(-) create mode 100644 dbms/src/Storages/Page/V3/spacemap/SpaceMapBig.h diff --git a/dbms/src/Storages/Page/V3/BlobStore.cpp b/dbms/src/Storages/Page/V3/BlobStore.cpp index 3e58a3eb87e..3ed1d1a69b1 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.cpp +++ b/dbms/src/Storages/Page/V3/BlobStore.cpp @@ -90,8 +90,16 @@ void BlobStore::registerPaths() if (blob_id != INVALID_BLOBFILE_ID) { Poco::File blob(fmt::format("{}/{}", path, blob_name)); - delegator->addPageFileUsedSize({blob_id, 0}, blob.getSize(), path, true); - blob_stats.createStatNotChecking(blob_id, lock_stats); + auto blob_size = blob.getSize(); + delegator->addPageFileUsedSize({blob_id, 0}, blob_size, path, true); + if (blob_size > config.file_limit_size) + { + blob_stats.createBigPageStatNotChecking(blob_id, lock_stats); + } + else + { + blob_stats.createStatNotChecking(blob_id, lock_stats); + } } else { @@ -101,20 +109,100 @@ void BlobStore::registerPaths() } } +PageEntriesEdit BlobStore::handleLargeWrite(DB::WriteBatch & wb, const WriteLimiterPtr & write_limiter) +{ + auto ns_id = wb.getNamespaceId(); + PageEntriesEdit edit; + for (auto & write : wb.getWrites()) + { + switch (write.type) + { + case WriteBatch::WriteType::PUT: + { + ChecksumClass digest; + PageEntryV3 entry; + + auto [blob_id, offset_in_file] = getPosFromStats(write.size); + + entry.file_id = blob_id; + entry.size = write.size; + entry.tag = write.tag; + entry.offset = offset_in_file; + // padding size won't work on big write batch + entry.padded_size = 0; + + BufferBase::Buffer data_buf = write.read_buffer->buffer(); + + digest.update(data_buf.begin(), write.size); + entry.checksum = digest.checksum(); + + UInt64 field_begin, field_end; + + for (size_t i = 0; i < write.offsets.size(); ++i) + { + ChecksumClass field_digest; + field_begin = write.offsets[i].first; + field_end = (i == write.offsets.size() - 1) ? write.size : write.offsets[i + 1].first; + + field_digest.update(data_buf.begin() + field_begin, field_end - field_begin); + write.offsets[i].second = field_digest.checksum(); + } + + if (!write.offsets.empty()) + { + // we can swap from WriteBatch instead of copying + entry.field_offsets.swap(write.offsets); + } + + try + { + auto blob_file = getBlobFile(blob_id); + blob_file->write(data_buf.begin(), offset_in_file, write.size, write_limiter); + } + catch (DB::Exception & e) + { + removePosFromStats(blob_id, offset_in_file, write.size); + LOG_FMT_ERROR(log, "[blob_id={}] [offset_in_file={}] [size={}] write failed.", blob_id, offset_in_file, write.size); + throw e; + } + + edit.put(buildV3Id(ns_id, write.page_id), entry); + break; + } + case WriteBatch::WriteType::DEL: + { + edit.del(buildV3Id(ns_id, write.page_id)); + break; + } + case WriteBatch::WriteType::REF: + { + edit.ref(buildV3Id(ns_id, write.page_id), buildV3Id(ns_id, write.ori_page_id)); + break; + } + case WriteBatch::WriteType::PUT_EXTERNAL: + edit.putExternal(buildV3Id(ns_id, write.page_id)); + break; + case WriteBatch::WriteType::UPSERT: + throw Exception(fmt::format("Unknown write type: {}", write.type)); + } + } + + return edit; +} + PageEntriesEdit BlobStore::write(DB::WriteBatch & wb, const WriteLimiterPtr & write_limiter) { ProfileEvents::increment(ProfileEvents::PSMWritePages, wb.putWriteCount()); - PageEntriesEdit edit; const size_t all_page_data_size = wb.getTotalDataSize(); if (all_page_data_size > config.file_limit_size) { - throw Exception(fmt::format("Write batch is too large. It should less than [file_limit_size={}]", - config.file_limit_size.get()), - ErrorCodes::LOGICAL_ERROR); + return handleLargeWrite(wb, write_limiter); } + PageEntriesEdit edit; + auto ns_id = wb.getNamespaceId(); if (all_page_data_size == 0) { @@ -167,7 +255,6 @@ PageEntriesEdit BlobStore::write(DB::WriteBatch & wb, const WriteLimiterPtr & wr size_t offset_in_allocated = 0; - for (auto & write : wb.getWrites()) { switch (write.type) @@ -316,19 +403,29 @@ std::pair BlobStore::getPosFromStats(size_t size) auto lock_stat = [size, this, &stat]() { auto lock_stats = blob_stats.lock(); - BlobFileId blob_file_id = INVALID_BLOBFILE_ID; - std::tie(stat, blob_file_id) = blob_stats.chooseStat(size, lock_stats); - if (stat == nullptr) + if (size > config.file_limit_size) { - // No valid stat for puting data with `size`, create a new one - stat = blob_stats.createStat(blob_file_id, lock_stats); + auto blob_file_id = blob_stats.chooseBigStat(lock_stats); + stat = blob_stats.createBigStat(blob_file_id, lock_stats); + + return stat->lock(); } + else + { + BlobFileId blob_file_id = INVALID_BLOBFILE_ID; + std::tie(stat, blob_file_id) = blob_stats.chooseStat(size, lock_stats); + if (stat == nullptr) + { + // No valid stat for puting data with `size`, create a new one + stat = blob_stats.createStat(blob_file_id, lock_stats); + } - // We must get the lock from BlobStat under the BlobStats lock - // to ensure that BlobStat updates are serialized. - // Otherwise it may cause stat to fail to get the span for writing - // and throwing exception. - return stat->lock(); + // We must get the lock from BlobStat under the BlobStats lock + // to ensure that BlobStat updates are serialized. + // Otherwise it may cause stat to fail to get the span for writing + // and throwing exception. + return stat->lock(); + } }(); // We need to assume that this insert will reduce max_cap. @@ -701,11 +798,12 @@ struct BlobStoreGCInfo { String toString() const { - return fmt::format("{}. {}. {}. {}. ", + return fmt::format("{}. {}. {}. {}. {}.", toTypeString("Read-Only Blob", 0), toTypeString("No GC Blob", 1), toTypeString("Full GC Blob", 2), - toTypeString("Truncated Blob", 3)); + toTypeString("Truncated Blob", 3), + toTypeString("Big Blob", 4)); } void appendToReadOnlyBlob(const BlobFileId blob_id, double valid_rate) @@ -728,12 +826,18 @@ struct BlobStoreGCInfo blob_gc_info[3].emplace_back(std::make_pair(blob_id, valid_rate)); } + void appendToBigBlob(const BlobFileId blob_id, double valid_rate) + { + blob_gc_info[4].emplace_back(std::make_pair(blob_id, valid_rate)); + } + private: // 1. read only blob // 2. no need gc blob // 3. full gc blob // 4. need truncate blob - std::vector> blob_gc_info[4]; + // 5. big blob + std::vector> blob_gc_info[5]; String toTypeString(const std::string_view prefix, const size_t index) const { @@ -778,6 +882,13 @@ std::vector BlobStore::getGCStats() continue; } + if (stat->isBigBlob()) + { + blobstore_gc_info.appendToBigBlob(stat->id, stat->sm_valid_rate); + LOG_FMT_TRACE(log, "Current [blob_id={}] is big-blob", stat->id); + continue; + } + auto lock = stat->lock(); auto right_margin = stat->smap->getRightMargin(); @@ -1038,35 +1149,6 @@ std::pair BlobStore::BlobStats::getBlobIdFromName(String blo return {INVALID_BLOBFILE_ID, err_msg}; } -std::set BlobStore::BlobStats::getBlobIdsFromDisk(String path) const -{ - std::set blob_ids_on_disk; - - Poco::File store_path(path); - if (!store_path.exists()) - { - return blob_ids_on_disk; - } - - std::vector file_list; - store_path.list(file_list); - - for (const auto & blob_name : file_list) - { - const auto & [blob_id, err_msg] = getBlobIdFromName(blob_name); - if (blob_id != INVALID_BLOBFILE_ID) - { - blob_ids_on_disk.insert(blob_id); - } - else - { - LOG_FMT_INFO(log, "Ignore not blob file [dir={}] [file={}] [err_msg={}]", path, blob_name, err_msg); - } - } - - return blob_ids_on_disk; -} - void BlobStore::BlobStats::restore() { BlobFileId max_restored_file_id = 0; @@ -1129,7 +1211,7 @@ BlobStatPtr BlobStore::BlobStats::createStat(BlobFileId blob_file_id, const std: BlobStatPtr BlobStore::BlobStats::createStatNotChecking(BlobFileId blob_file_id, const std::lock_guard &) { - LOG_FMT_DEBUG(log, "Created a new BlobStat [blob_id={}]", blob_file_id); + LOG_FMT_INFO(log, "Created a new BlobStat [blob_id={}]", blob_file_id); BlobStatPtr stat = std::make_shared( blob_file_id, static_cast(config.spacemap_type.get()), @@ -1140,6 +1222,32 @@ BlobStatPtr BlobStore::BlobStats::createStatNotChecking(BlobFileId blob_file_id, return stat; } +BlobStatPtr BlobStore::BlobStats::createBigStat(BlobFileId blob_file_id, const std::lock_guard & guard) +{ + auto stat = createBigPageStatNotChecking(blob_file_id, guard); + // Roll to the next new blob id + if (blob_file_id == roll_id) + { + roll_id++; + } + + return stat; +} + +BlobStatPtr BlobStore::BlobStats::createBigPageStatNotChecking(BlobFileId blob_file_id, const std::lock_guard &) +{ + LOG_FMT_INFO(log, "Created a new big BlobStat [blob_id={}]", blob_file_id); + BlobStatPtr stat = std::make_shared( + blob_file_id, + SpaceMap::SpaceMapType::SMAP64_BIG, + config.file_limit_size, + BlobStatType::BIG_BLOB); + + PageFileIdAndLevel id_lvl{blob_file_id, 0}; + stats_map[delegator->choosePath(id_lvl)].emplace_back(stat); + return stat; +} + void BlobStore::BlobStats::eraseStat(const BlobStatPtr && stat, const std::lock_guard &) { PageFileIdAndLevel id_lvl{stat->id, 0}; @@ -1199,7 +1307,7 @@ std::pair BlobStore::BlobStats::chooseStat(size_t buf_s for (const auto & stat : stats_iter->second) { auto lock = stat->lock(); // TODO: will it bring performance regression? - if (!stat->isReadOnly() + if (stat->isNormal() && stat->sm_max_caps >= buf_size && stat->sm_valid_rate < smallest_valid_rate) { @@ -1234,6 +1342,11 @@ std::pair BlobStore::BlobStats::chooseStat(size_t buf_s return std::make_pair(stat_ptr, INVALID_BLOBFILE_ID); } +BlobFileId BlobStore::BlobStats::chooseBigStat(const std::lock_guard &) const +{ + return roll_id; +} + BlobStatPtr BlobStore::BlobStats::blobIdToStat(BlobFileId file_id, bool ignore_not_exist) { auto guard = lock(); @@ -1315,7 +1428,7 @@ bool BlobStore::BlobStats::BlobStat::removePosFromStat(BlobFileOffset offset, si sm_valid_size -= buf_size; sm_valid_rate = sm_valid_size * 1.0 / sm_total_size; - return (isReadOnly() && sm_valid_size == 0); + return ((isReadOnly() || isBigBlob()) && sm_valid_size == 0); } void BlobStore::BlobStats::BlobStat::restoreSpaceMap(BlobFileOffset offset, size_t buf_size) diff --git a/dbms/src/Storages/Page/V3/BlobStore.h b/dbms/src/Storages/Page/V3/BlobStore.h index 5aebc0f128d..b5cf5a677f1 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.h +++ b/dbms/src/Storages/Page/V3/BlobStore.h @@ -71,7 +71,11 @@ class BlobStore : private Allocator // Read Only. // Only after heavy GC, BlobFile will change to READ_ONLY type. // After GC remove, empty files will be removed. - READ_ONLY = 2 + READ_ONLY = 2, + + // Big Blob file + // Only used to page size > config.file_limit_size + BIG_BLOB = 3 }; static String blobTypeToString(BlobStatType type) @@ -82,6 +86,8 @@ class BlobStore : private Allocator return "normal"; case BlobStatType::READ_ONLY: return "read only"; + case BlobStatType::BIG_BLOB: + return "big blob"; } return "Invalid"; } @@ -104,18 +110,26 @@ class BlobStore : private Allocator double sm_valid_rate = 1.0; public: - BlobStat(BlobFileId id_, SpaceMap::SpaceMapType sm_type, UInt64 sm_max_caps_) + BlobStat(BlobFileId id_, SpaceMap::SpaceMapType sm_type, UInt64 sm_max_caps_, BlobStatType type_ = BlobStatType::NORMAL) : id(id_) - , type(BlobStatType::NORMAL) + , type(type_) , smap(SpaceMap::createSpaceMap(sm_type, 0, sm_max_caps_)) , sm_max_caps(sm_max_caps_) - {} + { + // Won't create read-only blob by default. + assert(type != BlobStatType::READ_ONLY); + } [[nodiscard]] std::lock_guard lock() { return std::lock_guard(sm_lock); } + bool isNormal() const + { + return type.load() == BlobStatType::NORMAL; + } + bool isReadOnly() const { return type.load() == BlobStatType::READ_ONLY; @@ -126,6 +140,11 @@ class BlobStore : private Allocator type.store(BlobStatType::READ_ONLY); } + bool isBigBlob() const + { + return type.load() == BlobStatType::BIG_BLOB; + } + BlobFileOffset getPosFromStat(size_t buf_size, const std::lock_guard &); bool removePosFromStat(BlobFileOffset offset, size_t buf_size, const std::lock_guard &); @@ -168,7 +187,11 @@ class BlobStore : private Allocator BlobStatPtr createStatNotChecking(BlobFileId blob_file_id, const std::lock_guard &); - BlobStatPtr createStat(BlobFileId blob_file_id, const std::lock_guard &); + BlobStatPtr createStat(BlobFileId blob_file_id, const std::lock_guard & guard); + + BlobStatPtr createBigPageStatNotChecking(BlobFileId blob_file_id, const std::lock_guard &); + + BlobStatPtr createBigStat(BlobFileId blob_file_id, const std::lock_guard & guard); void eraseStat(const BlobStatPtr && stat, const std::lock_guard &); @@ -190,6 +213,8 @@ class BlobStore : private Allocator */ std::pair chooseStat(size_t buf_size, const std::lock_guard &); + BlobFileId chooseBigStat(const std::lock_guard &) const; + BlobStatPtr blobIdToStat(BlobFileId file_id, bool ignore_not_exist = false); std::map> getStats() const @@ -198,8 +223,6 @@ class BlobStore : private Allocator return stats_map; } - std::set getBlobIdsFromDisk(String path) const; - static std::pair getBlobIdFromName(String blob_name); #ifndef DBMS_PUBLIC_GTEST @@ -263,6 +286,8 @@ class BlobStore : private Allocator private: #endif + PageEntriesEdit handleLargeWrite(DB::WriteBatch & wb, const WriteLimiterPtr & write_limiter = nullptr); + BlobFilePtr read(BlobFileId blob_id, BlobFileOffset offset, char * buffers, size_t size, const ReadLimiterPtr & read_limiter = nullptr, bool background = false); /** diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMap.cpp b/dbms/src/Storages/Page/V3/spacemap/SpaceMap.cpp index 7ee9e02ce48..7ec89d174e1 100644 --- a/dbms/src/Storages/Page/V3/spacemap/SpaceMap.cpp +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMap.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -42,13 +43,16 @@ SpaceMapPtr SpaceMap::createSpaceMap(SpaceMapType type, UInt64 start, UInt64 end case SMAP64_STD_MAP: smap = STDMapSpaceMap::create(start, end); break; + case SMAP64_BIG: + smap = BigSpaceMap::create(start, end); + break; default: - throw Exception("Invalid type to create spaceMap", ErrorCodes::LOGICAL_ERROR); + throw Exception(fmt::format("Invalid [type={}] to create spaceMap", static_cast(type)), ErrorCodes::LOGICAL_ERROR); } if (!smap) { - throw Exception("Failed create SpaceMap [type=" + typeToString(type) + "]", ErrorCodes::LOGICAL_ERROR); + throw Exception(fmt::format("Failed create SpaceMap [type={}]", typeToString(type)), ErrorCodes::LOGICAL_ERROR); } return smap; @@ -56,6 +60,11 @@ SpaceMapPtr SpaceMap::createSpaceMap(SpaceMapType type, UInt64 start, UInt64 end bool SpaceMap::checkSpace(UInt64 offset, size_t size) const { + // If we used `SMAP64_BIG`, we won't check the space. + if (type == SMAP64_BIG) + { + return false; + } return (offset < start) || (offset > end) || (offset + size - 1 > end); } @@ -64,13 +73,11 @@ void SpaceMap::logDebugString() LOG_DEBUG(log, toDebugString()); } - bool SpaceMap::markFree(UInt64 offset, size_t length) { if (checkSpace(offset, length)) { - throw Exception("Unmark space out of the limit space.[type=" + typeToString(getType()) - + "] [block=" + DB::toString(offset) + "], [size=" + DB::toString(length) + "]", + throw Exception(fmt::format("Unmark space out of the limit space.[type={}] [block={}], [size={}]", typeToString(getType()), offset, length), ErrorCodes::LOGICAL_ERROR); } @@ -81,8 +88,7 @@ bool SpaceMap::markUsed(UInt64 offset, size_t length) { if (checkSpace(offset, length)) { - throw Exception("Mark space out of the limit space.[type=" + typeToString(getType()) - + "] [block=" + DB::toString(offset) + "], [size=" + DB::toString(length) + "]", + throw Exception(fmt::format("Mark space out of the limit space.[type={}] [block={}], [size={}]", typeToString(getType()), offset, length), ErrorCodes::LOGICAL_ERROR); } @@ -93,8 +99,7 @@ bool SpaceMap::isMarkUsed(UInt64 offset, size_t length) { if (checkSpace(offset, length)) { - throw Exception("Test space out of the limit space.[type=" + typeToString(getType()) - + "] [block=" + DB::toString(offset) + "], [size=" + DB::toString(length) + "]", + throw Exception(fmt::format("Test space out of the limit space.[type={}] [block={}], [size={}]", typeToString(getType()), offset, length), ErrorCodes::LOGICAL_ERROR); } diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h b/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h index 4a0c035cd3f..ae44b608de0 100644 --- a/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h @@ -38,7 +38,8 @@ class SpaceMap { SMAP64_INVALID = 0, SMAP64_RBTREE = 1, - SMAP64_STD_MAP = 2 + SMAP64_STD_MAP = 2, + SMAP64_BIG = 3 // support for writebatch bigger than blobstore.config.file_limit_size }; /** @@ -73,6 +74,7 @@ class SpaceMap /** * Check a span [offset, offset + length) has been used or not. + * Only used in tests * * ret value: * true: This span is used, or some sub span is used @@ -139,6 +141,8 @@ class SpaceMap return "RB-Tree"; case SMAP64_STD_MAP: return "STD Map"; + case SMAP64_BIG: + return "STD Big"; default: return "Invalid"; } diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMapBig.h b/dbms/src/Storages/Page/V3/spacemap/SpaceMapBig.h new file mode 100644 index 00000000000..22128a09f30 --- /dev/null +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMapBig.h @@ -0,0 +1,151 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once +#include +#include +#include +#include + +#include +#include + +namespace DB +{ +namespace ErrorCodes +{ +extern const int LOGICAL_ERROR; +} + +namespace PS::V3 +{ +// A space map that is designed for holding only one large page data (size > blobstore.config.file_limit_size) +class BigSpaceMap + : public SpaceMap + , public ext::SharedPtrHelper +{ +public: + ~BigSpaceMap() override = default; + + bool check(std::function /*checker*/, size_t /*size*/) override + { + // Can't do check + return true; + } + +protected: + BigSpaceMap(UInt64 start, UInt64 end) + : SpaceMap(start, end, SMAP64_BIG) + { + if (start != 0) + { + throw Exception(fmt::format("[start={}] is not zero. We should not use [type=SMAP64_BIG] to do that.", start), // + ErrorCodes::LOGICAL_ERROR); + } + } + + String toDebugString() override + { + FmtBuffer fmt_buffer; + fmt_buffer.append(" BIG-Map entries status: \n"); + fmt_buffer.fmtAppend(" Single Space start: 0 size : {}\n", size_in_used); + + return fmt_buffer.toString(); + } + + std::pair getSizes() const override + { + if (size_in_used == 0) + { + throw Exception("size_in_used is zero. it should not happend", // + ErrorCodes::LOGICAL_ERROR); + } + return std::make_pair(size_in_used, size_in_used); + } + + UInt64 getRightMargin() override + { + return end; + } + + bool isMarkUnused(UInt64 /*offset*/, size_t /*length*/) override + { + return true; + } + + bool markUsedImpl(UInt64 offset, size_t length) override + { + if (offset != 0) + { + throw Exception(fmt::format("[offset={}] is not zero. We should not use [type=SMAP64_BIG] to do that.", offset), // + ErrorCodes::LOGICAL_ERROR); + } + + if (length < end) + { + throw Exception(fmt::format("[length={}] less than [end={}]. We should not use [type=SMAP64_BIG] to do that.", length, end), // + ErrorCodes::LOGICAL_ERROR); + } + + size_in_used = length; + + return true; + } + + std::tuple searchInsertOffset(size_t size) override + { + if (size < end) + { + throw Exception(fmt::format("[size={}] less than [end={}]. We should not use [type=SMAP64_BIG] to do that.", size, end), // + ErrorCodes::LOGICAL_ERROR); + } + size_in_used = size; + + // offset : from start 0 + // max_cap : will be 0 + // is expansion : true(or false, both ok) + return std::make_tuple(0, 0, true); + } + + UInt64 updateAccurateMaxCapacity() override + { + // Won't update max_cap. + return 0; + } + + bool markFreeImpl(UInt64 offset, size_t length) override + { + if (length != size_in_used) + { + throw Exception(fmt::format("[length={}] less than [end={}]. We should not use [type=SMAP64_BIG] to do that.", length, end), // + ErrorCodes::LOGICAL_ERROR); + } + + if (offset != 0) + { + throw Exception(fmt::format("[offset={}] is not zero. We should not use [type=SMAP64_BIG] to do that.", offset), // + ErrorCodes::LOGICAL_ERROR); + } + + return true; + } + +private: + UInt64 size_in_used = 0; +}; + +using BigSpaceMapPtr = std::shared_ptr; + +}; // namespace PS::V3 +}; // namespace DB \ No newline at end of file diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h b/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h index 92c08deb555..b6ff8797f0f 100644 --- a/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h @@ -113,6 +113,10 @@ class STDMapSpaceMap UInt64 getRightMargin() override { + if (free_map.empty()) + { + return end - start; + } return free_map.rbegin()->first; } diff --git a/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp b/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp index adaa4e2ee08..048140ed04f 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp @@ -928,7 +928,8 @@ try } CATCH -TEST_F(BlobStoreTest, testWriteOutOfLimitSize) +// BlobStore allow (page size > blob_file_limit) +TEST_F(BlobStoreTest, DISABLED_testWriteOutOfLimitSize) { const auto file_provider = DB::tests::TiFlashTestEnv::getContext().getFileProvider(); size_t buff_size = 100; @@ -1180,7 +1181,7 @@ TEST_F(BlobStoreTest, GC) const auto & gc_edit = blob_store.gc(gc_context, static_cast(buff_size * buff_nums)); - // Check copy_list which will apply fo Mvcc + // Check copy_list which will apply for Mvcc ASSERT_EQ(gc_edit.size(), buff_nums); auto it = versioned_pageid_entries.begin(); for (const auto & record : gc_edit.getRecords()) @@ -1299,4 +1300,246 @@ try } } CATCH + +TEST_F(BlobStoreTest, TestBigBlob) +try +{ + const auto file_provider = DB::tests::TiFlashTestEnv::getContext().getFileProvider(); + PageId fixed_page_id = 50; + PageId page_id = fixed_page_id; + + BlobStore::Config config_with_small_file_limit_size; + config_with_small_file_limit_size.file_limit_size = 400; + auto blob_store = BlobStore(getCurrentTestName(), file_provider, delegator, config_with_small_file_limit_size); + + // PUT page_id 50 into blob 1 range [0,200] + { + size_t size_200 = 200; + char c_buff[size_200]; + + WriteBatch wb; + ReadBufferPtr buff = std::make_shared(const_cast(c_buff), size_200); + wb.putPage(page_id, /* tag */ 0, buff, size_200); + PageEntriesEdit edit = blob_store.write(wb, nullptr); + + const auto & records = edit.getRecords(); + ASSERT_EQ(records.size(), 1); + ASSERT_EQ(records[0].entry.file_id, 1); + ASSERT_EQ(records[0].entry.offset, 0); + ASSERT_EQ(records[0].entry.size, 200); + + const auto & stat = blob_store.blob_stats.blobIdToStat(1); + ASSERT_TRUE(stat->isNormal()); + ASSERT_EQ(stat->sm_max_caps, 200); + ASSERT_DOUBLE_EQ(stat->sm_valid_rate, 1.0); + ASSERT_EQ(stat->sm_valid_size, 200); + ASSERT_EQ(stat->sm_total_size, 200); + + page_id++; + wb.clear(); + } + + // PUT page_id 51 into blob 2 range [0,500] , stat will be a BIG_BLOB in mem + { + size_t size_500 = 500; + char c_buff[size_500]; + + WriteBatch wb; + ReadBufferPtr buff = std::make_shared(const_cast(c_buff), size_500); + wb.putPage(page_id, /* tag */ 0, buff, size_500); + PageEntriesEdit edit = blob_store.write(wb, nullptr); + + const auto & records = edit.getRecords(); + ASSERT_EQ(records.size(), 1); + ASSERT_EQ(records[0].entry.file_id, 2); + ASSERT_EQ(records[0].entry.offset, 0); + ASSERT_EQ(records[0].entry.size, 500); + + // verify blobstat + const auto & stat = blob_store.blob_stats.blobIdToStat(2); + ASSERT_TRUE(stat->isBigBlob()); + ASSERT_EQ(stat->sm_max_caps, 0); + ASSERT_DOUBLE_EQ(stat->sm_valid_rate, 1.0); + ASSERT_EQ(stat->sm_valid_size, 500); + ASSERT_EQ(stat->sm_total_size, 500); + + // Verify read + Page page = blob_store.read(std::make_pair(buildV3Id(TEST_NAMESPACE_ID, page_id), records[0].entry), nullptr); + ASSERT_TRUE(page.isValid()); + ASSERT_EQ(page.data.size(), size_500); + + page_id++; + wb.clear(); + } + + // PUT page_id 52 into blob 1 range [200,100] + { + size_t size_100 = 100; + char c_buff[size_100]; + + WriteBatch wb; + ReadBufferPtr buff = std::make_shared(const_cast(c_buff), size_100); + wb.putPage(page_id, /* tag */ 0, buff, size_100); + PageEntriesEdit edit = blob_store.write(wb, nullptr); + + const auto & records = edit.getRecords(); + ASSERT_EQ(records.size(), 1); + ASSERT_EQ(records[0].entry.file_id, 1); + ASSERT_EQ(records[0].entry.offset, 200); + ASSERT_EQ(records[0].entry.size, 100); + + const auto & stat = blob_store.blob_stats.blobIdToStat(1); + ASSERT_TRUE(stat->isNormal()); + ASSERT_EQ(stat->sm_max_caps, 100); + ASSERT_DOUBLE_EQ(stat->sm_valid_rate, 1.0); + ASSERT_EQ(stat->sm_valid_size, 300); + ASSERT_EQ(stat->sm_total_size, 300); + + page_id++; + wb.clear(); + } + + // PUT page_id 53 into blob 3 range [0,300] + { + size_t size_300 = 300; + char c_buff[size_300]; + + WriteBatch wb; + ReadBufferPtr buff = std::make_shared(const_cast(c_buff), size_300); + wb.putPage(page_id, /* tag */ 0, buff, size_300); + PageEntriesEdit edit = blob_store.write(wb, nullptr); + + const auto & records = edit.getRecords(); + ASSERT_EQ(records.size(), 1); + ASSERT_EQ(records[0].entry.file_id, 3); + ASSERT_EQ(records[0].entry.offset, 0); + ASSERT_EQ(records[0].entry.size, 300); + + const auto & stat = blob_store.blob_stats.blobIdToStat(3); + ASSERT_TRUE(stat->isNormal()); + ASSERT_EQ(stat->sm_max_caps, 100); + ASSERT_DOUBLE_EQ(stat->sm_valid_rate, 1.0); + ASSERT_EQ(stat->sm_valid_size, 300); + ASSERT_EQ(stat->sm_total_size, 300); + + page_id++; + } + + // Test mix BigBlob + { + char c_buff1[600]; + char c_buff2[10]; + char c_buff3[500]; + char c_buff4[200]; + + WriteBatch wb; + wb.putPage(page_id++, /* tag */ 0, std::make_shared(const_cast(c_buff1), sizeof(c_buff1)), sizeof(c_buff1)); + wb.putPage(page_id++, /* tag */ 0, std::make_shared(const_cast(c_buff2), sizeof(c_buff2)), sizeof(c_buff2)); + wb.putPage(page_id++, /* tag */ 0, std::make_shared(const_cast(c_buff3), sizeof(c_buff3)), sizeof(c_buff3)); + wb.putPage(page_id++, /* tag */ 0, std::make_shared(const_cast(c_buff4), sizeof(c_buff4)), sizeof(c_buff4)); + + PageEntriesEdit edit = blob_store.write(wb, nullptr); + + const auto & records = edit.getRecords(); + ASSERT_EQ(records.size(), 4); + + // PUT page_id 54 into blob 4 range [0,600] + ASSERT_EQ(records[0].page_id.low, 54); + ASSERT_EQ(records[0].entry.file_id, 4); + ASSERT_EQ(records[0].entry.offset, 0); + ASSERT_EQ(records[0].entry.size, 600); + + // PUT page_id 55 into blob 1 or 3 + ASSERT_EQ(records[1].page_id.low, 55); + ASSERT_TRUE(records[1].entry.file_id == 1 || records[1].entry.file_id == 3); + + // PUT page_id 56 into blob 5 range [0,600] + ASSERT_EQ(records[2].page_id.low, 56); + ASSERT_EQ(records[2].entry.file_id, 5); + ASSERT_EQ(records[2].entry.offset, 0); + ASSERT_EQ(records[2].entry.size, 500); + + // PUT page_id 57 into blob 6 range [0,200] + ASSERT_EQ(records[3].page_id.low, 57); + ASSERT_EQ(records[3].entry.file_id, 6); + ASSERT_EQ(records[3].entry.offset, 0); + ASSERT_EQ(records[3].entry.size, 200); + } +} +CATCH + + +TEST_F(BlobStoreTest, TestBigBlobRemove) +try +{ + const auto file_provider = DB::tests::TiFlashTestEnv::getContext().getFileProvider(); + PageId fixed_page_id = 50; + PageId page_id = fixed_page_id; + + BlobStore::Config config_with_small_file_limit_size; + config_with_small_file_limit_size.file_limit_size = 400; + auto blob_store = BlobStore(getCurrentTestName(), file_provider, delegator, config_with_small_file_limit_size); + + { + size_t size_500 = 500; + char c_buff[size_500]; + + WriteBatch wb; + ReadBufferPtr buff = std::make_shared(const_cast(c_buff), size_500); + wb.putPage(page_id, /* tag */ 0, buff, size_500); + PageEntriesEdit edit = blob_store.write(wb, nullptr); + + const auto & gc_info = blob_store.getGCStats(); + ASSERT_TRUE(gc_info.empty()); + + const auto & records = edit.getRecords(); + ASSERT_EQ(records.size(), 1); + blob_store.remove({records[0].entry}); + } +} +CATCH + + +TEST_F(BlobStoreTest, TestBigBlobRegisterPath) +try +{ + const auto file_provider = DB::tests::TiFlashTestEnv::getContext().getFileProvider(); + PageId fixed_page_id = 50; + PageId page_id = fixed_page_id; + + BlobStore::Config config_with_small_file_limit_size; + config_with_small_file_limit_size.file_limit_size = 400; + + PageEntryV3 entry_from_write; + { + auto blob_store = BlobStore(getCurrentTestName(), file_provider, delegator, config_with_small_file_limit_size); + size_t size_500 = 500; + char c_buff[size_500]; + + WriteBatch wb; + ReadBufferPtr buff = std::make_shared(const_cast(c_buff), size_500); + wb.putPage(page_id, /* tag */ 0, buff, size_500); + auto edit = blob_store.write(wb, nullptr); + const auto & records = edit.getRecords(); + ASSERT_EQ(records.size(), 1); + entry_from_write = records[0].entry; + } + + { + auto blob_store = BlobStore(getCurrentTestName(), file_provider, delegator, config_with_small_file_limit_size); + blob_store.registerPaths(); + + const auto & stat = blob_store.blob_stats.blobIdToStat(1); + ASSERT_TRUE(stat->isBigBlob()); + + blob_store.blob_stats.restoreByEntry(entry_from_write); + blob_store.blob_stats.restore(); + ASSERT_EQ(stat->sm_max_caps, 0); + ASSERT_DOUBLE_EQ(stat->sm_valid_rate, 1.0); + ASSERT_EQ(stat->sm_valid_size, 500); + ASSERT_EQ(stat->sm_total_size, 500); + } +} +CATCH + } // namespace DB::PS::V3::tests From 08e2d2d85b2e0671de5eb134783266673cc04307 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Fri, 13 May 2022 19:56:36 +0800 Subject: [PATCH 024/127] Add a force transfrom kvstore from v2 to v3 (#4875) ref pingcap/tiflash#3594 --- .../Storages/Transaction/RegionPersister.cpp | 73 +++++++++++++++++-- .../Storages/Transaction/RegionPersister.h | 2 + 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/dbms/src/Storages/Transaction/RegionPersister.cpp b/dbms/src/Storages/Transaction/RegionPersister.cpp index 77901922e2f..8e6ed6821df 100644 --- a/dbms/src/Storages/Transaction/RegionPersister.cpp +++ b/dbms/src/Storages/Transaction/RegionPersister.cpp @@ -170,6 +170,45 @@ PS::V1::PageStorage::Config getV1PSConfig(const PS::V2::PageStorage::Config & co return c; } +void RegionPersister::forceTransformKVStoreV2toV3() +{ + assert(page_reader != nullptr); + assert(page_writer != nullptr); + + WriteBatch write_batch_del_v2{KVSTORE_NAMESPACE_ID}; + auto meta_transform_acceptor = [&](const DB::Page & page) { + WriteBatch write_batch_transform{KVSTORE_NAMESPACE_ID}; + // Check pages have not contain field offset + // Also get the tag of page_id + const auto & page_transform_entry = page_reader->getPageEntry(page.page_id); + if (!page_transform_entry.field_offsets.empty()) + { + throw Exception(fmt::format("Can't transfrom kvstore from V2 to V3, [page_id={}] {}", + page.page_id, + page_transform_entry.toDebugString()), + ErrorCodes::LOGICAL_ERROR); + } + + write_batch_transform.putPage(page.page_id, // + page_transform_entry.tag, + std::make_shared(page.data.begin(), + page.data.size()), + page.data.size()); + + // Will rewrite into V3 one by one. + // The region data is big. It is not a good idea to combine pages. + page_writer->write(std::move(write_batch_transform), nullptr); + + // Record del page_id + write_batch_del_v2.delPage(page.page_id); + }; + + page_reader->traverse(meta_transform_acceptor, /*only_v2*/ true, /*only_v3*/ false); + + // DEL must call after rewrite. + page_writer->writeIntoV2(std::move(write_batch_del_v2), nullptr); +} + RegionMap RegionPersister::restore(const TiFlashRaftProxyHelper * proxy_helper, PageStorage::Config config) { { @@ -247,18 +286,36 @@ RegionMap RegionPersister::restore(const TiFlashRaftProxyHelper * proxy_helper, page_storage_v2->restore(); page_storage_v3->restore(); - if (page_storage_v2->getNumberOfPages() == 0) - { - page_storage_v2 = nullptr; - run_mode = PageStorageRunMode::ONLY_V3; - page_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, page_storage_v3); - page_reader = std::make_shared(run_mode, ns_id, /*storage_v2_*/ nullptr, page_storage_v3, global_context.getReadLimiter()); - } - else + if (const auto & kvstore_remain_pages = page_storage_v2->getNumberOfPages(); kvstore_remain_pages != 0) { page_writer = std::make_shared(run_mode, page_storage_v2, page_storage_v3); page_reader = std::make_shared(run_mode, ns_id, page_storage_v2, page_storage_v3, global_context.getReadLimiter()); + + LOG_FMT_INFO(log, "Current kvstore transform to V3 begin [pages_before_transform={}]", kvstore_remain_pages); + forceTransformKVStoreV2toV3(); + const auto & kvstore_remain_pages_after_transform = page_storage_v2->getNumberOfPages(); + LOG_FMT_INFO(log, "Current kvstore transfrom to V3 finished. [ns_id={}] [done={}] [pages_before_transform={}] [pages_after_transform={}]", // + ns_id, + kvstore_remain_pages_after_transform == 0, + kvstore_remain_pages, + kvstore_remain_pages_after_transform); + + if (kvstore_remain_pages_after_transform != 0) + { + throw Exception("KVStore transform failed. Still have some data exist in V2", ErrorCodes::LOGICAL_ERROR); + } + } + else // no need do transform + { + LOG_FMT_INFO(log, "Current kvstore translate already done before restored."); } + + // change run_mode to ONLY_V3 + page_storage_v2 = nullptr; + page_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, page_storage_v3); + page_reader = std::make_shared(run_mode, ns_id, /*storage_v2_*/ nullptr, page_storage_v3, global_context.getReadLimiter()); + + run_mode = PageStorageRunMode::ONLY_V3; break; } } diff --git a/dbms/src/Storages/Transaction/RegionPersister.h b/dbms/src/Storages/Transaction/RegionPersister.h index a4f611cbdbb..f2828add202 100644 --- a/dbms/src/Storages/Transaction/RegionPersister.h +++ b/dbms/src/Storages/Transaction/RegionPersister.h @@ -61,6 +61,8 @@ class RegionPersister final : private boost::noncopyable private: #endif + void forceTransformKVStoreV2toV3(); + void doPersist(RegionCacheWriteElement & region_write_buffer, const RegionTaskLock & lock, const Region & region); void doPersist(const Region & region, const RegionTaskLock * lock); From b8e58ebcb741e3a921fc93352de72c5a20a5ba6f Mon Sep 17 00:00:00 2001 From: hehechen Date: Fri, 13 May 2022 20:44:38 +0800 Subject: [PATCH 025/127] add metrics for storage pool run mode (#4881) close pingcap/tiflash#4870 --- dbms/src/Common/CurrentMetrics.cpp | 7 +- dbms/src/Interpreters/Context.cpp | 3 +- dbms/src/Storages/DeltaMerge/StoragePool.cpp | 16 +- dbms/src/Storages/DeltaMerge/StoragePool.h | 2 + .../Storages/Transaction/RegionPersister.cpp | 6 + metrics/grafana/tiflash_summary.json | 283 ++++++++++++++++++ 6 files changed, 314 insertions(+), 3 deletions(-) diff --git a/dbms/src/Common/CurrentMetrics.cpp b/dbms/src/Common/CurrentMetrics.cpp index 06173cc3cc9..8a2f111d882 100644 --- a/dbms/src/Common/CurrentMetrics.cpp +++ b/dbms/src/Common/CurrentMetrics.cpp @@ -78,7 +78,12 @@ M(IOLimiterPendingBgWriteReq) \ M(IOLimiterPendingFgWriteReq) \ M(IOLimiterPendingBgReadReq) \ - M(IOLimiterPendingFgReadReq) + M(IOLimiterPendingFgReadReq) \ + M(StoragePoolV2Only) \ + M(StoragePoolV3Only) \ + M(StoragePoolMixMode) \ + M(RegionPersisterRunMode) \ + M(GlobalStorageRunMode) namespace CurrentMetrics { diff --git a/dbms/src/Interpreters/Context.cpp b/dbms/src/Interpreters/Context.cpp index a2088483c20..1c74b5f402e 100644 --- a/dbms/src/Interpreters/Context.cpp +++ b/dbms/src/Interpreters/Context.cpp @@ -81,6 +81,7 @@ namespace CurrentMetrics { extern const Metric ContextLockWait; extern const Metric MemoryTrackingForMerges; +extern const Metric GlobalStorageRunMode; } // namespace CurrentMetrics @@ -1652,7 +1653,7 @@ bool Context::initializeGlobalStoragePoolIfNeed(const PathPool & path_pool) // GlobalStoragePool may be initialized many times in some test cases for restore. LOG_WARNING(shared->log, "GlobalStoragePool has already been initialized."); } - + CurrentMetrics::set(CurrentMetrics::GlobalStorageRunMode, static_cast(shared->storage_run_mode)); if (shared->storage_run_mode == PageStorageRunMode::MIX_MODE || shared->storage_run_mode == PageStorageRunMode::ONLY_V3) { try diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.cpp b/dbms/src/Storages/DeltaMerge/StoragePool.cpp index f0c3db1e4e8..42be13c9ecf 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.cpp +++ b/dbms/src/Storages/DeltaMerge/StoragePool.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include #include @@ -19,6 +20,14 @@ #include #include + +namespace CurrentMetrics +{ +extern const Metric StoragePoolV2Only; +extern const Metric StoragePoolV3Only; +extern const Metric StoragePoolMixMode; +} // namespace CurrentMetrics + namespace DB { namespace ErrorCodes @@ -30,6 +39,7 @@ namespace FailPoints { extern const char force_set_dtfile_exist_when_acquire_id[]; } // namespace FailPoints + namespace DM { enum class StorageType @@ -159,6 +169,7 @@ StoragePool::StoragePool(Context & global_ctx, NamespaceId ns_id_, StoragePathPo , run_mode(global_ctx.getPageStorageRunMode()) , ns_id(ns_id_) , global_context(global_ctx) + , storage_pool_metrics(CurrentMetrics::StoragePoolV3Only, 0) { const auto & global_storage_pool = global_context.getGlobalStoragePool(); switch (run_mode) @@ -200,7 +211,6 @@ StoragePool::StoragePool(Context & global_ctx, NamespaceId ns_id_, StoragePathPo log_storage_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, log_storage_v3); data_storage_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, data_storage_v3); meta_storage_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, meta_storage_v3); - break; } case PageStorageRunMode::MIX_MODE: @@ -303,6 +313,7 @@ PageStorageRunMode StoragePool::restore() max_data_page_id = data_max_ids[0]; max_meta_page_id = meta_max_ids[0]; + storage_pool_metrics = CurrentMetrics::Increment{CurrentMetrics::StoragePoolV2Only}; break; } case PageStorageRunMode::ONLY_V3: @@ -311,6 +322,7 @@ PageStorageRunMode StoragePool::restore() max_data_page_id = global_storage_pool->getDataMaxId(ns_id); max_meta_page_id = global_storage_pool->getMetaMaxId(ns_id); + storage_pool_metrics = CurrentMetrics::Increment{CurrentMetrics::StoragePoolV3Only}; break; } case PageStorageRunMode::MIX_MODE: @@ -368,12 +380,14 @@ PageStorageRunMode StoragePool::restore() max_meta_page_id = global_storage_pool->getMetaMaxId(ns_id); run_mode = PageStorageRunMode::ONLY_V3; + storage_pool_metrics = CurrentMetrics::Increment{CurrentMetrics::StoragePoolV3Only}; } else // Still running Mix Mode { max_log_page_id = std::max(v2_log_max_ids[0], global_storage_pool->getLogMaxId(ns_id)); max_data_page_id = std::max(v2_data_max_ids[0], global_storage_pool->getDataMaxId(ns_id)); max_meta_page_id = std::max(v2_meta_max_ids[0], global_storage_pool->getMetaMaxId(ns_id)); + storage_pool_metrics = CurrentMetrics::Increment{CurrentMetrics::StoragePoolMixMode}; } break; } diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.h b/dbms/src/Storages/DeltaMerge/StoragePool.h index 14dac73b667..2e3b3f563f5 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.h +++ b/dbms/src/Storages/DeltaMerge/StoragePool.h @@ -235,6 +235,8 @@ class StoragePool : private boost::noncopyable std::atomic max_meta_page_id = 0; BackgroundProcessingPool::TaskHandle gc_handle = nullptr; + + CurrentMetrics::Increment storage_pool_metrics; }; struct StorageSnapshot : private boost::noncopyable diff --git a/dbms/src/Storages/Transaction/RegionPersister.cpp b/dbms/src/Storages/Transaction/RegionPersister.cpp index 8e6ed6821df..154112adfd4 100644 --- a/dbms/src/Storages/Transaction/RegionPersister.cpp +++ b/dbms/src/Storages/Transaction/RegionPersister.cpp @@ -28,6 +28,11 @@ #include +namespace CurrentMetrics +{ +extern const Metric RegionPersisterRunMode; +} + namespace DB { namespace ErrorCodes @@ -320,6 +325,7 @@ RegionMap RegionPersister::restore(const TiFlashRaftProxyHelper * proxy_helper, } } + CurrentMetrics::set(CurrentMetrics::RegionPersisterRunMode, static_cast(run_mode)); LOG_FMT_INFO(log, "RegionPersister running. Current Run Mode is {}", static_cast(run_mode)); } diff --git a/metrics/grafana/tiflash_summary.json b/metrics/grafana/tiflash_summary.json index a1c75e7c04e..5b534e27f01 100644 --- a/metrics/grafana/tiflash_summary.json +++ b/metrics/grafana/tiflash_summary.json @@ -7390,6 +7390,289 @@ ], "title": "Rough Set Filter Rate Histogram", "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 119, + "panels": [ + { + "datasource": "${DS_TEST-CLUSTER}", + "description": "The Global StoragePool and KVStore Runmode", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 5, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 11, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false + }, + "decimals": 0, + "mappings": [ + { + "from": "", + "id": 1, + "text": "ONLY_V2", + "to": "", + "type": 1, + "value": "1" + }, + { + "from": "", + "id": 2, + "text": "ONLY_V3", + "to": "", + "type": 1, + "value": "2" + }, + { + "from": "", + "id": 3, + "text": "MIX_MODE", + "to": "", + "type": 1, + "value": "3" + }, + { + "from": "", + "id": 4, + "text": " ", + "to": "", + "type": 1, + "value": "4" + }, + { + "from": "", + "id": 5, + "text": " ", + "to": "", + "type": 1, + "value": "5" + } + ], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 126, + "links": [], + "options": { + "graph": {}, + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right" + }, + "tooltipOptions": { + "mode": "multi" + } + }, + "pluginVersion": "7.5.11", + "targets": [ + { + "exemplar": true, + "expr": "tiflash_system_current_metric_GlobalStorageRunMode{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{instance}}-GlobalRunMode", + "refId": "A", + "step": 10 + }, + { + "exemplar": false, + "expr": "tiflash_system_current_metric_RegionPersisterRunMode{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "{{instance}}-KVStoreRunMode", + "refId": "B" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Global Runmode", + "type": "timeseries" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_TEST-CLUSTER}", + "decimals": 1, + "description": "The StoragePool Runmode in DeltaMerge Storage", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "grid": {}, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 9 + }, + "hiddenSeries": false, + "id": 123, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": null, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.11", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(tiflash_system_current_metric_StoragePoolV2Only{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}) by (instance)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{instance}}-OnlyV2", + "refId": "A", + "step": 10 + }, + { + "exemplar": true, + "expr": "sum(tiflash_system_current_metric_StoragePoolV3Only{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}) by (instance)", + "hide": false, + "interval": "", + "legendFormat": "{{instance}}-OnlyV3", + "refId": "B" + }, + { + "exemplar": true, + "expr": "sum(tiflash_system_current_metric_StoragePoolMixMode{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}) by (instance)", + "hide": false, + "interval": "", + "legendFormat": "{{instance}}-MixMode", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "StoragePool Runmode", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:650", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:651", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "title": "StoragePool", + "type": "row" } ], "refresh": false, From 2bff33cbb4323e9af308e47ef450f71d7db6c27a Mon Sep 17 00:00:00 2001 From: Calvin Neo Date: Mon, 16 May 2022 14:40:37 +0800 Subject: [PATCH 026/127] update proxy (#4894) close pingcap/tiflash#4618 --- contrib/tiflash-proxy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tiflash-proxy b/contrib/tiflash-proxy index e46e88799f3..e98f9597078 160000 --- a/contrib/tiflash-proxy +++ b/contrib/tiflash-proxy @@ -1 +1 @@ -Subproject commit e46e88799f383bbd10d6508da74a625053ab78e5 +Subproject commit e98f95970781e3b70793f8c90e7c7022e8354ef8 From e9227b1cc1ef9b2f8809bbd0f07dca9992bf1c50 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Mon, 16 May 2022 19:10:37 +0800 Subject: [PATCH 027/127] Fix nullptr when mix mode change to only v3. (#4898) close pingcap/tiflash#4893 --- dbms/src/Storages/DeltaMerge/StoragePool.cpp | 22 +++++++++---------- .../Storages/Transaction/RegionPersister.cpp | 6 +++-- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.cpp b/dbms/src/Storages/DeltaMerge/StoragePool.cpp index 42be13c9ecf..70e4b08ea14 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.cpp +++ b/dbms/src/Storages/DeltaMerge/StoragePool.cpp @@ -357,23 +357,23 @@ PageStorageRunMode StoragePool::restore() // If V2 already have no any data in disk, Then change run_mode to ONLY_V3 if (log_storage_v2->getNumberOfPages() == 0 && data_storage_v2->getNumberOfPages() == 0 && meta_storage_v2->getNumberOfPages() == 0) { - // TODO: when `compaction` happend - // 1. Will rewrite meta into V3 by DT. - // 2. Need `DEL` meta in V2. - // 3. Need drop V2 in disk and check. - LOG_FMT_INFO(logger, "Current pagestorage change from {} to {}", static_cast(PageStorageRunMode::MIX_MODE), static_cast(PageStorageRunMode::ONLY_V3)); + // TODO: Need drop V2 in disk and check. + LOG_FMT_INFO(logger, "Current pagestorage change from {} to {}", // + static_cast(PageStorageRunMode::MIX_MODE), + static_cast(PageStorageRunMode::ONLY_V3)); log_storage_v2 = nullptr; data_storage_v2 = nullptr; meta_storage_v2 = nullptr; - log_storage_reader = std::make_shared(run_mode, ns_id, /*storage_v2_*/ nullptr, log_storage_v3, nullptr); - data_storage_reader = std::make_shared(run_mode, ns_id, /*storage_v2_*/ nullptr, data_storage_v3, nullptr); - meta_storage_reader = std::make_shared(run_mode, ns_id, /*storage_v2_*/ nullptr, meta_storage_v3, nullptr); + // Must init by PageStorageRunMode::ONLY_V3 + log_storage_reader = std::make_shared(PageStorageRunMode::ONLY_V3, ns_id, /*storage_v2_*/ nullptr, log_storage_v3, nullptr); + data_storage_reader = std::make_shared(PageStorageRunMode::ONLY_V3, ns_id, /*storage_v2_*/ nullptr, data_storage_v3, nullptr); + meta_storage_reader = std::make_shared(PageStorageRunMode::ONLY_V3, ns_id, /*storage_v2_*/ nullptr, meta_storage_v3, nullptr); - log_storage_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, log_storage_v3); - data_storage_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, data_storage_v3); - meta_storage_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, meta_storage_v3); + log_storage_writer = std::make_shared(PageStorageRunMode::ONLY_V3, /*storage_v2_*/ nullptr, log_storage_v3); + data_storage_writer = std::make_shared(PageStorageRunMode::ONLY_V3, /*storage_v2_*/ nullptr, data_storage_v3); + meta_storage_writer = std::make_shared(PageStorageRunMode::ONLY_V3, /*storage_v2_*/ nullptr, meta_storage_v3); max_log_page_id = global_storage_pool->getLogMaxId(ns_id); max_data_page_id = global_storage_pool->getDataMaxId(ns_id); diff --git a/dbms/src/Storages/Transaction/RegionPersister.cpp b/dbms/src/Storages/Transaction/RegionPersister.cpp index 154112adfd4..c3db88daece 100644 --- a/dbms/src/Storages/Transaction/RegionPersister.cpp +++ b/dbms/src/Storages/Transaction/RegionPersister.cpp @@ -317,8 +317,10 @@ RegionMap RegionPersister::restore(const TiFlashRaftProxyHelper * proxy_helper, // change run_mode to ONLY_V3 page_storage_v2 = nullptr; - page_writer = std::make_shared(run_mode, /*storage_v2_*/ nullptr, page_storage_v3); - page_reader = std::make_shared(run_mode, ns_id, /*storage_v2_*/ nullptr, page_storage_v3, global_context.getReadLimiter()); + + // Must use PageStorageRunMode::ONLY_V3 here. + page_writer = std::make_shared(PageStorageRunMode::ONLY_V3, /*storage_v2_*/ nullptr, page_storage_v3); + page_reader = std::make_shared(PageStorageRunMode::ONLY_V3, ns_id, /*storage_v2_*/ nullptr, page_storage_v3, global_context.getReadLimiter()); run_mode = PageStorageRunMode::ONLY_V3; break; From 07f39f99645822cf5b36d1d00626e684900e39b6 Mon Sep 17 00:00:00 2001 From: hehechen Date: Mon, 16 May 2022 21:22:37 +0800 Subject: [PATCH 028/127] Revert "MinMax Index Supports Nullable DataType" (#4895) ref pingcap/tiflash#4787 --- dbms/src/DataTypes/IDataType.h | 1 + .../Storages/DeltaMerge/File/DMFileWriter.cpp | 5 +- .../Storages/DeltaMerge/Index/MinMaxIndex.cpp | 191 +----------------- .../Storages/DeltaMerge/Index/MinMaxIndex.h | 3 - .../tests/gtest_dm_minmax_index.cpp | 82 +------- 5 files changed, 18 insertions(+), 264 deletions(-) diff --git a/dbms/src/DataTypes/IDataType.h b/dbms/src/DataTypes/IDataType.h index 71fda0615e4..120d0b1ba30 100644 --- a/dbms/src/DataTypes/IDataType.h +++ b/dbms/src/DataTypes/IDataType.h @@ -471,6 +471,7 @@ class IDataType : private boost::noncopyable virtual bool isEnum() const { return false; }; virtual bool isNullable() const { return false; } + /** Is this type can represent only NULL value? (It also implies isNullable) */ virtual bool onlyNull() const { return false; } diff --git a/dbms/src/Storages/DeltaMerge/File/DMFileWriter.cpp b/dbms/src/Storages/DeltaMerge/File/DMFileWriter.cpp index 424e3f1b0c1..3bff05ef19f 100644 --- a/dbms/src/Storages/DeltaMerge/File/DMFileWriter.cpp +++ b/dbms/src/Storages/DeltaMerge/File/DMFileWriter.cpp @@ -72,9 +72,10 @@ DMFileWriter::DMFileWriter(const DMFilePtr & dmfile_, for (auto & cd : write_columns) { // TODO: currently we only generate index for Integers, Date, DateTime types, and this should be configurable by user. + // TODO: If column type is nullable, we won't generate index for it /// for handle column always generate index - auto type = removeNullable(cd.type); - bool do_index = cd.id == EXTRA_HANDLE_COLUMN_ID || type->isInteger() || type->isDateOrDateTime(); + bool do_index = cd.id == EXTRA_HANDLE_COLUMN_ID || cd.type->isInteger() || cd.type->isDateOrDateTime(); + if (options.flags.isSingleFile()) { if (do_index) diff --git a/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.cpp b/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.cpp index ef42a036e1a..2681284948c 100644 --- a/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.cpp +++ b/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.cpp @@ -61,6 +61,7 @@ inline std::pair minmax(const IColumn & column, const ColumnVect void MinMaxIndex::addPack(const IColumn & column, const ColumnVector * del_mark) { + const IColumn * column_ptr = &column; auto size = column.size(); bool has_null = false; if (column.isColumnNullable()) @@ -69,6 +70,7 @@ void MinMaxIndex::addPack(const IColumn & column, const ColumnVector * de const auto & nullable_column = static_cast(column); const auto & null_mark_data = nullable_column.getNullMapColumn().getData(); + column_ptr = &nullable_column.getNestedColumn(); for (size_t i = 0; i < size; ++i) { @@ -80,13 +82,14 @@ void MinMaxIndex::addPack(const IColumn & column, const ColumnVector * de } } - auto [min_index, max_index] = details::minmax(column, del_mark, 0, column.size()); + const IColumn & updated_column = *column_ptr; + auto [min_index, max_index] = details::minmax(updated_column, del_mark, 0, updated_column.size()); if (min_index != NONE_EXIST) { has_null_marks->push_back(has_null); has_value_marks->push_back(1); - minmaxes->insertFrom(column, min_index); - minmaxes->insertFrom(column, max_index); + minmaxes->insertFrom(updated_column, min_index); + minmaxes->insertFrom(updated_column, max_index); } else { @@ -155,64 +158,6 @@ std::pair MinMaxIndex::getUInt64MinMax(size_t pack_index) return {minmaxes->get64(pack_index * 2), minmaxes->get64(pack_index * 2 + 1)}; } -RSResult MinMaxIndex::checkNullableEqual(size_t pack_index, const Field & value, const DataTypePtr & type) -{ - const ColumnNullable & column_nullable = static_cast(*minmaxes); - - const auto * raw_type = type.get(); - - // if minmaxes_data has null value, the value of minmaxes_data[i] is meaningless and maybe just some random value. - // But in checkEqual, we have checked the has_null_marks and ensured that there is no null value in MinMax Indexes. -#define DISPATCH(TYPE) \ - if (typeid_cast(raw_type)) \ - { \ - auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); \ - auto min = minmaxes_data[pack_index * 2]; \ - auto max = minmaxes_data[pack_index * 2 + 1]; \ - return RoughCheck::checkEqual(value, type, min, max); \ - } - FOR_NUMERIC_TYPES(DISPATCH) -#undef DISPATCH - if (typeid_cast(raw_type)) - { - const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); - auto min = minmaxes_data[pack_index * 2]; - auto max = minmaxes_data[pack_index * 2 + 1]; - return RoughCheck::checkEqual(value, type, min, max); - } - if (typeid_cast(raw_type)) - { - const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); - auto min = minmaxes_data[pack_index * 2]; - auto max = minmaxes_data[pack_index * 2 + 1]; - return RoughCheck::checkEqual(value, type, min, max); - } - if (typeid_cast(raw_type) || typeid_cast(raw_type)) - { - // For DataTypeMyDateTime / DataTypeMyDate, simply compare them as comparing UInt64 is OK. - // Check `struct MyTimeBase` for more details. - const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); - auto min = minmaxes_data[pack_index * 2]; - auto max = minmaxes_data[pack_index * 2 + 1]; - return RoughCheck::checkEqual(value, type, min, max); - } - if (typeid_cast(raw_type)) - { - const auto * string_column = checkAndGetColumn(column_nullable.getNestedColumnPtr().get()); - const auto & chars = string_column->getChars(); - const auto & offsets = string_column->getOffsets(); - size_t pos = pack_index * 2; - size_t prev_offset = pos == 0 ? 0 : offsets[pos - 1]; - // todo use StringRef instead of String - auto min = String(chars[prev_offset], offsets[pos] - prev_offset - 1); - pos = pack_index * 2 + 1; - prev_offset = offsets[pos - 1]; - auto max = String(chars[prev_offset], offsets[pos] - prev_offset - 1); - return RoughCheck::checkEqual(value, type, min, max); - } - return RSResult::Some; -} - RSResult MinMaxIndex::checkEqual(size_t pack_index, const Field & value, const DataTypePtr & type) { if ((*has_null_marks)[pack_index] || value.isNull()) @@ -221,10 +166,6 @@ RSResult MinMaxIndex::checkEqual(size_t pack_index, const Field & value, const D return RSResult::None; const auto * raw_type = type.get(); - if (typeid_cast(raw_type)) - { - return checkNullableEqual(pack_index, value, removeNullable(type)); - } #define DISPATCH(TYPE) \ if (typeid_cast(raw_type)) \ { \ @@ -274,62 +215,6 @@ RSResult MinMaxIndex::checkEqual(size_t pack_index, const Field & value, const D } return RSResult::Some; } - -RSResult MinMaxIndex::checkNullableGreater(size_t pack_index, const Field & value, const DataTypePtr & type) -{ - const ColumnNullable & column_nullable = static_cast(*minmaxes); - const auto * raw_type = type.get(); - -#define DISPATCH(TYPE) \ - if (typeid_cast(raw_type)) \ - { \ - auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); \ - auto min = minmaxes_data[pack_index * 2]; \ - auto max = minmaxes_data[pack_index * 2 + 1]; \ - return RoughCheck::checkGreater(value, type, min, max); \ - } - FOR_NUMERIC_TYPES(DISPATCH) -#undef DISPATCH - if (typeid_cast(raw_type)) - { - const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); - auto min = minmaxes_data[pack_index * 2]; - auto max = minmaxes_data[pack_index * 2 + 1]; - return RoughCheck::checkGreater(value, type, min, max); - } - if (typeid_cast(raw_type)) - { - const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); - auto min = minmaxes_data[pack_index * 2]; - auto max = minmaxes_data[pack_index * 2 + 1]; - return RoughCheck::checkGreater(value, type, min, max); - } - if (typeid_cast(raw_type) || typeid_cast(raw_type)) - { - // For DataTypeMyDateTime / DataTypeMyDate, simply compare them as comparing UInt64 is OK. - // Check `struct MyTimeBase` for more details. - const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); - auto min = minmaxes_data[pack_index * 2]; - auto max = minmaxes_data[pack_index * 2 + 1]; - return RoughCheck::checkGreater(value, type, min, max); - } - if (typeid_cast(raw_type)) - { - const auto * string_column = checkAndGetColumn(column_nullable.getNestedColumnPtr().get()); - const auto & chars = string_column->getChars(); - const auto & offsets = string_column->getOffsets(); - size_t pos = pack_index * 2; - size_t prev_offset = pos == 0 ? 0 : offsets[pos - 1]; - // todo use StringRef instead of String - auto min = String(chars[prev_offset], offsets[pos] - prev_offset - 1); - pos = pack_index * 2 + 1; - prev_offset = offsets[pos - 1]; - auto max = String(chars[prev_offset], offsets[pos] - prev_offset - 1); - return RoughCheck::checkGreater(value, type, min, max); - } - return RSResult::Some; -} - RSResult MinMaxIndex::checkGreater(size_t pack_index, const Field & value, const DataTypePtr & type, int /*nan_direction_hint*/) { if ((*has_null_marks)[pack_index] || value.isNull()) @@ -338,10 +223,6 @@ RSResult MinMaxIndex::checkGreater(size_t pack_index, const Field & value, const return RSResult::None; const auto * raw_type = type.get(); - if (typeid_cast(raw_type)) - { - return checkNullableGreater(pack_index, value, removeNullable(type)); - } #define DISPATCH(TYPE) \ if (typeid_cast(raw_type)) \ { \ @@ -391,62 +272,6 @@ RSResult MinMaxIndex::checkGreater(size_t pack_index, const Field & value, const } return RSResult::Some; } - -RSResult MinMaxIndex::checkNullableGreaterEqual(size_t pack_index, const Field & value, const DataTypePtr & type) -{ - const ColumnNullable & column_nullable = static_cast(*minmaxes); - - const auto * raw_type = type.get(); -#define DISPATCH(TYPE) \ - if (typeid_cast(raw_type)) \ - { \ - auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); \ - auto min = minmaxes_data[pack_index * 2]; \ - auto max = minmaxes_data[pack_index * 2 + 1]; \ - return RoughCheck::checkGreaterEqual(value, type, min, max); \ - } - FOR_NUMERIC_TYPES(DISPATCH) -#undef DISPATCH - if (typeid_cast(raw_type)) - { - const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); - auto min = minmaxes_data[pack_index * 2]; - auto max = minmaxes_data[pack_index * 2 + 1]; - return RoughCheck::checkGreaterEqual(value, type, min, max); - } - if (typeid_cast(raw_type)) - { - const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); - auto min = minmaxes_data[pack_index * 2]; - auto max = minmaxes_data[pack_index * 2 + 1]; - return RoughCheck::checkGreaterEqual(value, type, min, max); - } - if (typeid_cast(raw_type) || typeid_cast(raw_type)) - { - // For DataTypeMyDateTime / DataTypeMyDate, simply compare them as comparing UInt64 is OK. - // Check `struct MyTimeBase` for more details. - const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); - auto min = minmaxes_data[pack_index * 2]; - auto max = minmaxes_data[pack_index * 2 + 1]; - return RoughCheck::checkGreaterEqual(value, type, min, max); - } - if (typeid_cast(raw_type)) - { - const auto * string_column = checkAndGetColumn(column_nullable.getNestedColumnPtr().get()); - const auto & chars = string_column->getChars(); - const auto & offsets = string_column->getOffsets(); - size_t pos = pack_index * 2; - size_t prev_offset = pos == 0 ? 0 : offsets[pos - 1]; - // todo use StringRef instead of String - auto min = String(reinterpret_cast(&chars[prev_offset]), offsets[pos] - prev_offset - 1); - pos = pack_index * 2 + 1; - prev_offset = offsets[pos - 1]; - auto max = String(reinterpret_cast(&chars[prev_offset]), offsets[pos] - prev_offset - 1); - return RoughCheck::checkGreaterEqual(value, type, min, max); - } - return RSResult::Some; -} - RSResult MinMaxIndex::checkGreaterEqual(size_t pack_index, const Field & value, const DataTypePtr & type, int /*nan_direction_hint*/) { if ((*has_null_marks)[pack_index] || value.isNull()) @@ -455,10 +280,6 @@ RSResult MinMaxIndex::checkGreaterEqual(size_t pack_index, const Field & value, return RSResult::None; const auto * raw_type = type.get(); - if (typeid_cast(raw_type)) - { - return checkNullableGreaterEqual(pack_index, value, removeNullable(type)); - } #define DISPATCH(TYPE) \ if (typeid_cast(raw_type)) \ { \ diff --git a/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.h b/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.h index 73284333c73..7efd37fafa4 100644 --- a/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.h +++ b/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.h @@ -81,9 +81,6 @@ class MinMaxIndex RSResult checkGreaterEqual(size_t pack_index, const Field & value, const DataTypePtr & type, int nan_direction); static String toString(); - RSResult checkNullableEqual(size_t pack_index, const Field & value, const DataTypePtr & type); - RSResult checkNullableGreater(size_t pack_index, const Field & value, const DataTypePtr & type); - RSResult checkNullableGreaterEqual(size_t pack_index, const Field & value, const DataTypePtr & type); }; diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp index 460d42828d5..31fd99faf01 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp @@ -214,6 +214,14 @@ try ASSERT_EQ(true, checkMatch(case_name, *context, "MyDateTime", "2020-09-27", createLessEqual(attr("MyDateTime"), parseMyDateTime("2020-09-27"), 0))); ASSERT_EQ(false, checkMatch(case_name, *context, "MyDateTime", "2020-09-27", createLessEqual(attr("MyDateTime"), parseMyDateTime("2020-09-26"), 0))); + /// Currently we don't do filtering for null values. i.e. if a pack contains any null values, then the pack will pass the filter. + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createEqual(attr("Nullable(Int64)"), Field((Int64)101)))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createIn(attr("Nullable(Int64)"), {Field((Int64)101)}))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createGreater(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)101), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createLess(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createLessEqual(attr("Nullable(Int64)"), Field((Int64)99), 0))); + ASSERT_EQ(false, checkDelMatch(case_name, *context, "Int64", "100", createEqual(attr("Int64"), Field((Int64)100)))); ASSERT_EQ(true, checkPkMatch(case_name, *context, "Int64", "100", createEqual(pkAttr(), Field((Int64)100)), true)); ASSERT_EQ(true, checkPkMatch(case_name, *context, "Int64", "100", createGreater(pkAttr(), Field((Int64)99), 0), true)); @@ -228,80 +236,6 @@ try } CATCH -TEST_F(DMMinMaxIndexTest, NullableToNullable) -try -{ - const auto * case_name = ::testing::UnitTest::GetInstance()->current_test_info()->name(); - // clang-format off - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createEqual(attr("Nullable(Int64)"), Field((Int64)101)))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createEqual(attr("Nullable(Int64)"), Field((Int64)100)))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createIn(attr("Nullable(Int64)"), {Field((Int64)100)}))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createIn(attr("Nullable(Int64)"), {Field((Int64)101)}))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createGreater(attr("Nullable(Int64)"), Field((Int64)99), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createGreater(attr("Nullable(Int64)"), Field((Int64)100), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)100), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)101), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createLess(attr("Nullable(Int64)"), Field((Int64)101), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createLess(attr("Nullable(Int64)"), Field((Int64)100), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createLessEqual(attr("Nullable(Int64)"), Field((Int64)100), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createLessEqual(attr("Nullable(Int64)"), Field((Int64)99), 0))); - - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createEqual(attr("Nullable(Date)"), Field((String) "2020-09-27")))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createEqual(attr("Nullable(Date)"), Field((String) "2020-09-28")))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createIn(attr("Nullable(Date)"), {Field((String) "2020-09-27")}))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createIn(attr("Nullable(Date)"), {Field((String) "2020-09-28")}))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createGreater(attr("Nullable(Date)"), Field((String) "2020-09-26"), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createGreater(attr("Nullable(Date)"), Field((String) "2020-09-27"), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createGreaterEqual(attr("Nullable(Date)"), Field((String) "2020-09-27"), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createGreaterEqual(attr("Nullable(Date)"), Field((String) "2020-09-28"), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createLess(attr("Nullable(Date)"), Field((String) "2020-09-28"), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createLess(attr("Nullable(Date)"), Field((String) "2020-09-27"), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createLessEqual(attr("Nullable(Date)"), Field((String) "2020-09-27"), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createLessEqual(attr("Nullable(Date)"), Field((String) "2020-09-26"), 0))); - - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01")))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:02")))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createIn(attr("Nullable(DateTime)"), {Field((String) "2020-01-01 05:00:01")}))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createIn(attr("Nullable(DateTime)"), {Field((String) "2020-01-01 05:00:02")}))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createGreater(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:00"), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createGreater(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01"), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createGreaterEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01"), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createGreaterEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:02"), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createLess(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:02"), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createLess(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01"), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createLessEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01"), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createLessEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:00"), 0))); - - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27")))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-28")))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createIn(attr("Nullable(MyDateTime)"), {parseMyDateTime("2020-09-27")}))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createIn(attr("Nullable(MyDateTime)"), {parseMyDateTime("2020-09-28")}))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createGreater(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-26"), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createGreater(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27"), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createGreaterEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27"), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createGreaterEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-28"), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createLess(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-28"), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createLess(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27"), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createLessEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27"), 0))); - ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createLessEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-26"), 0))); - - // has null - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createEqual(attr("Nullable(Int64)"), Field((Int64)101)))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createIn(attr("Nullable(Int64)"), {Field((Int64)101)}))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createGreater(attr("Nullable(Int64)"), Field((Int64)100), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)101), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createLess(attr("Nullable(Int64)"), Field((Int64)100), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createLessEqual(attr("Nullable(Int64)"), Field((Int64)99), 0))); - - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createEqual(attr("Nullable(Int64)"), Field((Int64)101)))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createIn(attr("Nullable(Int64)"), {Field((Int64)101)}))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createGreater(attr("Nullable(Int64)"), Field((Int64)100), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)101), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createLess(attr("Nullable(Int64)"), Field((Int64)100), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createLessEqual(attr("Nullable(Int64)"), Field((Int64)99), 0))); -} -CATCH - TEST_F(DMMinMaxIndexTest, Logical) try { From e03dcc1e38e14906e7598c7e8775a2660e9ab58a Mon Sep 17 00:00:00 2001 From: hzh0425 <642256541@qq.com> Date: Mon, 16 May 2022 22:56:37 +0800 Subject: [PATCH 029/127] Delete config 'disable_bg_flush ' (#4869) close pingcap/tiflash#4641 --- dbms/src/Server/DTTool/DTTool.h | 1 - dbms/src/Server/RaftConfigParser.cpp | 23 ------ dbms/src/Server/RaftConfigParser.h | 1 - .../Transaction/BackgroundService.cpp | 75 ++----------------- .../Storages/Transaction/BackgroundService.h | 7 -- dbms/src/Storages/Transaction/KVStore.cpp | 69 ++++++++--------- dbms/src/Storages/Transaction/Region.cpp | 9 +-- dbms/src/Storages/Transaction/RegionTable.cpp | 18 +---- dbms/src/Storages/Transaction/TMTContext.cpp | 1 - dbms/src/Storages/Transaction/TMTContext.h | 4 - dbms/src/TestUtils/TiFlashTestEnv.cpp | 1 - 11 files changed, 41 insertions(+), 168 deletions(-) diff --git a/dbms/src/Server/DTTool/DTTool.h b/dbms/src/Server/DTTool/DTTool.h index 4f9db03892d..911c29bf98b 100644 --- a/dbms/src/Server/DTTool/DTTool.h +++ b/dbms/src/Server/DTTool/DTTool.h @@ -308,7 +308,6 @@ class ImitativeEnv raft_config.ignore_databases = {"default", "system"}; raft_config.engine = TiDB::StorageEngine::DT; - raft_config.disable_bg_flush = true; global_context->createTMTContext(raft_config, pingcap::ClusterConfig()); global_context->setDeltaIndexManager(1024 * 1024 * 100 /*100MB*/); diff --git a/dbms/src/Server/RaftConfigParser.cpp b/dbms/src/Server/RaftConfigParser.cpp index c6fa510dde8..8e146dd842e 100644 --- a/dbms/src/Server/RaftConfigParser.cpp +++ b/dbms/src/Server/RaftConfigParser.cpp @@ -82,29 +82,6 @@ TiFlashRaftConfig TiFlashRaftConfig::parseSettings(Poco::Util::LayeredConfigurat res.engine = DEFAULT_ENGINE; } - /// "tmt" engine ONLY support disable_bg_flush = false. - /// "dt" engine ONLY support disable_bg_flush = true. - - String disable_bg_flush_conf = "raft.disable_bg_flush"; - if (res.engine == ::TiDB::StorageEngine::TMT) - { - if (config.has(disable_bg_flush_conf) && config.getBool(disable_bg_flush_conf)) - throw Exception( - fmt::format("Illegal arguments: disable background flush while using engine {}", MutableSupport::txn_storage_name), - ErrorCodes::INVALID_CONFIG_PARAMETER); - res.disable_bg_flush = false; - } - else if (res.engine == ::TiDB::StorageEngine::DT) - { - /// If background flush is enabled, read will not triggle schema sync. - /// Which means that we may get the wrong result with outdated schema. - if (config.has(disable_bg_flush_conf) && !config.getBool(disable_bg_flush_conf)) - throw Exception( - fmt::format("Illegal arguments: enable background flush while using engine {}", MutableSupport::delta_tree_storage_name), - ErrorCodes::INVALID_CONFIG_PARAMETER); - res.disable_bg_flush = true; - } - // just for test if (config.has("raft.enable_compatible_mode")) { diff --git a/dbms/src/Server/RaftConfigParser.h b/dbms/src/Server/RaftConfigParser.h index d2cb7a333c2..0eb78ba20a8 100644 --- a/dbms/src/Server/RaftConfigParser.h +++ b/dbms/src/Server/RaftConfigParser.h @@ -40,7 +40,6 @@ struct TiFlashRaftConfig bool enable_compatible_mode = true; static constexpr TiDB::StorageEngine DEFAULT_ENGINE = TiDB::StorageEngine::DT; - bool disable_bg_flush = false; TiDB::StorageEngine engine = DEFAULT_ENGINE; TiDB::SnapshotApplyMethod snapshot_apply_method = TiDB::SnapshotApplyMethod::DTFile_Directory; diff --git a/dbms/src/Storages/Transaction/BackgroundService.cpp b/dbms/src/Storages/Transaction/BackgroundService.cpp index e751575575b..54abf29db64 100644 --- a/dbms/src/Storages/Transaction/BackgroundService.cpp +++ b/dbms/src/Storages/Transaction/BackgroundService.cpp @@ -36,65 +36,12 @@ BackgroundService::BackgroundService(TMTContext & tmt_) }, false); - if (!tmt.isBgFlushDisabled()) - { - table_flush_handle = background_pool.addTask([this] { - RegionTable & region_table = tmt.getRegionTable(); - - return region_table.tryFlushRegions(); - }); - - region_handle = background_pool.addTask([this] { - bool ok = false; - - { - RegionPtr region = nullptr; - { - std::lock_guard lock(region_mutex); - if (!regions_to_flush.empty()) - { - auto it = regions_to_flush.begin(); - region = it->second; - regions_to_flush.erase(it); - ok = true; - } - } - if (region) - tmt.getRegionTable().tryFlushRegion(region, true); - } - return ok; - }); - - { - std::vector regions; - tmt.getKVStore()->traverseRegions([®ions](RegionID, const RegionPtr & region) { - if (region->dataSize()) - regions.emplace_back(region); - }); - } - } - else - { - LOG_FMT_INFO(log, "Configuration raft.disable_bg_flush is set to true, background flush tasks are disabled."); - auto & global_settings = tmt.getContext().getSettingsRef(); - storage_gc_handle = background_pool.addTask( - [this] { return tmt.getGCManager().work(); }, - false, - /*interval_ms=*/global_settings.dt_bg_gc_check_interval * 1000); - LOG_FMT_INFO(log, "Start background storage gc worker with interval {} seconds.", global_settings.dt_bg_gc_check_interval); - } -} - -void BackgroundService::addRegionToFlush(const DB::RegionPtr & region) -{ - if (tmt.isBgFlushDisabled()) - throw Exception("Try to addRegionToFlush while background flush is disabled.", ErrorCodes::LOGICAL_ERROR); - - { - std::lock_guard lock(region_mutex); - regions_to_flush.emplace(region->id(), region); - } - region_handle->wake(); + auto & global_settings = tmt.getContext().getSettingsRef(); + storage_gc_handle = background_pool.addTask( + [this] { return tmt.getGCManager().work(); }, + false, + /*interval_ms=*/global_settings.dt_bg_gc_check_interval * 1000); + LOG_FMT_INFO(log, "Start background storage gc worker with interval {} seconds.", global_settings.dt_bg_gc_check_interval); } BackgroundService::~BackgroundService() @@ -104,17 +51,7 @@ BackgroundService::~BackgroundService() background_pool.removeTask(single_thread_task_handle); single_thread_task_handle = nullptr; } - if (table_flush_handle) - { - background_pool.removeTask(table_flush_handle); - table_flush_handle = nullptr; - } - if (region_handle) - { - background_pool.removeTask(region_handle); - region_handle = nullptr; - } if (storage_gc_handle) { background_pool.removeTask(storage_gc_handle); diff --git a/dbms/src/Storages/Transaction/BackgroundService.h b/dbms/src/Storages/Transaction/BackgroundService.h index 18a0f04206f..13bf2c8add5 100644 --- a/dbms/src/Storages/Transaction/BackgroundService.h +++ b/dbms/src/Storages/Transaction/BackgroundService.h @@ -39,20 +39,13 @@ class BackgroundService : boost::noncopyable ~BackgroundService(); - void addRegionToFlush(const RegionPtr & region); - private: TMTContext & tmt; BackgroundProcessingPool & background_pool; Poco::Logger * log; - std::mutex region_mutex; - RegionMap regions_to_flush; - BackgroundProcessingPool::TaskHandle single_thread_task_handle; - BackgroundProcessingPool::TaskHandle table_flush_handle; - BackgroundProcessingPool::TaskHandle region_handle; BackgroundProcessingPool::TaskHandle storage_gc_handle; }; diff --git a/dbms/src/Storages/Transaction/KVStore.cpp b/dbms/src/Storages/Transaction/KVStore.cpp index 8a7df489f20..2b82220cbc4 100644 --- a/dbms/src/Storages/Transaction/KVStore.cpp +++ b/dbms/src/Storages/Transaction/KVStore.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace DB { @@ -129,35 +130,33 @@ void KVStore::traverseRegions(std::function & void KVStore::tryFlushRegionCacheInStorage(TMTContext & tmt, const Region & region, Poco::Logger * log) { - if (tmt.isBgFlushDisabled()) + auto table_id = region.getMappedTableID(); + auto storage = tmt.getStorages().get(table_id); + if (unlikely(storage == nullptr)) { - auto table_id = region.getMappedTableID(); - auto storage = tmt.getStorages().get(table_id); - if (storage == nullptr) - { - LOG_WARNING(log, - "tryFlushRegionCacheInStorage can not get table for region:" + region.toString() - + " with table id: " + DB::toString(table_id) + ", ignored"); - return; - } + LOG_FMT_WARNING(log, + "tryFlushRegionCacheInStorage can not get table for region {} with table id {}, ignored", + region.toString(), + table_id); + return; + } - try - { - // Acquire `drop_lock` so that no other threads can drop the storage during `flushCache`. `alter_lock` is not required. - auto storage_lock = storage->lockForShare(getThreadName()); - auto rowkey_range = DM::RowKeyRange::fromRegionRange( - region.getRange(), - region.getRange()->getMappedTableID(), - storage->isCommonHandle(), - storage->getRowKeyColumnSize()); - storage->flushCache(tmt.getContext(), rowkey_range); - } - catch (DB::Exception & e) - { - // We can ignore if storage is already dropped. - if (e.code() != ErrorCodes::TABLE_IS_DROPPED) - throw; - } + try + { + // Acquire `drop_lock` so that no other threads can drop the storage during `flushCache`. `alter_lock` is not required. + auto storage_lock = storage->lockForShare(getThreadName()); + auto rowkey_range = DM::RowKeyRange::fromRegionRange( + region.getRange(), + region.getRange()->getMappedTableID(), + storage->isCommonHandle(), + storage->getRowKeyColumnSize()); + storage->flushCache(tmt.getContext(), rowkey_range); + } + catch (DB::Exception & e) + { + // We can ignore if storage is already dropped. + if (e.code() != ErrorCodes::TABLE_IS_DROPPED) + throw; } } @@ -444,21 +443,13 @@ EngineStoreApplyRes KVStore::handleAdminRaftCmd(raft_cmdpb::AdminRequest && requ // After region split / merge, try to flush it const auto try_to_flush_region = [&tmt](const RegionPtr & region) { - if (tmt.isBgFlushDisabled()) + try { - try - { - tmt.getRegionTable().tryFlushRegion(region, false); - } - catch (const Exception & e) - { - tryLogCurrentException(__PRETTY_FUNCTION__); - } + tmt.getRegionTable().tryFlushRegion(region, false); } - else + catch (...) { - if (region->writeCFCount() >= 8192) - tmt.getBackgroundService().addRegionToFlush(region); + tryLogCurrentException(__PRETTY_FUNCTION__); } }; diff --git a/dbms/src/Storages/Transaction/Region.cpp b/dbms/src/Storages/Transaction/Region.cpp index 4ee2bb7963a..e021de3d978 100644 --- a/dbms/src/Storages/Transaction/Region.cpp +++ b/dbms/src/Storages/Transaction/Region.cpp @@ -707,12 +707,9 @@ EngineStoreApplyRes Region::handleWriteRaftCmd(const WriteCmdsView & cmds, UInt6 // If transfer-leader happened during ingest-sst, there might be illegal data. if (0 != cmds.len) { - if (tmt.isBgFlushDisabled()) - { - /// Flush data right after they are committed. - RegionDataReadInfoList data_list_to_remove; - RegionTable::writeBlockByRegion(context, shared_from_this(), data_list_to_remove, log, false); - } + /// Flush data right after they are committed. + RegionDataReadInfoList data_list_to_remove; + RegionTable::writeBlockByRegion(context, shared_from_this(), data_list_to_remove, log, false); } meta.setApplied(index, term); diff --git a/dbms/src/Storages/Transaction/RegionTable.cpp b/dbms/src/Storages/Transaction/RegionTable.cpp index afe905ea180..8b5ca5746f0 100644 --- a/dbms/src/Storages/Transaction/RegionTable.cpp +++ b/dbms/src/Storages/Transaction/RegionTable.cpp @@ -117,14 +117,7 @@ RegionDataReadInfoList RegionTable::flushRegion(const RegionPtrWithBlock & regio { auto & tmt = context->getTMTContext(); - if (tmt.isBgFlushDisabled()) - { - LOG_FMT_TRACE(log, "table {}, {} original {} bytes", region->getMappedTableID(), region->toString(false), region->dataSize()); - } - else - { - LOG_FMT_INFO(log, "table {}, {} original {} bytes", region->getMappedTableID(), region->toString(false), region->dataSize()); - } + LOG_FMT_TRACE(log, "table {}, {} original {} bytes", region->getMappedTableID(), region->toString(false), region->dataSize()); /// Write region data into corresponding storage. RegionDataReadInfoList data_list_to_remove; @@ -144,14 +137,7 @@ RegionDataReadInfoList RegionTable::flushRegion(const RegionPtrWithBlock & regio } } - if (tmt.isBgFlushDisabled()) - { - LOG_FMT_TRACE(log, "table {}, {} after flush {} bytes", region->getMappedTableID(), region->toString(false), cache_size); - } - else - { - LOG_FMT_INFO(log, "table {}, {} after flush {} bytes", region->getMappedTableID(), region->toString(false), cache_size); - } + LOG_FMT_TRACE(log, "table {}, {} after flush {} bytes", region->getMappedTableID(), region->toString(false), cache_size); } return data_list_to_remove; diff --git a/dbms/src/Storages/Transaction/TMTContext.cpp b/dbms/src/Storages/Transaction/TMTContext.cpp index 039a21dd8ea..006be9d7a92 100644 --- a/dbms/src/Storages/Transaction/TMTContext.cpp +++ b/dbms/src/Storages/Transaction/TMTContext.cpp @@ -53,7 +53,6 @@ TMTContext::TMTContext(Context & context_, const TiFlashRaftConfig & raft_config context.getSettingsRef().task_scheduler_thread_soft_limit, context.getSettingsRef().task_scheduler_thread_hard_limit))) , engine(raft_config.engine) - , disable_bg_flush(raft_config.disable_bg_flush) , replica_read_max_thread(1) , batch_read_index_timeout_ms(DEFAULT_BATCH_READ_INDEX_TIMEOUT_MS) , wait_region_ready_timeout_sec(DEFAULT_WAIT_REGION_READY_TIMEOUT_SEC) diff --git a/dbms/src/Storages/Transaction/TMTContext.h b/dbms/src/Storages/Transaction/TMTContext.h index 9ee49b5f099..27e0482b787 100644 --- a/dbms/src/Storages/Transaction/TMTContext.h +++ b/dbms/src/Storages/Transaction/TMTContext.h @@ -81,8 +81,6 @@ class TMTContext : private boost::noncopyable const Context & getContext() const; - bool isBgFlushDisabled() const { return disable_bg_flush; } - explicit TMTContext(Context & context_, const TiFlashRaftConfig & raft_config, const pingcap::ClusterConfig & cluster_config_); SchemaSyncerPtr getSchemaSyncer() const; @@ -140,8 +138,6 @@ class TMTContext : private boost::noncopyable ::TiDB::StorageEngine engine; - bool disable_bg_flush; - std::atomic_uint64_t replica_read_max_thread; std::atomic_uint64_t batch_read_index_timeout_ms; std::atomic_uint64_t wait_index_timeout_ms; diff --git a/dbms/src/TestUtils/TiFlashTestEnv.cpp b/dbms/src/TestUtils/TiFlashTestEnv.cpp index 7e8bd0da319..264fd6009a3 100644 --- a/dbms/src/TestUtils/TiFlashTestEnv.cpp +++ b/dbms/src/TestUtils/TiFlashTestEnv.cpp @@ -76,7 +76,6 @@ void TiFlashTestEnv::initializeGlobalContext(Strings testdata_path, bool enable_ raft_config.ignore_databases = {"default", "system"}; raft_config.engine = TiDB::StorageEngine::DT; - raft_config.disable_bg_flush = true; global_context->createTMTContext(raft_config, pingcap::ClusterConfig()); global_context->setDeltaIndexManager(1024 * 1024 * 100 /*100MB*/); From 6b8ca3df33a116513cbdba7dc3a3c5389287e1f4 Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Tue, 17 May 2022 15:46:38 +0800 Subject: [PATCH 030/127] Interpreter: Run interpreter test with exchange executors (#4858) ref pingcap/tiflash#4609 --- .../MockExchangeReceiverInputStream.cpp | 57 ++++++++ .../MockExchangeReceiverInputStream.h | 41 ++++++ .../MockExchangeSenderInputStream.cpp | 46 ++++++ .../MockExchangeSenderInputStream.h | 46 ++++++ .../MockTableScanBlockInputStream.cpp | 2 +- .../Coprocessor/DAGQueryBlockInterpreter.cpp | 63 +++++--- .../Coprocessor/DAGQueryBlockInterpreter.h | 6 +- dbms/src/Flash/tests/gtest_interpreter.cpp | 134 +++++++++++++++++- 8 files changed, 375 insertions(+), 20 deletions(-) create mode 100644 dbms/src/DataStreams/MockExchangeReceiverInputStream.cpp create mode 100644 dbms/src/DataStreams/MockExchangeReceiverInputStream.h create mode 100644 dbms/src/DataStreams/MockExchangeSenderInputStream.cpp create mode 100644 dbms/src/DataStreams/MockExchangeSenderInputStream.h diff --git a/dbms/src/DataStreams/MockExchangeReceiverInputStream.cpp b/dbms/src/DataStreams/MockExchangeReceiverInputStream.cpp new file mode 100644 index 00000000000..b1de3e23914 --- /dev/null +++ b/dbms/src/DataStreams/MockExchangeReceiverInputStream.cpp @@ -0,0 +1,57 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include + +namespace DB +{ +MockExchangeReceiverInputStream::MockExchangeReceiverInputStream(const tipb::ExchangeReceiver & receiver, size_t max_block_size, size_t rows_) + : output_index(0) + , max_block_size(max_block_size) + , rows(rows_) +{ + for (int i = 0; i < receiver.field_types_size(); ++i) + { + columns.emplace_back( + getDataTypeByColumnInfoForComputingLayer(TiDB::fieldTypeToColumnInfo(receiver.field_types(i))), + fmt::format("exchange_receiver_{}", i)); + } +} + +ColumnPtr MockExchangeReceiverInputStream::makeColumn(ColumnWithTypeAndName elem) const +{ + auto column = elem.type->createColumn(); + size_t row_count = 0; + for (size_t i = output_index; (i < rows) & (row_count < max_block_size); ++i) + { + column->insert((*elem.column)[i]); + ++row_count; + } + return column; +} + +Block MockExchangeReceiverInputStream::readImpl() +{ + if (output_index >= rows) + return {}; + ColumnsWithTypeAndName output_columns; + for (const auto & elem : columns) + { + output_columns.push_back({makeColumn(elem), elem.type, elem.name, elem.column_id}); + } + output_index += max_block_size; + return Block(output_columns); +} +} // namespace DB diff --git a/dbms/src/DataStreams/MockExchangeReceiverInputStream.h b/dbms/src/DataStreams/MockExchangeReceiverInputStream.h new file mode 100644 index 00000000000..24ae80d4f62 --- /dev/null +++ b/dbms/src/DataStreams/MockExchangeReceiverInputStream.h @@ -0,0 +1,41 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +#include +#include +#include + +namespace DB +{ +/// Mock the receiver like table scan, can mock blocks according to the receiver's schema. +/// TODO: Mock the receiver process +class MockExchangeReceiverInputStream : public IProfilingBlockInputStream +{ +public: + MockExchangeReceiverInputStream(const tipb::ExchangeReceiver & receiver, size_t max_block_size, size_t rows_); + Block getHeader() const override { return Block(columns); } + String getName() const override { return "MockExchangeReceiver"; } + ColumnsWithTypeAndName columns; + size_t output_index; + size_t max_block_size; + size_t rows; + +protected: + Block readImpl() override; + ColumnPtr makeColumn(ColumnWithTypeAndName elem) const; +}; + +} // namespace DB diff --git a/dbms/src/DataStreams/MockExchangeSenderInputStream.cpp b/dbms/src/DataStreams/MockExchangeSenderInputStream.cpp new file mode 100644 index 00000000000..c981b191398 --- /dev/null +++ b/dbms/src/DataStreams/MockExchangeSenderInputStream.cpp @@ -0,0 +1,46 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +namespace DB +{ +MockExchangeSenderInputStream::MockExchangeSenderInputStream( + const BlockInputStreamPtr & input, + const String & req_id) + : log(Logger::get(NAME, req_id)) +{ + children.push_back(input); +} + +Block MockExchangeSenderInputStream::getTotals() +{ + if (IProfilingBlockInputStream * child = dynamic_cast(&*children.back())) + { + totals = child->getTotals(); + } + + return totals; +} + +Block MockExchangeSenderInputStream::getHeader() const +{ + return children.back()->getHeader(); +} + +Block MockExchangeSenderInputStream::readImpl() +{ + return children.back()->read(); +} + +} // namespace DB diff --git a/dbms/src/DataStreams/MockExchangeSenderInputStream.h b/dbms/src/DataStreams/MockExchangeSenderInputStream.h new file mode 100644 index 00000000000..077b07b4da8 --- /dev/null +++ b/dbms/src/DataStreams/MockExchangeSenderInputStream.h @@ -0,0 +1,46 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +#include +#include +#include + +namespace DB +{ +/// Currently, this operator do nothing with the block. +/// TODO: Mock the sender process +class MockExchangeSenderInputStream : public IProfilingBlockInputStream +{ +private: + static constexpr auto NAME = "MockExchangeSender"; + +public: + MockExchangeSenderInputStream( + const BlockInputStreamPtr & input, + const String & req_id); + + String getName() const override { return NAME; } + Block getTotals() override; + Block getHeader() const override; + +protected: + Block readImpl() override; + +private: + const LoggerPtr log; +}; + +} // namespace DB diff --git a/dbms/src/DataStreams/MockTableScanBlockInputStream.cpp b/dbms/src/DataStreams/MockTableScanBlockInputStream.cpp index 0405e8082db..d731d612047 100644 --- a/dbms/src/DataStreams/MockTableScanBlockInputStream.cpp +++ b/dbms/src/DataStreams/MockTableScanBlockInputStream.cpp @@ -36,7 +36,7 @@ ColumnPtr MockTableScanBlockInputStream::makeColumn(ColumnWithTypeAndName elem) { auto column = elem.type->createColumn(); size_t row_count = 0; - for (size_t i = output_index; i < rows & row_count < max_block_size; ++i) + for (size_t i = output_index; (i < rows) & (row_count < max_block_size); ++i) { column->insert((*elem.column)[i]); row_count++; diff --git a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp index cef1fe1b596..8d91b8b23e9 100644 --- a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp +++ b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,8 @@ #include #include #include +#include +#include #include #include #include @@ -51,6 +54,7 @@ #include #include + namespace DB { namespace FailPoints @@ -190,7 +194,7 @@ void DAGQueryBlockInterpreter::prepareJoin( const google::protobuf::RepeatedPtrField & filters, String & filter_column_name) { - std::vector source_columns; + NamesAndTypes source_columns; for (auto const & p : pipeline.firstStream()->getHeader().getNamesAndTypesList()) source_columns.emplace_back(p.name, p.type); DAGExpressionAnalyzer dag_analyzer(std::move(source_columns), context); @@ -203,7 +207,7 @@ void DAGQueryBlockInterpreter::prepareJoin( ExpressionActionsPtr DAGQueryBlockInterpreter::genJoinOtherConditionAction( const tipb::Join & join, - std::vector & source_columns, + NamesAndTypes & source_columns, String & filter_column_for_other_condition, String & filter_column_for_other_eq_condition) { @@ -326,7 +330,7 @@ void DAGQueryBlockInterpreter::handleJoin(const tipb::Join & join, DAGPipeline & right_pipeline.streams = input_streams_vec[1]; } - std::vector join_output_columns; + NamesAndTypes join_output_columns; /// columns_for_other_join_filter is a vector of columns used /// as the input columns when compiling other join filter. /// Note the order in the column vector is very important: @@ -336,7 +340,7 @@ void DAGQueryBlockInterpreter::handleJoin(const tipb::Join & join, DAGPipeline & /// append the extra columns afterwards. In order to figure out /// whether a given column is already in the column vector or /// not quickly, we use another set to store the column names - std::vector columns_for_other_join_filter; + NamesAndTypes columns_for_other_join_filter; std::unordered_set column_set_for_other_join_filter; bool make_nullable = join.join_type() == tipb::JoinType::TypeRightOuterJoin; for (auto const & p : input_streams_vec[0][0]->getHeader().getNamesAndTypesList()) @@ -479,7 +483,7 @@ void DAGQueryBlockInterpreter::handleJoin(const tipb::Join & join, DAGPipeline & right_query.join = join_ptr; right_query.join->setSampleBlock(right_query.source->getHeader()); - std::vector source_columns; + NamesAndTypes source_columns; for (const auto & p : left_pipeline.streams[0]->getHeader().getNamesAndTypesList()) source_columns.emplace_back(p.name, p.type); DAGExpressionAnalyzer dag_analyzer(std::move(source_columns), context); @@ -617,7 +621,7 @@ void DAGQueryBlockInterpreter::executeWindowOrder(DAGPipeline & pipeline, SortDe orderStreams(pipeline, sort_desc, 0); } -void DAGQueryBlockInterpreter::executeOrder(DAGPipeline & pipeline, const std::vector & order_columns) +void DAGQueryBlockInterpreter::executeOrder(DAGPipeline & pipeline, const NamesAndTypes & order_columns) { Int64 limit = query_block.limit_or_topn->topn().limit(); orderStreams(pipeline, getSortDescription(order_columns, query_block.limit_or_topn->topn().order_by()), limit); @@ -673,25 +677,39 @@ void DAGQueryBlockInterpreter::handleExchangeReceiver(DAGPipeline & pipeline) stream = std::make_shared(stream, 8192, 0, log->identifier()); pipeline.streams.push_back(stream); } - std::vector source_columns; - Block block = pipeline.firstStream()->getHeader(); - for (const auto & col : block.getColumnsWithTypeAndName()) + NamesAndTypes source_columns; + for (const auto & col : pipeline.firstStream()->getHeader()) + { + source_columns.emplace_back(col.name, col.type); + } + analyzer = std::make_unique(std::move(source_columns), context); +} + +void DAGQueryBlockInterpreter::handleMockExchangeReceiver(DAGPipeline & pipeline) +{ + for (size_t i = 0; i < max_streams; ++i) + { + // use max_block_size / 10 to determine the mock block's size + pipeline.streams.push_back(std::make_shared(query_block.source->exchange_receiver(), context.getSettingsRef().max_block_size, context.getSettingsRef().max_block_size / 10)); + } + NamesAndTypes source_columns; + for (const auto & col : pipeline.firstStream()->getHeader()) { - source_columns.emplace_back(NameAndTypePair(col.name, col.type)); + source_columns.emplace_back(col.name, col.type); } analyzer = std::make_unique(std::move(source_columns), context); } void DAGQueryBlockInterpreter::handleProjection(DAGPipeline & pipeline, const tipb::Projection & projection) { - std::vector input_columns; + NamesAndTypes input_columns; pipeline.streams = input_streams_vec[0]; for (auto const & p : pipeline.firstStream()->getHeader().getNamesAndTypesList()) input_columns.emplace_back(p.name, p.type); DAGExpressionAnalyzer dag_analyzer(std::move(input_columns), context); ExpressionActionsChain chain; auto & last_step = dag_analyzer.initAndGetLastStep(chain); - std::vector output_columns; + NamesAndTypes output_columns; NamesWithAliases project_cols; UniqueNameGenerator unique_name_generator; for (const auto & expr : projection.exprs()) @@ -758,7 +776,10 @@ void DAGQueryBlockInterpreter::executeImpl(DAGPipeline & pipeline) } else if (query_block.source->tp() == tipb::ExecType::TypeExchangeReceiver) { - handleExchangeReceiver(pipeline); + if (unlikely(dagContext().isTest())) + handleMockExchangeReceiver(pipeline); + else + handleExchangeReceiver(pipeline); recordProfileStreams(pipeline, query_block.source_name); } else if (query_block.source->tp() == tipb::ExecType::TypeProjection) @@ -769,7 +790,7 @@ void DAGQueryBlockInterpreter::executeImpl(DAGPipeline & pipeline) else if (query_block.isTableScanSource()) { TiDBTableScan table_scan(query_block.source, query_block.source_name, dagContext()); - if (dagContext().isTest()) + if (unlikely(dagContext().isTest())) handleMockTableScan(table_scan, pipeline); else handleTableScan(table_scan, pipeline); @@ -850,7 +871,10 @@ void DAGQueryBlockInterpreter::executeImpl(DAGPipeline & pipeline) // execute exchange_sender if (query_block.exchange_sender) { - handleExchangeSender(pipeline); + if (unlikely(dagContext().isTest())) + handleMockExchangeSender(pipeline); + else + handleExchangeSender(pipeline); recordProfileStreams(pipeline, query_block.exchange_sender_name); } } @@ -901,6 +925,13 @@ void DAGQueryBlockInterpreter::handleExchangeSender(DAGPipeline & pipeline) }); } +void DAGQueryBlockInterpreter::handleMockExchangeSender(DAGPipeline & pipeline) +{ + pipeline.transform([&](auto & stream) { + stream = std::make_shared(stream, log->identifier()); + }); +} + void DAGQueryBlockInterpreter::restorePipelineConcurrency(DAGPipeline & pipeline) { if (query_block.can_restore_pipeline_concurrency) @@ -919,4 +950,4 @@ BlockInputStreams DAGQueryBlockInterpreter::execute() return pipeline.streams; } -} // namespace DB +} // namespace DB \ No newline at end of file diff --git a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h index f5a8d2b5ce5..69bac9c3ba9 100644 --- a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h +++ b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h @@ -71,19 +71,20 @@ class DAGQueryBlockInterpreter const google::protobuf::RepeatedPtrField & filters, String & filter_column_name); void handleExchangeReceiver(DAGPipeline & pipeline); + void handleMockExchangeReceiver(DAGPipeline & pipeline); void handleProjection(DAGPipeline & pipeline, const tipb::Projection & projection); void handleWindow(DAGPipeline & pipeline, const tipb::Window & window); void handleWindowOrder(DAGPipeline & pipeline, const tipb::Sort & window_sort); ExpressionActionsPtr genJoinOtherConditionAction( const tipb::Join & join, - std::vector & source_columns, + NamesAndTypes & source_columns, String & filter_column_for_other_condition, String & filter_column_for_other_eq_condition); void executeWhere(DAGPipeline & pipeline, const ExpressionActionsPtr & expressionActionsPtr, String & filter_column); void executeExpression(DAGPipeline & pipeline, const ExpressionActionsPtr & expressionActionsPtr); void executeWindowOrder(DAGPipeline & pipeline, SortDescription sort_desc); void orderStreams(DAGPipeline & pipeline, SortDescription order_descr, Int64 limit); - void executeOrder(DAGPipeline & pipeline, const std::vector & order_columns); + void executeOrder(DAGPipeline & pipeline, const NamesAndTypes & order_columns); void executeLimit(DAGPipeline & pipeline); void executeWindow( DAGPipeline & pipeline, @@ -97,6 +98,7 @@ class DAGQueryBlockInterpreter bool is_final_agg); void executeProject(DAGPipeline & pipeline, NamesWithAliases & project_cols); void handleExchangeSender(DAGPipeline & pipeline); + void handleMockExchangeSender(DAGPipeline & pipeline); void recordProfileStreams(DAGPipeline & pipeline, const String & key); diff --git a/dbms/src/Flash/tests/gtest_interpreter.cpp b/dbms/src/Flash/tests/gtest_interpreter.cpp index 961ab525be8..0d07159b8fc 100644 --- a/dbms/src/Flash/tests/gtest_interpreter.cpp +++ b/dbms/src/Flash/tests/gtest_interpreter.cpp @@ -30,6 +30,9 @@ class InterpreterExecuteTest : public DB::tests::InterpreterTest context.addMockTable({"test_db", "test_table_1"}, {{"s1", TiDB::TP::TypeString}, {"s2", TiDB::TP::TypeString}, {"s3", TiDB::TP::TypeString}}); context.addMockTable({"test_db", "r_table"}, {{"r_a", TiDB::TP::TypeLong}, {"r_b", TiDB::TP::TypeString}, {"join_c", TiDB::TP::TypeString}}); context.addMockTable({"test_db", "l_table"}, {{"l_a", TiDB::TP::TypeLong}, {"l_b", TiDB::TP::TypeString}, {"join_c", TiDB::TP::TypeString}}); + context.addExchangeRelationSchema("sender_1", {{"s1", TiDB::TP::TypeString}, {"s2", TiDB::TP::TypeString}, {"s3", TiDB::TP::TypeString}}); + context.addExchangeRelationSchema("sender_l", {{"l_a", TiDB::TP::TypeString}, {"l_b", TiDB::TP::TypeString}, {"join_c", TiDB::TP::TypeString}}); + context.addExchangeRelationSchema("sender_r", {{"r_a", TiDB::TP::TypeString}, {"r_b", TiDB::TP::TypeString}, {"join_c", TiDB::TP::TypeString}}); } }; @@ -249,9 +252,138 @@ CreatingSets MockTableScan)"; ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); } + + request = context.receive("sender_1") + .project({"s1", "s2", "s3"}) + .project({"s1", "s2"}) + .project("s1") + .build(context); + { + String expected = R"( +Union + Expression x 10 + Expression + Expression + Expression + Expression + Expression + Expression + Expression + Expression + Expression + MockExchangeReceiver)"; + ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); + } + + request = context.receive("sender_1") + .project({"s1", "s2", "s3"}) + .project({"s1", "s2"}) + .project("s1") + .exchangeSender(tipb::Broadcast) + .build(context); + { + String expected = R"( +Union + MockExchangeSender x 10 + Expression + Expression + Expression + Expression + Expression + Expression + Expression + Expression + Expression + Expression + MockExchangeReceiver)"; + ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); + } + + // only join + ExchangeReceiver + DAGRequestBuilder receiver1 = context.receive("sender_l"); + DAGRequestBuilder receiver2 = context.receive("sender_r"); + DAGRequestBuilder receiver3 = context.receive("sender_l"); + DAGRequestBuilder receiver4 = context.receive("sender_r"); + + request = receiver1.join( + receiver2.join( + receiver3.join(receiver4, + {col("join_c")}, + ASTTableJoin::Kind::Left), + {col("join_c")}, + ASTTableJoin::Kind::Left), + {col("join_c")}, + ASTTableJoin::Kind::Left) + .build(context); + { + String expected = R"( +CreatingSets + Union + HashJoinBuildBlockInputStream x 10 + Expression + Expression + MockExchangeReceiver + Union x 2 + HashJoinBuildBlockInputStream x 10 + Expression + Expression + Expression + HashJoinProbe + Expression + MockExchangeReceiver + Union + Expression x 10 + Expression + HashJoinProbe + Expression + MockExchangeReceiver)"; + ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); + } + + // join + receiver + sender + // TODO: Find a way to write the request easier. + DAGRequestBuilder receiver5 = context.receive("sender_l"); + DAGRequestBuilder receiver6 = context.receive("sender_r"); + DAGRequestBuilder receiver7 = context.receive("sender_l"); + DAGRequestBuilder receiver8 = context.receive("sender_r"); + request = receiver5.join( + receiver6.join( + receiver7.join(receiver8, + {col("join_c")}, + ASTTableJoin::Kind::Left), + {col("join_c")}, + ASTTableJoin::Kind::Left), + {col("join_c")}, + ASTTableJoin::Kind::Left) + .exchangeSender(tipb::PassThrough) + .build(context); + { + String expected = R"( +CreatingSets + Union + HashJoinBuildBlockInputStream x 10 + Expression + Expression + MockExchangeReceiver + Union x 2 + HashJoinBuildBlockInputStream x 10 + Expression + Expression + Expression + HashJoinProbe + Expression + MockExchangeReceiver + Union + MockExchangeSender x 10 + Expression + Expression + HashJoinProbe + Expression + MockExchangeReceiver)"; + ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); + } } CATCH - } // namespace tests } // namespace DB \ No newline at end of file From 1c0806823ad93d620d544e8a554bfeb693bbc171 Mon Sep 17 00:00:00 2001 From: Calvin Neo Date: Tue, 17 May 2022 19:26:37 +0800 Subject: [PATCH 031/127] update TiFlash proxy (#4907) close pingcap/tiflash#4906 --- contrib/tiflash-proxy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tiflash-proxy b/contrib/tiflash-proxy index e98f9597078..5bd9c73fdb7 160000 --- a/contrib/tiflash-proxy +++ b/contrib/tiflash-proxy @@ -1 +1 @@ -Subproject commit e98f95970781e3b70793f8c90e7c7022e8354ef8 +Subproject commit 5bd9c73fdb7db4df76958319f2d751490e2ce696 From 0477f9658b83df75b8583d5ff3021634081775ce Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Tue, 17 May 2022 20:24:37 +0800 Subject: [PATCH 032/127] Add option to support clang LTO(Link Time Optimization) and time-traces (#4890) ref pingcap/tiflash#4909 --- CMakeLists.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 118424fe5b2..f1dd740b0b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,28 @@ if (COMPILER_CLANG) # Clang doesn't have int128 predefined macros, workaround by manually defining them # Reference: https://stackoverflow.com/questions/41198673/uint128-t-not-working-with-clang-and-libstdc set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__GLIBCXX_BITSIZE_INT_N_0=128 -D__GLIBCXX_TYPE_INT_N_0=__int128") + + option(ENABLE_TIME_TRACES "Enable clang feature time traces" OFF) + if (ENABLE_TIME_TRACES) + set (CLANG_TIME_TRACES_FLAGS "-ftime-trace") + message (STATUS "Using clang time traces flag `${CLANG_TIME_TRACES_FLAGS}`. Generates JSON file based on output filename. Results can be analyzed with chrome://tracing or https://www.speedscope.app for flamegraph visualization.") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CLANG_TIME_TRACES_FLAGS}") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CLANG_TIME_TRACES_FLAGS}") + endif () + + # https://clang.llvm.org/docs/ThinLTO.html + # Applies to clang only. + option(ENABLE_THINLTO "Clang-specific link time optimization" OFF) + + if (ENABLE_THINLTO AND NOT ENABLE_TESTS) + # Link time optimization + set (THINLTO_JOBS "0" CACHE STRING "ThinLTO compilation parallelism") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -flto=thin -fvisibility=hidden -fvisibility-inlines-hidden -fsplit-lto-unit") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto=thin -fvisibility=hidden -fvisibility-inlines-hidden -fwhole-program-vtables -fsplit-lto-unit") + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -flto=thin -flto-jobs=${THINLTO_JOBS} -fvisibility=hidden -fvisibility-inlines-hidden -fwhole-program-vtables -fsplit-lto-unit") + elseif (ENABLE_THINLTO) + message (WARNING "Cannot enable ThinLTO") + endif () endif () if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") From 43b25fc844817725e668812631fc7345ceddda0e Mon Sep 17 00:00:00 2001 From: JaySon Date: Tue, 17 May 2022 21:38:37 +0800 Subject: [PATCH 033/127] Fix the bug for restoring data with duplicated page id (#4889) ref pingcap/tiflash#3594 --- dbms/src/Common/ErrorCodes.cpp | 1 + dbms/src/Interpreters/Context.cpp | 5 +- .../Storages/DeltaMerge/DeltaMergeStore.cpp | 3 +- dbms/src/Storages/DeltaMerge/StoragePool.cpp | 89 ++-- dbms/src/Storages/DeltaMerge/StoragePool.h | 37 -- dbms/src/Storages/Page/PageStorage.h | 10 +- dbms/src/Storages/Page/V2/PageStorage.cpp | 9 +- dbms/src/Storages/Page/V2/PageStorage.h | 4 +- .../Page/V2/tests/gtest_page_storage.cpp | 28 -- dbms/src/Storages/Page/V3/BlobStore.h | 2 +- dbms/src/Storages/Page/V3/PageDirectory.cpp | 178 ++++--- dbms/src/Storages/Page/V3/PageDirectory.h | 32 +- .../Storages/Page/V3/PageDirectoryFactory.cpp | 135 +++--- .../Storages/Page/V3/PageDirectoryFactory.h | 12 +- dbms/src/Storages/Page/V3/PageEntriesEdit.h | 47 +- dbms/src/Storages/Page/V3/PageStorageImpl.cpp | 8 +- dbms/src/Storages/Page/V3/PageStorageImpl.h | 3 +- dbms/src/Storages/Page/V3/WAL/serialize.cpp | 6 +- dbms/src/Storages/Page/V3/WALStore.cpp | 2 +- dbms/src/Storages/Page/V3/WALStore.h | 2 +- .../Page/V3/tests/gtest_page_directory.cpp | 444 ++++++++++++++++-- .../Page/V3/tests/gtest_page_storage.cpp | 45 -- .../V3/tests/gtest_page_storage_mix_mode.cpp | 9 +- .../Page/V3/tests/gtest_wal_store.cpp | 34 +- .../Page/V3/tests/page_storage_ctl.cpp | 4 +- dbms/src/TestUtils/TiFlashTestEnv.cpp | 1 - tests/delta-merge-test/ddl/alter.test | 23 +- .../raft/schema/partition_table_restart.test | 3 +- .../raft/txn_mock/partition_table.test | 27 +- 29 files changed, 777 insertions(+), 426 deletions(-) diff --git a/dbms/src/Common/ErrorCodes.cpp b/dbms/src/Common/ErrorCodes.cpp index 40c14539644..4c3c2532c6a 100644 --- a/dbms/src/Common/ErrorCodes.cpp +++ b/dbms/src/Common/ErrorCodes.cpp @@ -424,6 +424,7 @@ extern const int DEADLOCK_AVOIDED = 10013; extern const int PTHREAD_ERROR = 10014; extern const int PS_ENTRY_NOT_EXISTS = 10015; extern const int PS_ENTRY_NO_VALID_VERSION = 10016; +extern const int PS_DIR_APPLY_INVALID_STATUS = 10017; } // namespace ErrorCodes } // namespace DB diff --git a/dbms/src/Interpreters/Context.cpp b/dbms/src/Interpreters/Context.cpp index 1c74b5f402e..26e950d7798 100644 --- a/dbms/src/Interpreters/Context.cpp +++ b/dbms/src/Interpreters/Context.cpp @@ -1650,8 +1650,9 @@ bool Context::initializeGlobalStoragePoolIfNeed(const PathPool & path_pool) auto lock = getLock(); if (shared->global_storage_pool) { - // GlobalStoragePool may be initialized many times in some test cases for restore. - LOG_WARNING(shared->log, "GlobalStoragePool has already been initialized."); + // Can't init GlobalStoragePool twice. + // otherwise the pagestorage instances in `StoragePool` for each table won't be updated and cause unexpected problem. + throw Exception("GlobalStoragePool has already been initialized.", ErrorCodes::LOGICAL_ERROR); } CurrentMetrics::set(CurrentMetrics::GlobalStorageRunMode, static_cast(shared->storage_run_mode)); if (shared->storage_run_mode == PageStorageRunMode::MIX_MODE || shared->storage_run_mode == PageStorageRunMode::ONLY_V3) diff --git a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp index 2d53f85f516..f20239638b3 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp +++ b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp @@ -243,7 +243,8 @@ DeltaMergeStore::DeltaMergeStore(Context & db_context, try { page_storage_run_mode = storage_pool->restore(); // restore from disk - if (!storage_pool->maxMetaPageId()) + if (const auto first_segment_entry = storage_pool->metaReader()->getPageEntry(DELTA_MERGE_FIRST_SEGMENT_ID); + !first_segment_entry.isValid()) { // Create the first segment. auto segment_id = storage_pool->newMetaPageId(); diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.cpp b/dbms/src/Storages/DeltaMerge/StoragePool.cpp index 70e4b08ea14..b94cc3c1735 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.cpp +++ b/dbms/src/Storages/DeltaMerge/StoragePool.cpp @@ -82,26 +82,21 @@ PageStorage::Config extractConfig(const Settings & settings, StorageType subtype } GlobalStoragePool::GlobalStoragePool(const PathPool & path_pool, Context & global_ctx, const Settings & settings) - : // The iops and bandwidth in log_storage are relatively high, use multi-disks if possible - log_storage(PageStorage::create("__global__.log", - path_pool.getPSDiskDelegatorGlobalMulti("log"), - extractConfig(settings, StorageType::Log), - global_ctx.getFileProvider(), - true)) - , - // The iops in data_storage is low, only use the first disk for storing data - data_storage(PageStorage::create("__global__.data", - path_pool.getPSDiskDelegatorGlobalSingle("data"), - extractConfig(settings, StorageType::Data), - global_ctx.getFileProvider(), - true)) - , - // The iops in meta_storage is relatively high, use multi-disks if possible - meta_storage(PageStorage::create("__global__.meta", - path_pool.getPSDiskDelegatorGlobalMulti("meta"), - extractConfig(settings, StorageType::Meta), - global_ctx.getFileProvider(), - true)) + : log_storage(PageStorage::create("__global__.log", + path_pool.getPSDiskDelegatorGlobalMulti("log"), + extractConfig(settings, StorageType::Log), + global_ctx.getFileProvider(), + true)) + , data_storage(PageStorage::create("__global__.data", + path_pool.getPSDiskDelegatorGlobalMulti("data"), + extractConfig(settings, StorageType::Data), + global_ctx.getFileProvider(), + true)) + , meta_storage(PageStorage::create("__global__.meta", + path_pool.getPSDiskDelegatorGlobalMulti("meta"), + extractConfig(settings, StorageType::Meta), + global_ctx.getFileProvider(), + true)) , global_context(global_ctx) { } @@ -118,9 +113,9 @@ GlobalStoragePool::~GlobalStoragePool() void GlobalStoragePool::restore() { - log_max_ids = log_storage->restore(); - data_max_ids = data_storage->restore(); - meta_max_ids = meta_storage->restore(); + log_storage->restore(); + data_storage->restore(); + meta_storage->restore(); gc_handle = global_context.getBackgroundPool().addTask( [this] { @@ -181,7 +176,7 @@ StoragePool::StoragePool(Context & global_ctx, NamespaceId ns_id_, StoragePathPo extractConfig(global_context.getSettingsRef(), StorageType::Log), global_context.getFileProvider()); data_storage_v2 = PageStorage::create(name + ".data", - storage_path_pool.getPSDiskDelegatorSingle("data"), + storage_path_pool.getPSDiskDelegatorSingle("data"), // keep for behavior not changed extractConfig(global_context.getSettingsRef(), StorageType::Data), global_ctx.getFileProvider()); meta_storage_v2 = PageStorage::create(name + ".meta", @@ -295,41 +290,35 @@ void StoragePool::forceTransformMetaV2toV3() PageStorageRunMode StoragePool::restore() { - const auto & global_storage_pool = global_context.getGlobalStoragePool(); - switch (run_mode) { case PageStorageRunMode::ONLY_V2: { - auto log_max_ids = log_storage_v2->restore(); - auto data_max_ids = data_storage_v2->restore(); - auto meta_max_ids = meta_storage_v2->restore(); + log_storage_v2->restore(); + data_storage_v2->restore(); + meta_storage_v2->restore(); - assert(log_max_ids.size() == 1); - assert(data_max_ids.size() == 1); - assert(meta_max_ids.size() == 1); - - max_log_page_id = log_max_ids[0]; - max_data_page_id = data_max_ids[0]; - max_meta_page_id = meta_max_ids[0]; + max_log_page_id = log_storage_v2->getMaxId(ns_id); + max_data_page_id = data_storage_v2->getMaxId(ns_id); + max_meta_page_id = meta_storage_v2->getMaxId(ns_id); storage_pool_metrics = CurrentMetrics::Increment{CurrentMetrics::StoragePoolV2Only}; break; } case PageStorageRunMode::ONLY_V3: { - max_log_page_id = global_storage_pool->getLogMaxId(ns_id); - max_data_page_id = global_storage_pool->getDataMaxId(ns_id); - max_meta_page_id = global_storage_pool->getMetaMaxId(ns_id); + max_log_page_id = log_storage_v3->getMaxId(ns_id); + max_data_page_id = data_storage_v3->getMaxId(ns_id); + max_meta_page_id = meta_storage_v3->getMaxId(ns_id); storage_pool_metrics = CurrentMetrics::Increment{CurrentMetrics::StoragePoolV3Only}; break; } case PageStorageRunMode::MIX_MODE: { - auto v2_log_max_ids = log_storage_v2->restore(); - auto v2_data_max_ids = data_storage_v2->restore(); - auto v2_meta_max_ids = meta_storage_v2->restore(); + log_storage_v2->restore(); + data_storage_v2->restore(); + meta_storage_v2->restore(); // The pages on data and log can be rewritten to V3 and the old pages on V2 are deleted by `delta merge`. // However, the pages on meta V2 can not be deleted. As the pages in meta are small, we perform a forceTransformMetaV2toV3 to convert pages before all. @@ -349,10 +338,6 @@ PageStorageRunMode StoragePool::restore() LOG_FMT_INFO(logger, "Current meta translate already done before restored.[ns_id={}] ", ns_id); } - assert(v2_log_max_ids.size() == 1); - assert(v2_data_max_ids.size() == 1); - assert(v2_meta_max_ids.size() == 1); - // Check number of valid pages in v2 // If V2 already have no any data in disk, Then change run_mode to ONLY_V3 if (log_storage_v2->getNumberOfPages() == 0 && data_storage_v2->getNumberOfPages() == 0 && meta_storage_v2->getNumberOfPages() == 0) @@ -375,18 +360,18 @@ PageStorageRunMode StoragePool::restore() data_storage_writer = std::make_shared(PageStorageRunMode::ONLY_V3, /*storage_v2_*/ nullptr, data_storage_v3); meta_storage_writer = std::make_shared(PageStorageRunMode::ONLY_V3, /*storage_v2_*/ nullptr, meta_storage_v3); - max_log_page_id = global_storage_pool->getLogMaxId(ns_id); - max_data_page_id = global_storage_pool->getDataMaxId(ns_id); - max_meta_page_id = global_storage_pool->getMetaMaxId(ns_id); + max_log_page_id = log_storage_v3->getMaxId(ns_id); + max_data_page_id = data_storage_v3->getMaxId(ns_id); + max_meta_page_id = meta_storage_v3->getMaxId(ns_id); run_mode = PageStorageRunMode::ONLY_V3; storage_pool_metrics = CurrentMetrics::Increment{CurrentMetrics::StoragePoolV3Only}; } else // Still running Mix Mode { - max_log_page_id = std::max(v2_log_max_ids[0], global_storage_pool->getLogMaxId(ns_id)); - max_data_page_id = std::max(v2_data_max_ids[0], global_storage_pool->getDataMaxId(ns_id)); - max_meta_page_id = std::max(v2_meta_max_ids[0], global_storage_pool->getMetaMaxId(ns_id)); + max_log_page_id = std::max(log_storage_v2->getMaxId(ns_id), log_storage_v3->getMaxId(ns_id)); + max_data_page_id = std::max(data_storage_v2->getMaxId(ns_id), data_storage_v3->getMaxId(ns_id)); + max_meta_page_id = std::max(meta_storage_v2->getMaxId(ns_id), meta_storage_v3->getMaxId(ns_id)); storage_pool_metrics = CurrentMetrics::Increment{CurrentMetrics::StoragePoolMixMode}; } break; diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.h b/dbms/src/Storages/DeltaMerge/StoragePool.h index 2e3b3f563f5..f106ac725e4 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.h +++ b/dbms/src/Storages/DeltaMerge/StoragePool.h @@ -51,39 +51,6 @@ class GlobalStoragePool : private boost::noncopyable friend class StoragePool; - PageId getLogMaxId(NamespaceId ns_id) const - { - PageId max_log_page_id = 0; - if (const auto & it = log_max_ids.find(ns_id); it != log_max_ids.end()) - { - max_log_page_id = it->second; - } - - return max_log_page_id; - } - - PageId getDataMaxId(NamespaceId ns_id) const - { - PageId max_data_page_id = 0; - if (const auto & it = data_max_ids.find(ns_id); it != data_max_ids.end()) - { - max_data_page_id = it->second; - } - - return max_data_page_id; - } - - PageId getMetaMaxId(NamespaceId ns_id) const - { - PageId max_meta_page_id = 0; - if (const auto & it = meta_max_ids.find(ns_id); it != meta_max_ids.end()) - { - max_meta_page_id = it->second; - } - - return max_meta_page_id; - } - // GC immediately // Only used on dbgFuncMisc bool gc(); @@ -96,10 +63,6 @@ class GlobalStoragePool : private boost::noncopyable PageStoragePtr data_storage; PageStoragePtr meta_storage; - std::map log_max_ids; - std::map data_max_ids; - std::map meta_max_ids; - std::atomic last_try_gc_time = Clock::now(); Context & global_context; diff --git a/dbms/src/Storages/Page/PageStorage.h b/dbms/src/Storages/Page/PageStorage.h index 5fc69c29364..06f3be5d1f7 100644 --- a/dbms/src/Storages/Page/PageStorage.h +++ b/dbms/src/Storages/Page/PageStorage.h @@ -229,16 +229,12 @@ class PageStorage : private boost::noncopyable virtual ~PageStorage() = default; - // Return the map[ns_id, max_page_id] - // The caller should ensure that it only allocate new id that is larger than `max_page_id`. Reusing the - // same ID for different kind of write (put/ref/put_external) would make PageStorage run into unexpected error. - // - // Note that for V2, we always return a map with only one element: cause V2 have no - // idea about ns_id. - virtual std::map restore() = 0; + virtual void restore() = 0; virtual void drop() = 0; + virtual PageId getMaxId(NamespaceId ns_id) = 0; + virtual SnapshotPtr getSnapshot(const String & tracing_id) = 0; // Get some statistics of all living snapshots and the oldest living snapshot. diff --git a/dbms/src/Storages/Page/V2/PageStorage.cpp b/dbms/src/Storages/Page/V2/PageStorage.cpp index 0452d0cc8ae..3ab62d55242 100644 --- a/dbms/src/Storages/Page/V2/PageStorage.cpp +++ b/dbms/src/Storages/Page/V2/PageStorage.cpp @@ -196,7 +196,7 @@ toConcreteSnapshot(const DB::PageStorage::SnapshotPtr & ptr) return assert_cast(ptr.get()); } -std::map PageStorage::restore() +void PageStorage::restore() { LOG_FMT_INFO(log, "{} begin to restore data from disk. [path={}] [num_writers={}]", storage_name, delegator->defaultPath(), write_files.size()); @@ -353,9 +353,12 @@ std::map PageStorage::restore() auto snapshot = getConcreteSnapshot(); size_t num_pages = snapshot->version()->numPages(); LOG_FMT_INFO(log, "{} restore {} pages, write batch sequence: {}, {}", storage_name, num_pages, write_batch_seq, statistics.toString()); +} - // Fixed namespace id 0 - return {{0, snapshot->version()->maxId()}}; +PageId PageStorage::getMaxId(NamespaceId /*ns_id*/) +{ + std::lock_guard write_lock(write_mutex); + return versioned_page_entries.getSnapshot("")->version()->maxId(); } PageId PageStorage::getNormalPageIdImpl(NamespaceId /*ns_id*/, PageId page_id, SnapshotPtr snapshot, bool throw_on_not_exist) diff --git a/dbms/src/Storages/Page/V2/PageStorage.h b/dbms/src/Storages/Page/V2/PageStorage.h index 6276c5e5086..cb55a769f37 100644 --- a/dbms/src/Storages/Page/V2/PageStorage.h +++ b/dbms/src/Storages/Page/V2/PageStorage.h @@ -91,10 +91,12 @@ class PageStorage : public DB::PageStorage const FileProviderPtr & file_provider_); ~PageStorage() = default; - std::map restore() override; + void restore() override; void drop() override; + PageId getMaxId(NamespaceId ns_id) override; + PageId getNormalPageIdImpl(NamespaceId ns_id, PageId page_id, SnapshotPtr snapshot, bool throw_on_not_exist) override; DB::PageStorage::SnapshotPtr getSnapshot(const String & tracing_id) override; diff --git a/dbms/src/Storages/Page/V2/tests/gtest_page_storage.cpp b/dbms/src/Storages/Page/V2/tests/gtest_page_storage.cpp index 5bbd319192b..fc429dde0ac 100644 --- a/dbms/src/Storages/Page/V2/tests/gtest_page_storage.cpp +++ b/dbms/src/Storages/Page/V2/tests/gtest_page_storage.cpp @@ -79,15 +79,6 @@ class PageStorage_test : public DB::base::TiFlashStorageTestBasic return storage; } - std::pair, std::map> reopen() - { - auto delegator = path_pool->getPSDiskDelegatorSingle("log"); - auto storage = std::make_shared("test.t", delegator, config, file_provider); - auto max_ids = storage->restore(); - return {storage, max_ids}; - } - - protected: PageStorage::Config config; std::shared_ptr storage; @@ -736,25 +727,6 @@ try } CATCH -TEST_F(PageStorage_test, getMaxIdsFromRestore) -try -{ - { - WriteBatch batch; - batch.putExternal(1, 0); - batch.putExternal(2, 0); - batch.delPage(1); - batch.delPage(2); - storage->write(std::move(batch)); - } - - storage = nullptr; - auto [page_storage, max_ids] = reopen(); - ASSERT_EQ(max_ids.size(), 1); - ASSERT_EQ(max_ids[0], 2); -} -CATCH - TEST_F(PageStorage_test, IgnoreIncompleteWriteBatch1) try { diff --git a/dbms/src/Storages/Page/V3/BlobStore.h b/dbms/src/Storages/Page/V3/BlobStore.h index b5cf5a677f1..e527eb0f3bf 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.h +++ b/dbms/src/Storages/Page/V3/BlobStore.h @@ -34,7 +34,7 @@ extern const int LOGICAL_ERROR; namespace PS::V3 { -using PageIdAndVersionedEntries = std::vector>; +using PageIdAndVersionedEntries = std::vector>; class BlobStore : private Allocator { diff --git a/dbms/src/Storages/Page/V3/PageDirectory.cpp b/dbms/src/Storages/Page/V3/PageDirectory.cpp index 35e73e5c5ff..06b26156529 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectory.cpp @@ -57,6 +57,7 @@ namespace ErrorCodes extern const int NOT_IMPLEMENTED; extern const int PS_ENTRY_NOT_EXISTS; extern const int PS_ENTRY_NO_VALID_VERSION; +extern const int PS_DIR_APPLY_INVALID_STATUS; } // namespace ErrorCodes namespace PS::V3 @@ -65,7 +66,7 @@ namespace PS::V3 * VersionedPageEntries methods * ********************************/ -void VersionedPageEntries::createNewEntry(const PageVersionType & ver, const PageEntryV3 & entry) +void VersionedPageEntries::createNewEntry(const PageVersion & ver, const PageEntryV3 & entry) { auto page_lock = acquireLock(); if (type == EditRecordType::VAR_DELETE) @@ -77,7 +78,7 @@ void VersionedPageEntries::createNewEntry(const PageVersionType & ver, const Pag if (type == EditRecordType::VAR_ENTRY) { - auto last_iter = MapUtils::findLess(entries, PageVersionType(ver.sequence + 1, 0)); + auto last_iter = MapUtils::findLess(entries, PageVersion(ver.sequence + 1, 0)); if (last_iter == entries.end()) { entries.emplace(ver, EntryOrDelete::newNormalEntry(entry)); @@ -93,11 +94,12 @@ void VersionedPageEntries::createNewEntry(const PageVersionType & ver, const Pag // to replace the entry with newer sequence. if (unlikely(last_iter->second.being_ref_count != 1 && last_iter->first.sequence < ver.sequence)) { - throw Exception(fmt::format( - "Try to replace normal entry with an newer seq [ver={}] [prev_ver={}] [last_entry={}]", - ver, - last_iter->first, - last_iter->second.toDebugString())); + throw Exception( + fmt::format("Try to replace normal entry with an newer seq [ver={}] [prev_ver={}] [last_entry={}]", + ver, + last_iter->first, + last_iter->second.toDebugString()), + ErrorCodes::LOGICAL_ERROR); } // create a new version that inherit the `being_ref_count` of the last entry entries.emplace(ver, EntryOrDelete::newRepalcingEntry(last_iter->second, entry)); @@ -105,18 +107,19 @@ void VersionedPageEntries::createNewEntry(const PageVersionType & ver, const Pag return; } - throw Exception(fmt::format( - "try to create entry version with invalid state " - "[ver={}] [entry={}] [state={}]", - ver, - ::DB::PS::V3::toDebugString(entry), - toDebugString())); + throw Exception( + fmt::format("try to create entry version with invalid state " + "[ver={}] [entry={}] [state={}]", + ver, + ::DB::PS::V3::toDebugString(entry), + toDebugString()), + ErrorCodes::PS_DIR_APPLY_INVALID_STATUS); } // Create a new external version with version=`ver`. // If create success, then return a shared_ptr as a holder for page_id. The holder // will be release when this external version is totally removed. -std::shared_ptr VersionedPageEntries::createNewExternal(const PageVersionType & ver) +std::shared_ptr VersionedPageEntries::createNewExternal(const PageVersion & ver) { auto page_lock = acquireLock(); if (type == EditRecordType::VAR_DELETE) @@ -124,7 +127,7 @@ std::shared_ptr VersionedPageEntries::createNewExternal(const type = EditRecordType::VAR_EXTERNAL; is_deleted = false; create_ver = ver; - delete_ver = PageVersionType(0); + delete_ver = PageVersion(0); being_ref_count = 1; // return the new created holder to caller to set the page_id external_holder = std::make_shared(0, 0); @@ -140,7 +143,7 @@ std::shared_ptr VersionedPageEntries::createNewExternal(const { is_deleted = false; create_ver = ver; - delete_ver = PageVersionType(0); + delete_ver = PageVersion(0); being_ref_count = 1; // return the new created holder to caller to set the page_id external_holder = std::make_shared(0, 0); @@ -160,15 +163,16 @@ std::shared_ptr VersionedPageEntries::createNewExternal(const } } - throw Exception(fmt::format( - "try to create external version with invalid state " - "[ver={}] [state={}]", - ver, - toDebugString())); + throw Exception( + fmt::format("try to create external version with invalid state " + "[ver={}] [state={}]", + ver, + toDebugString()), + ErrorCodes::PS_DIR_APPLY_INVALID_STATUS); } // Create a new delete version with version=`ver`. -void VersionedPageEntries::createDelete(const PageVersionType & ver) +void VersionedPageEntries::createDelete(const PageVersion & ver) { auto page_lock = acquireLock(); if (type == EditRecordType::VAR_ENTRY) @@ -203,7 +207,7 @@ void VersionedPageEntries::createDelete(const PageVersionType & ver) // Create a new reference version with version=`ver` and `ori_page_id_`. // If create success, then return true, otherwise return false. -bool VersionedPageEntries::createNewRef(const PageVersionType & ver, PageIdV3Internal ori_page_id_) +bool VersionedPageEntries::createNewRef(const PageVersion & ver, PageIdV3Internal ori_page_id_) { auto page_lock = acquireLock(); if (type == EditRecordType::VAR_DELETE) @@ -225,7 +229,7 @@ bool VersionedPageEntries::createNewRef(const PageVersionType & ver, PageIdV3Int ori_page_id = ori_page_id_; create_ver = ver; is_deleted = false; - delete_ver = PageVersionType(0); + delete_ver = PageVersion(0); return true; } else if (ori_page_id == ori_page_id_) @@ -248,11 +252,12 @@ bool VersionedPageEntries::createNewRef(const PageVersionType & ver, PageIdV3Int // adding ref to replace put/external is not allowed throw Exception(fmt::format( - "try to create ref version with invalid state " - "[ver={}] [ori_page_id={}] [state={}]", - ver, - ori_page_id_, - toDebugString())); + "try to create ref version with invalid state " + "[ver={}] [ori_page_id={}] [state={}]", + ver, + ori_page_id_, + toDebugString()), + ErrorCodes::PS_DIR_APPLY_INVALID_STATUS); } std::shared_ptr VersionedPageEntries::fromRestored(const PageEntriesEdit::EditRecord & rec) @@ -287,14 +292,14 @@ std::shared_ptr VersionedPageEntries::fromRestored(const PageE } } -std::tuple +std::tuple VersionedPageEntries::resolveToPageId(UInt64 seq, bool check_prev, PageEntryV3 * entry) { auto page_lock = acquireLock(); if (type == EditRecordType::VAR_ENTRY) { // entries are sorted by , find the first one less than - if (auto iter = MapUtils::findLess(entries, PageVersionType(seq + 1)); + if (auto iter = MapUtils::findLess(entries, PageVersion(seq + 1)); iter != entries.end()) { // If we applied write batches like this: [ver=1]{put 10}, [ver=2]{ref 11->10, del 10} @@ -304,7 +309,7 @@ VersionedPageEntries::resolveToPageId(UInt64 seq, bool check_prev, PageEntryV3 * { if (iter == entries.begin()) { - return {RESOLVE_FAIL, buildV3Id(0, 0), PageVersionType(0)}; + return {RESOLVE_FAIL, buildV3Id(0, 0), PageVersion(0)}; } --iter; // fallover the check the prev item @@ -314,7 +319,7 @@ VersionedPageEntries::resolveToPageId(UInt64 seq, bool check_prev, PageEntryV3 * { if (entry != nullptr) *entry = iter->second.entry; - return {RESOLVE_TO_NORMAL, buildV3Id(0, 0), PageVersionType(0)}; + return {RESOLVE_TO_NORMAL, buildV3Id(0, 0), PageVersion(0)}; } // fallover to FAIL } } @@ -324,7 +329,7 @@ VersionedPageEntries::resolveToPageId(UInt64 seq, bool check_prev, PageEntryV3 * bool ok = check_prev ? true : (!is_deleted || seq < delete_ver.sequence); if (create_ver.sequence <= seq && ok) { - return {RESOLVE_TO_NORMAL, buildV3Id(0, 0), PageVersionType(0)}; + return {RESOLVE_TO_NORMAL, buildV3Id(0, 0), PageVersion(0)}; } } else if (type == EditRecordType::VAR_REF) @@ -339,7 +344,7 @@ VersionedPageEntries::resolveToPageId(UInt64 seq, bool check_prev, PageEntryV3 * LOG_FMT_WARNING(&Poco::Logger::get("VersionedPageEntries"), "Can't reslove the EditRecordType {}", type); } - return {RESOLVE_FAIL, buildV3Id(0, 0), PageVersionType(0)}; + return {RESOLVE_FAIL, buildV3Id(0, 0), PageVersion(0)}; } std::optional VersionedPageEntries::getEntry(UInt64 seq) const @@ -348,10 +353,10 @@ std::optional VersionedPageEntries::getEntry(UInt64 seq) const if (type == EditRecordType::VAR_ENTRY) { // entries are sorted by , find the first one less than - if (auto iter = MapUtils::findLess(entries, PageVersionType(seq + 1)); + if (auto iter = MapUtils::findLess(entries, PageVersion(seq + 1)); iter != entries.end()) { - // NORMAL + // not deleted if (iter->second.isEntry()) return iter->second.entry; } @@ -375,12 +380,48 @@ std::optional VersionedPageEntries::getLastEntry() const return std::nullopt; } -Int64 VersionedPageEntries::incrRefCount(const PageVersionType & ver) +// Returns true when **this id** is "visible" by `seq`. +// If this page id is marked as deleted or not created, it is "not visible". +// Note that not visible does not means this id can be GC. +bool VersionedPageEntries::isVisible(UInt64 seq) const +{ + auto page_lock = acquireLock(); + if (type == EditRecordType::VAR_DELETE) + { + return false; + } + else if (type == EditRecordType::VAR_ENTRY) + { + // entries are sorted by , find the first one less than + if (auto iter = MapUtils::findLess(entries, PageVersion(seq + 1)); + iter != entries.end()) + { + // not deleted + return iter->second.isEntry(); + } + // else there are no valid entry less than seq + return false; + } + else if (type == EditRecordType::VAR_EXTERNAL || type == EditRecordType::VAR_REF) + { + // `delete_ver` is only valid when `is_deleted == true` + return create_ver.sequence <= seq && !(is_deleted && delete_ver.sequence <= seq); + } + + throw Exception(fmt::format( + "calling isDeleted with invalid state " + "[seq={}] [state={}]", + seq, + toDebugString()), + ErrorCodes::LOGICAL_ERROR); +} + +Int64 VersionedPageEntries::incrRefCount(const PageVersion & ver) { auto page_lock = acquireLock(); if (type == EditRecordType::VAR_ENTRY) { - if (auto iter = MapUtils::findMutLess(entries, PageVersionType(ver.sequence + 1)); + if (auto iter = MapUtils::findMutLess(entries, PageVersion(ver.sequence + 1)); iter != entries.end()) { if (iter->second.isEntry()) @@ -435,7 +476,7 @@ PageSize VersionedPageEntries::getEntriesByBlobIds( bool VersionedPageEntries::cleanOutdatedEntries( UInt64 lowest_seq, - std::map> * normal_entries_to_deref, + std::map> * normal_entries_to_deref, PageEntriesV3 & entries_removed, const PageLock & /*page_lock*/) { @@ -474,7 +515,7 @@ bool VersionedPageEntries::cleanOutdatedEntries( return true; } - auto iter = MapUtils::findLess(entries, PageVersionType(lowest_seq + 1)); + auto iter = MapUtils::findLess(entries, PageVersion(lowest_seq + 1)); // If we can't find any seq lower than `lowest_seq` then // all version in this list don't need gc. if (iter == entries.begin() || iter == entries.end()) @@ -522,7 +563,7 @@ bool VersionedPageEntries::cleanOutdatedEntries( return entries.empty() || (entries.size() == 1 && entries.begin()->second.isDelete()); } -bool VersionedPageEntries::derefAndClean(UInt64 lowest_seq, PageIdV3Internal page_id, const PageVersionType & deref_ver, const Int64 deref_count, PageEntriesV3 & entries_removed) +bool VersionedPageEntries::derefAndClean(UInt64 lowest_seq, PageIdV3Internal page_id, const PageVersion & deref_ver, const Int64 deref_count, PageEntriesV3 & entries_removed) { auto page_lock = acquireLock(); if (type == EditRecordType::VAR_EXTERNAL) @@ -538,7 +579,7 @@ bool VersionedPageEntries::derefAndClean(UInt64 lowest_seq, PageIdV3Internal pag { // Decrease the ref-counter. The entry may be moved to a newer entry with same sequence but higher epoch, // so we need to find the one less than and decrease the ref-counter of it. - auto iter = MapUtils::findMutLess(entries, PageVersionType(deref_ver.sequence + 1, 0)); + auto iter = MapUtils::findMutLess(entries, PageVersion(deref_ver.sequence + 1, 0)); if (iter == entries.end()) { throw Exception(fmt::format("Can not find entry for decreasing ref count [page_id={}] [ver={}] [deref_count={}]", page_id, deref_ver, deref_count)); @@ -598,7 +639,7 @@ void VersionedPageEntries::collapseTo(const UInt64 seq, const PageIdV3Internal p if (type == EditRecordType::VAR_ENTRY) { // dump the latest entry if it is not a "delete" - auto last_iter = MapUtils::findLess(entries, PageVersionType(seq + 1)); + auto last_iter = MapUtils::findLess(entries, PageVersion(seq + 1)); if (last_iter == entries.end()) return; @@ -709,7 +750,7 @@ PageIDAndEntryV3 PageDirectory::get(PageIdV3Internal page_id, const PageDirector PageEntryV3 entry_got; PageIdV3Internal id_to_resolve = page_id; - PageVersionType ver_to_resolve(snap->sequence, 0); + PageVersion ver_to_resolve(snap->sequence, 0); bool ok = true; while (ok) { @@ -762,8 +803,8 @@ std::pair PageDirectory::get(const PageIdV3Internal PageEntryV3 entry_got; PageIds page_not_found = {}; - const PageVersionType init_ver_to_resolve(snap->sequence, 0); - auto get_one = [&entry_got, init_ver_to_resolve, throw_on_not_exist, this](PageIdV3Internal page_id, PageVersionType ver_to_resolve, size_t idx) { + const PageVersion init_ver_to_resolve(snap->sequence, 0); + auto get_one = [&entry_got, init_ver_to_resolve, throw_on_not_exist, this](PageIdV3Internal page_id, PageVersion ver_to_resolve, size_t idx) { PageIdV3Internal id_to_resolve = page_id; bool ok = true; while (ok) @@ -825,7 +866,7 @@ std::pair PageDirectory::get(const PageIdV3Internal PageIdV3Internal PageDirectory::getNormalPageId(PageIdV3Internal page_id, const PageDirectorySnapshotPtr & snap, bool throw_on_not_exist) const { PageIdV3Internal id_to_resolve = page_id; - PageVersionType ver_to_resolve(snap->sequence, 0); + PageVersion ver_to_resolve(snap->sequence, 0); bool keep_resolve = true; while (keep_resolve) { @@ -899,10 +940,31 @@ PageId PageDirectory::getMaxId(NamespaceId ns_id) const // iter is not at the beginning and mvcc_table_directory is not empty, // so iter-- must be a valid iterator, and it's the largest page id which is smaller than the target page id. iter--; - if (iter->first.high == ns_id) - return iter->first.low; - else - return 0; + + do + { + // Can't find any entries in current ns_id + if (iter->first.high != ns_id) + { + break; + } + + // Check and return whether this id is visible, otherwise continue to check the previous one. + if (iter->second->isVisible(UINT64_MAX - 1)) + { + return iter->first.low; + } + + // Current entry/ref/external is deleted and there are no entries before it. + if (iter == mvcc_table_directory.begin()) + { + break; + } + + iter--; + } while (true); + + return 0; } } @@ -923,17 +985,17 @@ void PageDirectory::applyRefEditRecord( MVCCMapType & mvcc_table_directory, const VersionedPageEntriesPtr & version_list, const PageEntriesEdit::EditRecord & rec, - const PageVersionType & version) + const PageVersion & version) { // applying ref 3->2, existing ref 2->1, normal entry 1, then we should collapse // the ref to be 3->1, increase the refcounting of normale entry 1 - auto [resolve_success, resolved_id, resolved_ver] = [&mvcc_table_directory](PageIdV3Internal id_to_resolve, PageVersionType ver_to_resolve) - -> std::tuple { + auto [resolve_success, resolved_id, resolved_ver] = [&mvcc_table_directory](PageIdV3Internal id_to_resolve, PageVersion ver_to_resolve) + -> std::tuple { while (true) { auto resolve_ver_iter = mvcc_table_directory.find(id_to_resolve); if (resolve_ver_iter == mvcc_table_directory.end()) - return {false, buildV3Id(0, 0), PageVersionType(0)}; + return {false, buildV3Id(0, 0), PageVersion(0)}; const VersionedPageEntriesPtr & resolve_version_list = resolve_ver_iter->second; // If we already hold the lock from `id_to_resolve`, then we should not request it again. @@ -999,7 +1061,7 @@ void PageDirectory::apply(PageEntriesEdit && edit, const WriteLimiterPtr & write // TODO: It is totally serialized, make it a pipeline std::unique_lock write_lock(table_rw_mutex); UInt64 last_sequence = sequence.load(); - PageVersionType new_version(last_sequence + 1, 0); + PageVersion new_version(last_sequence + 1, 0); // stage 1, persisted the changes to WAL with version [seq=last_seq + 1, epoch=0] wal->apply(edit, new_version, write_limiter); @@ -1233,7 +1295,7 @@ PageEntriesV3 PageDirectory::gcInMemEntries() // The page_id that we need to decrease ref count // { id_0: , id_1: <...>, ... } - std::map> normal_entries_to_deref; + std::map> normal_entries_to_deref; // Iterate all page_id and try to clean up useless var entries while (true) { diff --git a/dbms/src/Storages/Page/V3/PageDirectory.h b/dbms/src/Storages/Page/V3/PageDirectory.h index 7f56676e363..635cf04bfe6 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.h +++ b/dbms/src/Storages/Page/V3/PageDirectory.h @@ -149,13 +149,13 @@ class VersionedPageEntries return std::lock_guard(m); } - void createNewEntry(const PageVersionType & ver, const PageEntryV3 & entry); + void createNewEntry(const PageVersion & ver, const PageEntryV3 & entry); - bool createNewRef(const PageVersionType & ver, PageIdV3Internal ori_page_id); + bool createNewRef(const PageVersion & ver, PageIdV3Internal ori_page_id); - std::shared_ptr createNewExternal(const PageVersionType & ver); + std::shared_ptr createNewExternal(const PageVersion & ver); - void createDelete(const PageVersionType & ver); + void createDelete(const PageVersion & ver); std::shared_ptr fromRestored(const PageEntriesEdit::EditRecord & rec); @@ -165,15 +165,17 @@ class VersionedPageEntries RESOLVE_TO_REF, RESOLVE_TO_NORMAL, }; - std::tuple + std::tuple resolveToPageId(UInt64 seq, bool check_prev, PageEntryV3 * entry); - Int64 incrRefCount(const PageVersionType & ver); + Int64 incrRefCount(const PageVersion & ver); std::optional getEntry(UInt64 seq) const; std::optional getLastEntry() const; + bool isVisible(UInt64 seq) const; + /** * If there are entries point to file in `blob_ids`, take out the and * store them into `blob_versioned_entries`. @@ -220,13 +222,13 @@ class VersionedPageEntries */ bool cleanOutdatedEntries( UInt64 lowest_seq, - std::map> * normal_entries_to_deref, + std::map> * normal_entries_to_deref, PageEntriesV3 & entries_removed, const PageLock & page_lock); bool derefAndClean( UInt64 lowest_seq, PageIdV3Internal page_id, - const PageVersionType & deref_ver, + const PageVersion & deref_ver, Int64 deref_count, PageEntriesV3 & entries_removed); @@ -258,15 +260,21 @@ class VersionedPageEntries private: mutable std::mutex m; + // Valid value of `type` is one of + // - VAR_DELETE + // - VAR_ENTRY + // - VAR_REF + // - VAR_EXTERNAL EditRecordType type; + // Has been deleted, valid when type == VAR_REF/VAR_EXTERNAL bool is_deleted; // Entries sorted by version, valid when type == VAR_ENTRY - std::multimap entries; + std::multimap entries; // The created version, valid when type == VAR_REF/VAR_EXTERNAL - PageVersionType create_ver; + PageVersion create_ver; // The deleted version, valid when type == VAR_REF/VAR_EXTERNAL && is_deleted = true - PageVersionType delete_ver; + PageVersion delete_ver; // Original page id, valid when type == VAR_REF PageIdV3Internal ori_page_id; // Being ref counter, valid when type == VAR_EXTERNAL @@ -379,7 +387,7 @@ class PageDirectory MVCCMapType & mvcc_table_directory, const VersionedPageEntriesPtr & version_list, const PageEntriesEdit::EditRecord & rec, - const PageVersionType & version); + const PageVersion & version); static inline PageDirectorySnapshotPtr toConcreteSnapshot(const DB::PageStorageSnapshotPtr & ptr) diff --git a/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp b/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp index 4f2a8a3fbd4..40b12b64f06 100644 --- a/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp @@ -20,7 +20,13 @@ #include -namespace DB::PS::V3 +namespace DB +{ +namespace ErrorCodes +{ +extern const int PS_DIR_APPLY_INVALID_STATUS; +} // namespace ErrorCodes +namespace PS::V3 { PageDirectoryPtr PageDirectoryFactory::create(String storage_name, FileProviderPtr & file_provider, PSDiskDelegatorPtr & delegator, WALStore::Config config) { @@ -101,80 +107,94 @@ PageDirectoryPtr PageDirectoryFactory::createFromEdit(String storage_name, FileP void PageDirectoryFactory::loadEdit(const PageDirectoryPtr & dir, const PageEntriesEdit & edit) { - PageDirectory::MVCCMapType & mvcc_table_directory = dir->mvcc_table_directory; - for (const auto & r : edit.getRecords()) { - if (auto it = max_apply_page_ids.find(r.page_id.high); it == max_apply_page_ids.end()) - { - max_apply_page_ids[r.page_id.high] = r.page_id.low; - } - else - { - it->second = std::max(it->second, r.page_id.low); - } - if (max_applied_ver < r.version) max_applied_ver = r.version; max_applied_page_id = std::max(r.page_id, max_applied_page_id); - auto [iter, created] = mvcc_table_directory.insert(std::make_pair(r.page_id, nullptr)); - if (created) + // We can not avoid page id from being reused under some corner situation. Try to do gcInMemEntries + // and apply again to resolve the error. + if (bool ok = applyRecord(dir, r, /*throw_on_error*/ false); unlikely(!ok)) { - iter->second = std::make_shared(); + dir->gcInMemEntries(); + applyRecord(dir, r, /*throw_on_error*/ true); + LOG_FMT_INFO(DB::Logger::get("PageDirectoryFactory"), "resolve from error status done, continue to restore"); } + } +} - const auto & version_list = iter->second; - const auto & restored_version = r.version; - try +bool PageDirectoryFactory::applyRecord( + const PageDirectoryPtr & dir, + const PageEntriesEdit::EditRecord & r, + bool throw_on_error) +{ + auto [iter, created] = dir->mvcc_table_directory.insert(std::make_pair(r.page_id, nullptr)); + if (created) + { + iter->second = std::make_shared(); + } + + const auto & version_list = iter->second; + const auto & restored_version = r.version; + try + { + switch (r.type) { - switch (r.type) - { - case EditRecordType::VAR_EXTERNAL: - case EditRecordType::VAR_REF: + case EditRecordType::VAR_EXTERNAL: + case EditRecordType::VAR_REF: + { + auto holder = version_list->fromRestored(r); + if (holder) { - auto holder = version_list->fromRestored(r); - if (holder) - { - *holder = r.page_id; - dir->external_ids.emplace_back(std::weak_ptr(holder)); - } - break; + *holder = r.page_id; + dir->external_ids.emplace_back(std::weak_ptr(holder)); } - case EditRecordType::VAR_ENTRY: - version_list->fromRestored(r); - break; - case EditRecordType::PUT_EXTERNAL: + break; + } + case EditRecordType::VAR_ENTRY: + version_list->fromRestored(r); + break; + case EditRecordType::PUT_EXTERNAL: + { + auto holder = version_list->createNewExternal(restored_version); + if (holder) { - auto holder = version_list->createNewExternal(restored_version); - if (holder) - { - *holder = r.page_id; - dir->external_ids.emplace_back(std::weak_ptr(holder)); - } - break; - } - case EditRecordType::PUT: - version_list->createNewEntry(restored_version, r.entry); - break; - case EditRecordType::DEL: - case EditRecordType::VAR_DELETE: // nothing different from `DEL` - version_list->createDelete(restored_version); - break; - case EditRecordType::REF: - PageDirectory::applyRefEditRecord(mvcc_table_directory, version_list, r, restored_version); - break; - case EditRecordType::UPSERT: - version_list->createNewEntry(restored_version, r.entry); - break; + *holder = r.page_id; + dir->external_ids.emplace_back(std::weak_ptr(holder)); } + break; } - catch (DB::Exception & e) + case EditRecordType::PUT: + version_list->createNewEntry(restored_version, r.entry); + break; + case EditRecordType::DEL: + case EditRecordType::VAR_DELETE: // nothing different from `DEL` + version_list->createDelete(restored_version); + break; + case EditRecordType::REF: + PageDirectory::applyRefEditRecord( + dir->mvcc_table_directory, + version_list, + r, + restored_version); + break; + case EditRecordType::UPSERT: + version_list->createNewEntry(restored_version, r.entry); + break; + } + } + catch (DB::Exception & e) + { + e.addMessage(fmt::format(" [type={}] [page_id={}] [ver={}]", r.type, r.page_id, restored_version)); + if (throw_on_error || e.code() != ErrorCodes::PS_DIR_APPLY_INVALID_STATUS) { - e.addMessage(fmt::format(" [type={}] [page_id={}] [ver={}]", r.type, r.page_id, restored_version)); throw e; } + LOG_FMT_WARNING(DB::Logger::get("PageDirectoryFactory"), "try to resolve error during restore: {}", e.message()); + return false; } + return true; } void PageDirectoryFactory::loadFromDisk(const PageDirectoryPtr & dir, WALStoreReaderPtr && reader) @@ -196,4 +216,5 @@ void PageDirectoryFactory::loadFromDisk(const PageDirectoryPtr & dir, WALStoreRe loadEdit(dir, edit); } } -} // namespace DB::PS::V3 +} // namespace PS::V3 +} // namespace DB diff --git a/dbms/src/Storages/Page/V3/PageDirectoryFactory.h b/dbms/src/Storages/Page/V3/PageDirectoryFactory.h index 4136e626050..11337e4a6cc 100644 --- a/dbms/src/Storages/Page/V3/PageDirectoryFactory.h +++ b/dbms/src/Storages/Page/V3/PageDirectoryFactory.h @@ -37,7 +37,7 @@ using WALStoreReaderPtr = std::shared_ptr; class PageDirectoryFactory { public: - PageVersionType max_applied_ver; + PageVersion max_applied_ver; PageIdV3Internal max_applied_page_id; PageDirectoryFactory & setBlobStore(BlobStore & blob_store) @@ -58,17 +58,15 @@ class PageDirectoryFactory return *this; } - std::map getMaxApplyPageIds() const - { - return max_apply_page_ids; - } - private: void loadFromDisk(const PageDirectoryPtr & dir, WALStoreReaderPtr && reader); void loadEdit(const PageDirectoryPtr & dir, const PageEntriesEdit & edit); + static bool applyRecord( + const PageDirectoryPtr & dir, + const PageEntriesEdit::EditRecord & r, + bool throw_on_error); BlobStore::BlobStats * blob_stats = nullptr; - std::map max_apply_page_ids; }; } // namespace PS::V3 diff --git a/dbms/src/Storages/Page/V3/PageEntriesEdit.h b/dbms/src/Storages/Page/V3/PageEntriesEdit.h index 1702b9e575f..b75b16da4a4 100644 --- a/dbms/src/Storages/Page/V3/PageEntriesEdit.h +++ b/dbms/src/Storages/Page/V3/PageEntriesEdit.h @@ -27,45 +27,45 @@ namespace DB::PS::V3 // `PageDirectory::apply` with create a version={directory.sequence, epoch=0}. // After data compaction and page entries need to be updated, will create // some entries with a version={old_sequence, epoch=old_epoch+1}. -struct PageVersionType +struct PageVersion { UInt64 sequence; // The write sequence UInt64 epoch; // The GC epoch - explicit PageVersionType(UInt64 seq) + explicit PageVersion(UInt64 seq) : sequence(seq) , epoch(0) {} - PageVersionType(UInt64 seq, UInt64 epoch_) + PageVersion(UInt64 seq, UInt64 epoch_) : sequence(seq) , epoch(epoch_) {} - PageVersionType() - : PageVersionType(0) + PageVersion() + : PageVersion(0) {} - bool operator<(const PageVersionType & rhs) const + bool operator<(const PageVersion & rhs) const { if (sequence == rhs.sequence) return epoch < rhs.epoch; return sequence < rhs.sequence; } - bool operator==(const PageVersionType & rhs) const + bool operator==(const PageVersion & rhs) const { return (sequence == rhs.sequence) && (epoch == rhs.epoch); } - bool operator<=(const PageVersionType & rhs) const + bool operator<=(const PageVersion & rhs) const { if (sequence == rhs.sequence) return epoch <= rhs.epoch; return sequence <= rhs.sequence; } }; -using VersionedEntry = std::pair; +using VersionedEntry = std::pair; using VersionedEntries = std::vector; enum class EditRecordType @@ -76,7 +76,8 @@ enum class EditRecordType DEL, // UPSERT, - // + // Variant types for dumping the in-memory entries into + // snapshot VAR_ENTRY, VAR_REF, VAR_EXTERNAL, @@ -138,7 +139,7 @@ class PageEntriesEdit records.emplace_back(record); } - void upsertPage(PageIdV3Internal page_id, const PageVersionType & ver, const PageEntryV3 & entry) + void upsertPage(PageIdV3Internal page_id, const PageVersion & ver, const PageEntryV3 & entry) { EditRecord record{}; record.type = EditRecordType::UPSERT; @@ -165,7 +166,7 @@ class PageEntriesEdit records.emplace_back(record); } - void varRef(PageIdV3Internal ref_id, const PageVersionType & ver, PageIdV3Internal ori_page_id) + void varRef(PageIdV3Internal ref_id, const PageVersion & ver, PageIdV3Internal ori_page_id) { EditRecord record{}; record.type = EditRecordType::VAR_REF; @@ -175,7 +176,7 @@ class PageEntriesEdit records.emplace_back(record); } - void varExternal(PageIdV3Internal page_id, const PageVersionType & create_ver, Int64 being_ref_count) + void varExternal(PageIdV3Internal page_id, const PageVersion & create_ver, Int64 being_ref_count) { EditRecord record{}; record.type = EditRecordType::VAR_EXTERNAL; @@ -185,7 +186,7 @@ class PageEntriesEdit records.emplace_back(record); } - void varEntry(PageIdV3Internal page_id, const PageVersionType & ver, const PageEntryV3 & entry, Int64 being_ref_count) + void varEntry(PageIdV3Internal page_id, const PageVersion & ver, const PageEntryV3 & entry, Int64 being_ref_count) { EditRecord record{}; record.type = EditRecordType::VAR_ENTRY; @@ -196,7 +197,7 @@ class PageEntriesEdit records.emplace_back(record); } - void varDel(PageIdV3Internal page_id, const PageVersionType & delete_ver) + void varDel(PageIdV3Internal page_id, const PageVersion & delete_ver) { EditRecord record{}; record.type = EditRecordType::VAR_DELETE; @@ -216,7 +217,7 @@ class PageEntriesEdit EditRecordType type; PageIdV3Internal page_id; PageIdV3Internal ori_page_id; - PageVersionType version; + PageVersion version; PageEntryV3 entry; Int64 being_ref_count; @@ -259,7 +260,7 @@ class PageEntriesEdit { putExternal(buildV3Id(TEST_NAMESPACE_ID, page_id)); } - void upsertPage(PageId page_id, const PageVersionType & ver, const PageEntryV3 & entry) + void upsertPage(PageId page_id, const PageVersion & ver, const PageEntryV3 & entry) { upsertPage(buildV3Id(TEST_NAMESPACE_ID, page_id), ver, entry); } @@ -271,19 +272,19 @@ class PageEntriesEdit { ref(buildV3Id(TEST_NAMESPACE_ID, ref_id), buildV3Id(TEST_NAMESPACE_ID, page_id)); } - void varRef(PageId ref_id, const PageVersionType & ver, PageId ori_page_id) + void varRef(PageId ref_id, const PageVersion & ver, PageId ori_page_id) { varRef(buildV3Id(TEST_NAMESPACE_ID, ref_id), ver, buildV3Id(TEST_NAMESPACE_ID, ori_page_id)); } - void varExternal(PageId page_id, const PageVersionType & create_ver, Int64 being_ref_count) + void varExternal(PageId page_id, const PageVersion & create_ver, Int64 being_ref_count) { varExternal(buildV3Id(TEST_NAMESPACE_ID, page_id), create_ver, being_ref_count); } - void varEntry(PageId page_id, const PageVersionType & ver, const PageEntryV3 & entry, Int64 being_ref_count) + void varEntry(PageId page_id, const PageVersion & ver, const PageEntryV3 & entry, Int64 being_ref_count) { varEntry(buildV3Id(TEST_NAMESPACE_ID, page_id), ver, entry, being_ref_count); } - void varDel(PageId page_id, const PageVersionType & delete_ver) + void varDel(PageId page_id, const PageVersion & delete_ver) { varDel(buildV3Id(TEST_NAMESPACE_ID, page_id), delete_ver); } @@ -315,7 +316,7 @@ class PageEntriesEdit /// See https://fmt.dev/latest/api.html#formatting-user-defined-types template <> -struct fmt::formatter +struct fmt::formatter { static constexpr auto parse(format_parse_context & ctx) { @@ -330,7 +331,7 @@ struct fmt::formatter } template - auto format(const DB::PS::V3::PageVersionType & ver, FormatContext & ctx) + auto format(const DB::PS::V3::PageVersion & ver, FormatContext & ctx) { return format_to(ctx.out(), "<{},{}>", ver.sequence, ver.epoch); } diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp index de0a26f68c7..6966f794de9 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp @@ -43,7 +43,7 @@ PageStorageImpl::PageStorageImpl( PageStorageImpl::~PageStorageImpl() = default; -std::map PageStorageImpl::restore() +void PageStorageImpl::restore() { // TODO: clean up blobstore. // TODO: Speedup restoring @@ -53,7 +53,11 @@ std::map PageStorageImpl::restore() page_directory = factory .setBlobStore(blob_store) .create(storage_name, file_provider, delegator, parseWALConfig(config)); - return factory.getMaxApplyPageIds(); +} + +PageId PageStorageImpl::getMaxId(NamespaceId ns_id) +{ + return page_directory->getMaxId(ns_id); } void PageStorageImpl::drop() diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.h b/dbms/src/Storages/Page/V3/PageStorageImpl.h index 62c45ac685d..f3b696d0351 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.h +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.h @@ -60,10 +60,11 @@ class PageStorageImpl : public DB::PageStorage return wal_config; } - std::map restore() override; + void restore() override; void drop() override; + PageId getMaxId(NamespaceId ns_id) override; PageId getNormalPageIdImpl(NamespaceId ns_id, PageId page_id, SnapshotPtr snapshot, bool throw_on_not_exist) override; diff --git a/dbms/src/Storages/Page/V3/WAL/serialize.cpp b/dbms/src/Storages/Page/V3/WAL/serialize.cpp index 26854f9a640..f8e26617499 100644 --- a/dbms/src/Storages/Page/V3/WAL/serialize.cpp +++ b/dbms/src/Storages/Page/V3/WAL/serialize.cpp @@ -23,13 +23,13 @@ namespace DB::PS::V3::ser { -inline void serializeVersionTo(const PageVersionType & version, WriteBuffer & buf) +inline void serializeVersionTo(const PageVersion & version, WriteBuffer & buf) { writeIntBinary(version.sequence, buf); writeIntBinary(version.epoch, buf); } -inline void deserializeVersionFrom(ReadBuffer & buf, PageVersionType & version) +inline void deserializeVersionFrom(ReadBuffer & buf, PageVersion & version) { readIntBinary(version.sequence, buf); readIntBinary(version.epoch, buf); @@ -174,7 +174,7 @@ void deserializeDelFrom([[maybe_unused]] const EditRecordType record_type, ReadB PageIdV3Internal page_id; readIntBinary(page_id, buf); - PageVersionType version; + PageVersion version; deserializeVersionFrom(buf, version); PageEntriesEdit::EditRecord rec; diff --git a/dbms/src/Storages/Page/V3/WALStore.cpp b/dbms/src/Storages/Page/V3/WALStore.cpp index 6759e80f416..1f1eaf3bc33 100644 --- a/dbms/src/Storages/Page/V3/WALStore.cpp +++ b/dbms/src/Storages/Page/V3/WALStore.cpp @@ -69,7 +69,7 @@ WALStore::WALStore( { } -void WALStore::apply(PageEntriesEdit & edit, const PageVersionType & version, const WriteLimiterPtr & write_limiter) +void WALStore::apply(PageEntriesEdit & edit, const PageVersion & version, const WriteLimiterPtr & write_limiter) { for (auto & r : edit.getMutRecords()) { diff --git a/dbms/src/Storages/Page/V3/WALStore.h b/dbms/src/Storages/Page/V3/WALStore.h index 8984c10e5e8..039903a8608 100644 --- a/dbms/src/Storages/Page/V3/WALStore.h +++ b/dbms/src/Storages/Page/V3/WALStore.h @@ -99,7 +99,7 @@ class WALStore PSDiskDelegatorPtr & delegator, WALStore::Config config); - void apply(PageEntriesEdit & edit, const PageVersionType & version, const WriteLimiterPtr & write_limiter = nullptr); + void apply(PageEntriesEdit & edit, const PageVersion & version, const WriteLimiterPtr & write_limiter = nullptr); void apply(const PageEntriesEdit & edit, const WriteLimiterPtr & write_limiter = nullptr); struct FilesSnapshot diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp index 9789fd0fe83..4511cc8ddd7 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp @@ -625,16 +625,16 @@ CATCH #define INSERT_BLOBID_ENTRY(BLOBID, VERSION) \ PageEntryV3 entry_v##VERSION{.file_id = (BLOBID), .size = (VERSION), .tag = 0, .offset = 0x123, .checksum = 0x4567}; \ - entries.createNewEntry(PageVersionType(VERSION), entry_v##VERSION); + entries.createNewEntry(PageVersion(VERSION), entry_v##VERSION); #define INSERT_ENTRY(VERSION) INSERT_BLOBID_ENTRY(1, VERSION) #define INSERT_GC_ENTRY(VERSION, EPOCH) \ PageEntryV3 entry_gc_v##VERSION##_##EPOCH{.file_id = 2, .size = 100 * (VERSION) + (EPOCH), .tag = 0, .offset = 0x234, .checksum = 0x5678}; \ - entries.createNewEntry(PageVersionType((VERSION), (EPOCH)), entry_gc_v##VERSION##_##EPOCH); + entries.createNewEntry(PageVersion((VERSION), (EPOCH)), entry_gc_v##VERSION##_##EPOCH); class VersionedEntriesTest : public ::testing::Test { public: - using DerefCounter = std::map>; + using DerefCounter = std::map>; std::tuple runClean(UInt64 seq) { DerefCounter deref_counter; @@ -643,7 +643,7 @@ class VersionedEntriesTest : public ::testing::Test return {all_removed, removed_entries, deref_counter}; } - std::tuple runDeref(UInt64 seq, PageVersionType ver, Int64 decrease_num) + std::tuple runDeref(UInt64 seq, PageVersion ver, Int64 decrease_num) { PageEntriesV3 removed_entries; bool all_removed = entries.derefAndClean(seq, buildV3Id(TEST_NAMESPACE_ID, page_id), ver, decrease_num, removed_entries); @@ -694,7 +694,7 @@ TEST_F(VersionedEntriesTest, InsertGet) // Insert delete. Can not get entry with seq >= delete_version. // But it won't affect reading with old seq. - entries.createDelete(PageVersionType(15)); + entries.createDelete(PageVersion(15)); ASSERT_FALSE(entries.getEntry(1).has_value()); ASSERT_SAME_ENTRY(*entries.getEntry(2), entry_gc_v2_1); ASSERT_SAME_ENTRY(*entries.getEntry(3), entry_gc_v2_1); @@ -722,6 +722,126 @@ TEST_F(VersionedEntriesTest, InsertWithLowerVersion) ASSERT_SAME_ENTRY(*entries.getEntry(2), entry_v2); } +TEST_F(VersionedEntriesTest, EntryIsVisible) +try +{ + // init state + ASSERT_FALSE(entries.isVisible(0)); + ASSERT_FALSE(entries.isVisible(1)); + ASSERT_FALSE(entries.isVisible(2)); + ASSERT_FALSE(entries.isVisible(10000)); + + // insert some entries + INSERT_ENTRY(2); + INSERT_ENTRY(3); + INSERT_ENTRY(5); + + ASSERT_FALSE(entries.isVisible(1)); + ASSERT_TRUE(entries.isVisible(2)); + ASSERT_TRUE(entries.isVisible(3)); + ASSERT_TRUE(entries.isVisible(4)); + ASSERT_TRUE(entries.isVisible(5)); + ASSERT_TRUE(entries.isVisible(6)); + + // insert delete + entries.createDelete(PageVersion(6)); + + ASSERT_FALSE(entries.isVisible(1)); + ASSERT_TRUE(entries.isVisible(2)); + ASSERT_TRUE(entries.isVisible(3)); + ASSERT_TRUE(entries.isVisible(4)); + ASSERT_TRUE(entries.isVisible(5)); + ASSERT_FALSE(entries.isVisible(6)); + ASSERT_FALSE(entries.isVisible(10000)); + + // insert entry after delete + INSERT_ENTRY(7); + + ASSERT_FALSE(entries.isVisible(1)); + ASSERT_TRUE(entries.isVisible(2)); + ASSERT_TRUE(entries.isVisible(3)); + ASSERT_TRUE(entries.isVisible(4)); + ASSERT_TRUE(entries.isVisible(5)); + ASSERT_FALSE(entries.isVisible(6)); + ASSERT_TRUE(entries.isVisible(7)); + ASSERT_TRUE(entries.isVisible(10000)); +} +CATCH + +TEST_F(VersionedEntriesTest, ExternalPageIsVisible) +try +{ + // init state + ASSERT_FALSE(entries.isVisible(0)); + ASSERT_FALSE(entries.isVisible(1)); + ASSERT_FALSE(entries.isVisible(2)); + ASSERT_FALSE(entries.isVisible(10000)); + + // insert some entries + entries.createNewExternal(PageVersion(2)); + + ASSERT_FALSE(entries.isVisible(1)); + ASSERT_TRUE(entries.isVisible(2)); + ASSERT_TRUE(entries.isVisible(10000)); + + // insert delete + entries.createDelete(PageVersion(6)); + + ASSERT_FALSE(entries.isVisible(1)); + ASSERT_TRUE(entries.isVisible(2)); + ASSERT_TRUE(entries.isVisible(3)); + ASSERT_TRUE(entries.isVisible(4)); + ASSERT_TRUE(entries.isVisible(5)); + ASSERT_FALSE(entries.isVisible(6)); + ASSERT_FALSE(entries.isVisible(10000)); + + // insert entry after delete + entries.createNewExternal(PageVersion(7)); + + // after re-create external page, the visible for 1~5 has changed + ASSERT_FALSE(entries.isVisible(6)); + ASSERT_TRUE(entries.isVisible(7)); + ASSERT_TRUE(entries.isVisible(10000)); +} +CATCH + +TEST_F(VersionedEntriesTest, RefPageIsVisible) +try +{ + // init state + ASSERT_FALSE(entries.isVisible(0)); + ASSERT_FALSE(entries.isVisible(1)); + ASSERT_FALSE(entries.isVisible(2)); + ASSERT_FALSE(entries.isVisible(10000)); + + // insert some entries + entries.createNewRef(PageVersion(2), buildV3Id(TEST_NAMESPACE_ID, 2)); + + ASSERT_FALSE(entries.isVisible(1)); + ASSERT_TRUE(entries.isVisible(2)); + ASSERT_TRUE(entries.isVisible(10000)); + + // insert delete + entries.createDelete(PageVersion(6)); + + ASSERT_FALSE(entries.isVisible(1)); + ASSERT_TRUE(entries.isVisible(2)); + ASSERT_TRUE(entries.isVisible(3)); + ASSERT_TRUE(entries.isVisible(4)); + ASSERT_TRUE(entries.isVisible(5)); + ASSERT_FALSE(entries.isVisible(6)); + ASSERT_FALSE(entries.isVisible(10000)); + + // insert entry after delete + entries.createNewRef(PageVersion(7), buildV3Id(TEST_NAMESPACE_ID, 2)); + + // after re-create ref page, the visible for 1~5 has changed + ASSERT_FALSE(entries.isVisible(6)); + ASSERT_TRUE(entries.isVisible(7)); + ASSERT_TRUE(entries.isVisible(10000)); +} +CATCH + TEST_F(VersionedEntriesTest, GC) try { @@ -732,7 +852,7 @@ try INSERT_GC_ENTRY(5, 2); INSERT_ENTRY(10); INSERT_ENTRY(11); - entries.createDelete(PageVersionType(15)); + entries.createDelete(PageVersion(15)); // noting to be removed auto [all_removed, removed_entries, deref_counter] = runClean(1); @@ -785,15 +905,15 @@ CATCH TEST_F(VersionedEntriesTest, DeleteMultiTime) try { - entries.createDelete(PageVersionType(1)); + entries.createDelete(PageVersion(1)); INSERT_ENTRY(2); INSERT_GC_ENTRY(2, 1); - entries.createDelete(PageVersionType(15)); - entries.createDelete(PageVersionType(17)); - entries.createDelete(PageVersionType(16)); + entries.createDelete(PageVersion(15)); + entries.createDelete(PageVersion(17)); + entries.createDelete(PageVersion(16)); bool all_removed; - std::map> deref_counter; + std::map> deref_counter; PageEntriesV3 removed_entries; // <2,0> get removed. @@ -818,13 +938,13 @@ TEST_F(VersionedEntriesTest, DontCleanWhenBeingRef) try { bool all_removed; - std::map> deref_counter; + std::map> deref_counter; PageEntriesV3 removed_entries; INSERT_ENTRY(2); - entries.incrRefCount(PageVersionType(2)); - entries.incrRefCount(PageVersionType(2)); - entries.createDelete(PageVersionType(5)); + entries.incrRefCount(PageVersion(2)); + entries.incrRefCount(PageVersion(2)); + entries.createDelete(PageVersion(5)); // <2, 0> is not available after seq=5, but not get removed ASSERT_SAME_ENTRY(entry_v2, *entries.getEntry(4)); @@ -838,13 +958,13 @@ try ASSERT_EQ(deref_counter.size(), 0); // decrease 1 ref counting - std::tie(all_removed, removed_entries) = runDeref(5, PageVersionType(2), 1); + std::tie(all_removed, removed_entries) = runDeref(5, PageVersion(2), 1); ASSERT_EQ(removed_entries.size(), 0); ASSERT_FALSE(all_removed); // should not remove this chain ASSERT_FALSE(entries.getEntry(5)); // clear all - std::tie(all_removed, removed_entries) = runDeref(5, PageVersionType(2), 1); + std::tie(all_removed, removed_entries) = runDeref(5, PageVersion(2), 1); ASSERT_EQ(removed_entries.size(), 1); ASSERT_SAME_ENTRY(removed_entries[0], entry_v2); ASSERT_TRUE(all_removed); // should remove this chain @@ -856,13 +976,13 @@ TEST_F(VersionedEntriesTest, DontCleanWhenBeingRef2) try { bool all_removed; - std::map> deref_counter; + std::map> deref_counter; PageEntriesV3 removed_entries; INSERT_ENTRY(2); - entries.incrRefCount(PageVersionType(2)); - entries.incrRefCount(PageVersionType(2)); - entries.createDelete(PageVersionType(5)); + entries.incrRefCount(PageVersion(2)); + entries.incrRefCount(PageVersion(2)); + entries.createDelete(PageVersion(5)); // <2, 0> is not available after seq=5, but not get removed ASSERT_SAME_ENTRY(entry_v2, *entries.getEntry(4)); @@ -876,7 +996,7 @@ try ASSERT_EQ(deref_counter.size(), 0); // clear all - std::tie(all_removed, removed_entries) = runDeref(5, PageVersionType(2), 2); + std::tie(all_removed, removed_entries) = runDeref(5, PageVersion(2), 2); ASSERT_EQ(removed_entries.size(), 1); ASSERT_SAME_ENTRY(removed_entries[0], entry_v2); ASSERT_TRUE(all_removed); // should remove this chain @@ -888,12 +1008,12 @@ TEST_F(VersionedEntriesTest, CleanDuplicatedWhenBeingRefAndAppliedUpsert) try { bool all_removed; - std::map> deref_counter; + std::map> deref_counter; PageEntriesV3 removed_entries; INSERT_ENTRY(2); - entries.incrRefCount(PageVersionType(2)); - entries.incrRefCount(PageVersionType(2)); + entries.incrRefCount(PageVersion(2)); + entries.incrRefCount(PageVersion(2)); INSERT_GC_ENTRY(2, 1); INSERT_GC_ENTRY(2, 2); @@ -910,7 +1030,7 @@ try ASSERT_EQ(deref_counter.size(), 0); // clear all - std::tie(all_removed, removed_entries) = runDeref(5, PageVersionType(2), 2); + std::tie(all_removed, removed_entries) = runDeref(5, PageVersion(2), 2); ASSERT_EQ(removed_entries.size(), 0); ASSERT_FALSE(all_removed); // should not remove this chain ASSERT_SAME_ENTRY(entry_gc_v2_2, *entries.getEntry(4)); @@ -921,15 +1041,15 @@ TEST_F(VersionedEntriesTest, CleanDuplicatedWhenBeingRefAndAppliedUpsert2) try { bool all_removed; - std::map> deref_counter; + std::map> deref_counter; PageEntriesV3 removed_entries; INSERT_ENTRY(2); - entries.incrRefCount(PageVersionType(2)); - entries.incrRefCount(PageVersionType(2)); + entries.incrRefCount(PageVersion(2)); + entries.incrRefCount(PageVersion(2)); INSERT_GC_ENTRY(2, 1); INSERT_GC_ENTRY(2, 2); - entries.createDelete(PageVersionType(5)); + entries.createDelete(PageVersion(5)); // <2, 2> is not available after seq=5, but not get removed ASSERT_SAME_ENTRY(entry_gc_v2_2, *entries.getEntry(4)); @@ -945,7 +1065,7 @@ try ASSERT_EQ(deref_counter.size(), 0); // clear all - std::tie(all_removed, removed_entries) = runDeref(5, PageVersionType(2), 2); + std::tie(all_removed, removed_entries) = runDeref(5, PageVersion(2), 2); ASSERT_EQ(removed_entries.size(), 1); ASSERT_SAME_ENTRY(removed_entries[0], entry_gc_v2_2); ASSERT_TRUE(all_removed); // should remove this chain @@ -957,7 +1077,7 @@ TEST_F(VersionedEntriesTest, ReadAfterGcApplied) try { bool all_removed; - std::map> deref_counter; + std::map> deref_counter; PageEntriesV3 removed_entries; INSERT_ENTRY(2); @@ -2094,6 +2214,191 @@ try } CATCH +TEST_F(PageDirectoryGCTest, RestoreWithDuplicateID) +try +{ + auto restore_from_edit = [](const PageEntriesEdit & edit) { + auto ctx = ::DB::tests::TiFlashTestEnv::getContext(); + auto provider = ctx.getFileProvider(); + auto path = getTemporaryPath(); + PSDiskDelegatorPtr delegator = std::make_shared(path); + PageDirectoryFactory factory; + auto d = factory.createFromEdit(getCurrentTestName(), provider, delegator, edit); + return d; + }; + + const PageId target_id = 100; + // ========= 1 =======// + // Reuse same id: PUT_EXT/DEL/REF + { + PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntriesEdit edit; + edit.put(50, entry_50); + edit.putExternal(target_id); + edit.del(target_id); + // restart and reuse id=100 as ref to replace put_ext + edit.ref(target_id, 50); + + auto restored_dir = restore_from_edit(edit); + auto snap = restored_dir->createSnapshot(); + ASSERT_EQ(restored_dir->getNormalPageId(target_id, snap).low, 50); + } + // Reuse same id: PUT_EXT/DEL/PUT + { + PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_100{.file_id = 100, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntriesEdit edit; + edit.put(50, entry_50); + edit.putExternal(target_id); + edit.del(target_id); + // restart and reuse id=100 as put to replace put_ext + edit.put(target_id, entry_100); + + auto restored_dir = restore_from_edit(edit); + auto snap = restored_dir->createSnapshot(); + ASSERT_SAME_ENTRY(restored_dir->get(target_id, snap).second, entry_100); + } + + // ========= 1-invalid =======// + // Reuse same id: PUT_EXT/BEING REF/DEL/REF + { + PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntriesEdit edit; + edit.put(50, entry_50); + edit.putExternal(target_id); + edit.ref(101, target_id); + edit.del(target_id); + // restart and reuse id=100 as ref. Should not happen because 101 still ref to 100 + edit.ref(target_id, 50); + + ASSERT_THROW(restore_from_edit(edit);, DB::Exception); + } + // Reuse same id: PUT_EXT/BEING REF/DEL/PUT + { + PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_100{.file_id = 100, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntriesEdit edit; + edit.put(50, entry_50); + edit.putExternal(target_id); + edit.ref(101, target_id); + edit.del(target_id); + // restart and reuse id=100 as put. Should not happen because 101 still ref to 100 + edit.put(target_id, entry_100); + + ASSERT_THROW(restore_from_edit(edit);, DB::Exception); + } + + // ========= 2 =======// + // Reuse same id: PUT/DEL/REF + { + PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntriesEdit edit; + edit.put(50, entry_50); + edit.put(target_id, entry_50); + edit.del(target_id); + // restart and reuse id=100 as ref to replace put + edit.ref(target_id, 50); + + auto restored_dir = restore_from_edit(edit); + auto snap = restored_dir->createSnapshot(); + ASSERT_EQ(restored_dir->getNormalPageId(target_id, snap).low, 50); + } + // Reuse same id: PUT/DEL/PUT_EXT + { + PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntriesEdit edit; + edit.put(50, entry_50); + edit.put(target_id, entry_50); + edit.del(target_id); + // restart and reuse id=100 as external to replace put + edit.putExternal(target_id); + + auto restored_dir = restore_from_edit(edit); + auto snap = restored_dir->createSnapshot(); + auto ext_ids = restored_dir->getAliveExternalIds(TEST_NAMESPACE_ID); + ASSERT_EQ(ext_ids.size(), 1); + ASSERT_EQ(*ext_ids.begin(), target_id); + } + + // ========= 2-invalid =======// + // Reuse same id: PUT/BEING REF/DEL/REF + { + PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntriesEdit edit; + edit.put(50, entry_50); + edit.put(target_id, entry_50); + edit.ref(101, target_id); + edit.del(target_id); + // restart and reuse id=100 as ref to replace put + edit.ref(target_id, 50); + + ASSERT_THROW(restore_from_edit(edit);, DB::Exception); + } + // Reuse same id: PUT/BEING REF/DEL/PUT_EXT + { + PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntriesEdit edit; + edit.put(50, entry_50); + edit.put(target_id, entry_50); + edit.ref(101, target_id); + edit.del(target_id); + // restart and reuse id=100 as external to replace put + edit.putExternal(target_id); + + ASSERT_THROW(restore_from_edit(edit);, DB::Exception); + } + + // ========= 3 =======// + // Reuse same id: REF/DEL/PUT + { + PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_100{.file_id = 100, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntriesEdit edit; + edit.put(50, entry_50); + edit.ref(target_id, 50); + edit.del(target_id); + // restart and reuse id=100 as put to replace ref + edit.put(target_id, entry_100); + + auto restored_dir = restore_from_edit(edit); + auto snap = restored_dir->createSnapshot(); + ASSERT_SAME_ENTRY(restored_dir->get(target_id, snap).second, entry_100); + } + // Reuse same id: REF/DEL/PUT_EXT + { + PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntriesEdit edit; + edit.put(50, entry_50); + edit.ref(target_id, 50); + edit.del(target_id); + // restart and reuse id=100 as external to replace ref + edit.putExternal(target_id); + + auto restored_dir = restore_from_edit(edit); + auto snap = restored_dir->createSnapshot(); + auto ext_ids = restored_dir->getAliveExternalIds(TEST_NAMESPACE_ID); + ASSERT_EQ(ext_ids.size(), 1); + ASSERT_EQ(*ext_ids.begin(), target_id); + } + // Reuse same id: REF/DEL/REF another id + { + PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_51{.file_id = 2, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntriesEdit edit; + edit.put(50, entry_50); + edit.put(51, entry_51); + edit.ref(target_id, 50); + edit.del(target_id); + // restart and reuse id=target_id as external to replace put + edit.ref(target_id, 51); + + auto restored_dir = restore_from_edit(edit); + auto snap = restored_dir->createSnapshot(); + ASSERT_EQ(restored_dir->getNormalPageId(target_id, snap).low, 51); + } +} +CATCH + TEST_F(PageDirectoryTest, GetMaxId) try { @@ -2127,6 +2432,81 @@ try ASSERT_EQ(dir->getMaxId(medium), 320); ASSERT_EQ(dir->getMaxId(large), 2); } + + { + PageEntriesEdit edit; + edit.del(buildV3Id(medium, 320)); + dir->apply(std::move(edit)); + ASSERT_EQ(dir->getMaxId(medium), 300); + } + + { + PageEntriesEdit edit; + edit.del(buildV3Id(medium, 300)); + dir->apply(std::move(edit)); + ASSERT_EQ(dir->getMaxId(medium), 0); + } +} +CATCH + +TEST_F(PageDirectoryTest, GetMaxIdAfterDelete) +try +{ + /// test for deleting put + PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry2{.file_id = 2, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + { + PageEntriesEdit edit; + edit.put(1, entry1); + edit.put(2, entry2); + dir->apply(std::move(edit)); + } + + ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 2); + + { + PageEntriesEdit edit; + edit.del(2); + dir->apply(std::move(edit)); + } + ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 1); + + { + PageEntriesEdit edit; + edit.del(1); + dir->apply(std::move(edit)); + } + ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 0); + + dir->gcInMemEntries(); + ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 0); + + /// test for deleting put_ext/ref + + { + PageEntriesEdit edit; + edit.putExternal(1); + edit.ref(2, 1); + dir->apply(std::move(edit)); + } + + { + PageEntriesEdit edit; + edit.del(1); + dir->apply(std::move(edit)); + } + ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 2); + dir->gcInMemEntries(); + ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 2); + + { + PageEntriesEdit edit; + edit.del(2); + dir->apply(std::move(edit)); + } + ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 0); + dir->gcInMemEntries(); + ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 0); } CATCH diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp index 4bd3b2832b0..b0c2625466d 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp @@ -67,15 +67,6 @@ class PageStorageTest : public DB::base::TiFlashStorageTestBasic } - std::pair, std::map> reopen() - { - auto path = getTemporaryPath(); - auto delegator = std::make_shared(path); - auto storage = std::make_shared("test.t", delegator, config, file_provider); - auto max_ids = storage->restore(); - return {storage, max_ids}; - } - protected: FileProviderPtr file_provider; std::unique_ptr path_pool; @@ -1310,41 +1301,5 @@ try } CATCH -TEST_F(PageStorageTest, getMaxIdsFromRestore) -try -{ - { - WriteBatch batch; - batch.putExternal(1, 0); - batch.putExternal(2, 0); - batch.delPage(1); - batch.delPage(2); - page_storage->write(std::move(batch)); - - WriteBatch batch2{TEST_NAMESPACE_ID + 1}; - batch2.putExternal(1, 0); - batch2.putExternal(2, 0); - batch2.putRefPage(3, 1); - batch2.putRefPage(100, 2); - page_storage->write(std::move(batch2)); - - WriteBatch batch3{TEST_NAMESPACE_ID + 2}; - batch3.putExternal(1, 0); - batch3.putExternal(2, 0); - batch3.putRefPage(3, 1); - batch3.putRefPage(10, 2); - batch3.delPage(10); - page_storage->write(std::move(batch3)); - } - - page_storage = nullptr; - auto [page_storage, max_ids] = reopen(); - ASSERT_EQ(max_ids.size(), 3); - ASSERT_EQ(max_ids[TEST_NAMESPACE_ID], 2); // external page 2 is marked as deleted, but we can still restore it. - ASSERT_EQ(max_ids[TEST_NAMESPACE_ID + 1], 100); - ASSERT_EQ(max_ids[TEST_NAMESPACE_ID + 2], 10); // page 10 is marked as deleted, but we can still restore it. -} -CATCH - } // namespace PS::V3::tests } // namespace DB diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp index 2a5714de027..d3fdafe57e8 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp @@ -43,16 +43,17 @@ class PageStorageMixedTest : public DB::base::TiFlashStorageTestBasic storage_path_pool_v3 = std::make_unique(Strings{path}, Strings{path}, Strings{}, std::make_shared(0, paths, caps, Strings{}, caps), global_context.getFileProvider(), true); global_context.setPageStorageRunMode(PageStorageRunMode::MIX_MODE); + if (!global_context.getGlobalStoragePool()) + global_context.initializeGlobalStoragePoolIfNeed(*storage_path_pool_v3); } void SetUp() override { - auto & global_context = DB::tests::TiFlashTestEnv::getGlobalContext(); - global_context.setPageStorageRunMode(PageStorageRunMode::MIX_MODE); TiFlashStorageTestBasic::SetUp(); const auto & path = getTemporaryPath(); createIfNotExist(path); + auto & global_context = DB::tests::TiFlashTestEnv::getGlobalContext(); std::vector caps = {}; Strings paths = {path}; @@ -74,7 +75,7 @@ class PageStorageMixedTest : public DB::base::TiFlashStorageTestBasic PageStorageRunMode reloadMixedStoragePool() { - db_context->setPageStorageRunMode(PageStorageRunMode::MIX_MODE); + DB::tests::TiFlashTestEnv::getContext().setPageStorageRunMode(PageStorageRunMode::MIX_MODE); PageStorageRunMode run_mode = storage_pool_mix->restore(); page_writer_mix = storage_pool_mix->logWriter(); page_reader_mix = storage_pool_mix->logReader(); @@ -83,7 +84,7 @@ class PageStorageMixedTest : public DB::base::TiFlashStorageTestBasic void reloadV2StoragePool() { - db_context->setPageStorageRunMode(PageStorageRunMode::ONLY_V2); + DB::tests::TiFlashTestEnv::getContext().setPageStorageRunMode(PageStorageRunMode::ONLY_V2); storage_pool_v2->restore(); page_writer_v2 = storage_pool_v2->logWriter(); page_reader_v2 = storage_pool_v2->logReader(); diff --git a/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp b/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp index fadc0fb3bae..89c4e54f7e7 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp @@ -36,7 +36,7 @@ TEST(WALSeriTest, AllPuts) { PageEntryV3 entry_p1{.file_id = 1, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageEntryV3 entry_p2{.file_id = 1, .size = 2, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageVersionType ver20(/*seq=*/20); + PageVersion ver20(/*seq=*/20); PageEntriesEdit edit; edit.put(1, entry_p1); edit.put(2, entry_p2); @@ -58,7 +58,7 @@ try { PageEntryV3 entry_p3{.file_id = 1, .size = 3, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageEntryV3 entry_p5{.file_id = 1, .size = 5, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageVersionType ver21(/*seq=*/21); + PageVersion ver21(/*seq=*/21); PageEntriesEdit edit; edit.put(3, entry_p3); edit.ref(4, 3); @@ -107,8 +107,8 @@ TEST(WALSeriTest, Upserts) PageEntryV3 entry_p1_2{.file_id = 2, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageEntryV3 entry_p3_2{.file_id = 2, .size = 3, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageEntryV3 entry_p5_2{.file_id = 2, .size = 5, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageVersionType ver20_1(/*seq=*/20, /*epoch*/ 1); - PageVersionType ver21_1(/*seq=*/21, /*epoch*/ 1); + PageVersion ver20_1(/*seq=*/20, /*epoch*/ 1); + PageVersion ver21_1(/*seq=*/21, /*epoch*/ 1); PageEntriesEdit edit; edit.upsertPage(1, ver20_1, entry_p1_2); edit.upsertPage(3, ver21_1, entry_p3_2); @@ -135,9 +135,9 @@ TEST(WALSeriTest, Upserts) TEST(WALSeriTest, RefExternalAndEntry) { - PageVersionType ver1_0(/*seq=*/1, /*epoch*/ 0); - PageVersionType ver2_0(/*seq=*/2, /*epoch*/ 0); - PageVersionType ver3_0(/*seq=*/3, /*epoch*/ 0); + PageVersion ver1_0(/*seq=*/1, /*epoch*/ 0); + PageVersion ver2_0(/*seq=*/2, /*epoch*/ 0); + PageVersion ver3_0(/*seq=*/3, /*epoch*/ 0); { PageEntriesEdit edit; edit.varExternal(1, ver1_0, 2); @@ -407,7 +407,7 @@ try // Stage 2. Apply with only puts PageEntryV3 entry_p1{.file_id = 1, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageEntryV3 entry_p2{.file_id = 1, .size = 2, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageVersionType ver20(/*seq=*/20); + PageVersion ver20(/*seq=*/20); { PageEntriesEdit edit; edit.put(1, entry_p1); @@ -437,7 +437,7 @@ try // Stage 3. Apply with puts and refs PageEntryV3 entry_p3{.file_id = 1, .size = 3, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageEntryV3 entry_p5{.file_id = 1, .size = 5, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageVersionType ver21(/*seq=*/21); + PageVersion ver21(/*seq=*/21); { PageEntriesEdit edit; edit.put(3, entry_p3); @@ -471,8 +471,8 @@ try PageEntryV3 entry_p1_2{.file_id = 2, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageEntryV3 entry_p3_2{.file_id = 2, .size = 3, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageEntryV3 entry_p5_2{.file_id = 2, .size = 5, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageVersionType ver20_1(/*seq=*/20, /*epoch*/ 1); - PageVersionType ver21_1(/*seq=*/21, /*epoch*/ 1); + PageVersion ver20_1(/*seq=*/20, /*epoch*/ 1); + PageVersion ver21_1(/*seq=*/21, /*epoch*/ 1); { PageEntriesEdit edit; edit.upsertPage(1, ver20_1, entry_p1_2); @@ -516,7 +516,7 @@ try // Stage 1. Apply with only puts PageEntryV3 entry_p1{.file_id = 1, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageEntryV3 entry_p2{.file_id = 1, .size = 2, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageVersionType ver20(/*seq=*/20); + PageVersion ver20(/*seq=*/20); { PageEntriesEdit edit; edit.put(1, entry_p1); @@ -528,7 +528,7 @@ try // Stage 2. Apply with puts and refs PageEntryV3 entry_p3{.file_id = 1, .size = 3, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageEntryV3 entry_p5{.file_id = 1, .size = 5, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageVersionType ver21(/*seq=*/21); + PageVersion ver21(/*seq=*/21); { PageEntriesEdit edit; edit.put(3, entry_p3); @@ -543,8 +543,8 @@ try PageEntryV3 entry_p1_2{.file_id = 2, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageEntryV3 entry_p3_2{.file_id = 2, .size = 3, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageEntryV3 entry_p5_2{.file_id = 2, .size = 5, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageVersionType ver20_1(/*seq=*/20, /*epoch*/ 1); - PageVersionType ver21_1(/*seq=*/21, /*epoch*/ 1); + PageVersion ver20_1(/*seq=*/20, /*epoch*/ 1); + PageVersion ver21_1(/*seq=*/21, /*epoch*/ 1); { PageEntriesEdit edit; edit.upsertPage(1, ver20_1, entry_p1_2); @@ -611,7 +611,7 @@ try PageId page_id = 0; std::vector size_each_edit; size_each_edit.reserve(num_edits_test); - PageVersionType ver(/*seq*/ 32); + PageVersion ver(/*seq*/ 32); for (size_t i = 0; i < num_edits_test; ++i) { PageEntryV3 entry{.file_id = 2, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; @@ -665,7 +665,7 @@ try // just fill in some random entry for (size_t i = 0; i < 70; ++i) { - snap_edit.varEntry(d_10000(rd), PageVersionType(345, 22), entry, 1); + snap_edit.varEntry(d_10000(rd), PageVersion(345, 22), entry, 1); } std::tie(wal, reader) = WALStore::create(getCurrentTestName(), enc_provider, delegator, config); bool done = wal->saveSnapshot(std::move(file_snap), std::move(snap_edit)); diff --git a/dbms/src/Storages/Page/V3/tests/page_storage_ctl.cpp b/dbms/src/Storages/Page/V3/tests/page_storage_ctl.cpp index 4f3cefa0ad7..7ea8da6892a 100644 --- a/dbms/src/Storages/Page/V3/tests/page_storage_ctl.cpp +++ b/dbms/src/Storages/Page/V3/tests/page_storage_ctl.cpp @@ -399,7 +399,7 @@ class PageStorageControl size_t index = 0; std::cout << fmt::format("Begin to check all of datas CRC. enable_fo_check={}", static_cast(enable_fo_check)) << std::endl; - std::list> error_versioned_pages; + std::list> error_versioned_pages; for (const auto & [internal_id, versioned_entries] : mvcc_table_directory) { if (index == total_pages / 10 * cut_index) @@ -474,4 +474,4 @@ int main(int argc, char ** argv) const auto & options = ControlOptions::parse(argc, argv); PageStorageControl(options).run(); return 0; -} \ No newline at end of file +} diff --git a/dbms/src/TestUtils/TiFlashTestEnv.cpp b/dbms/src/TestUtils/TiFlashTestEnv.cpp index 264fd6009a3..34355d43775 100644 --- a/dbms/src/TestUtils/TiFlashTestEnv.cpp +++ b/dbms/src/TestUtils/TiFlashTestEnv.cpp @@ -94,7 +94,6 @@ Context TiFlashTestEnv::getContext(const DB::Settings & settings, Strings testda context.setPath(root_path); auto paths = getPathPool(testdata_path); context.setPathPool(paths.first, paths.second, Strings{}, true, context.getPathCapacity(), context.getFileProvider()); - global_context->initializeGlobalStoragePoolIfNeed(context.getPathPool()); context.getSettingsRef() = settings; return context; } diff --git a/tests/delta-merge-test/ddl/alter.test b/tests/delta-merge-test/ddl/alter.test index 3dc57b05843..4bf405ac9e1 100644 --- a/tests/delta-merge-test/ddl/alter.test +++ b/tests/delta-merge-test/ddl/alter.test @@ -73,19 +73,18 @@ ## rename table -# FIXME: No support rename after PR 4850 (PageStorage V3) -# >> drop table if exists dm_test_renamed -# >> rename table dm_test to dm_test_renamed -# >> select * from dm_test -# Received exception from server (version {#WORD}): -# Code: 60. DB::Exception: Received from {#WORD} DB::Exception: Table default.dm_test doesn't exist.. +>> drop table if exists dm_test_renamed +>> rename table dm_test to dm_test_renamed +>> select * from dm_test +Received exception from server (version {#WORD}): +Code: 60. DB::Exception: Received from {#WORD} DB::Exception: Table default.dm_test doesn't exist.. -# >> select * from dm_test_renamed -# ┌─a─┬────b─┬─────c─┬────d─┐ -# │ 1 │ 0 │ 0 │ \N │ -# │ 2 │ 1024 │ 65535 │ 4096 │ -# │ 3 │ 2048 │ 65536 │ \N │ -# └───┴──────┴───────┴──────┘ +>> select * from dm_test_renamed +┌─a─┬────b─┬─────c─┬────d─┐ +│ 1 │ 0 │ 0 │ \N │ +│ 2 │ 1024 │ 65535 │ 4096 │ +│ 3 │ 2048 │ 65536 │ \N │ +└───┴──────┴───────┴──────┘ ## Clean up diff --git a/tests/delta-merge-test/raft/schema/partition_table_restart.test b/tests/delta-merge-test/raft/schema/partition_table_restart.test index bd0facdc153..893bb617af4 100644 --- a/tests/delta-merge-test/raft/schema/partition_table_restart.test +++ b/tests/delta-merge-test/raft/schema/partition_table_restart.test @@ -31,12 +31,11 @@ => DBGInvoke __mock_tidb_partition(default, test, 9998) => DBGInvoke __mock_tidb_partition(default, test, 9997) => DBGInvoke __refresh_schemas() + => drop table default.test_9997 # schema syncer guarantees logical table creation at last, so there won't be cases that logical table exists whereas physical table not. => drop table default.test => DBGInvoke __reset_schemas() -# remove obsolete entry left by previous dropped table -=> DBGInvoke __gc_global_storage_pool() => DBGInvoke __add_column_to_tidb_table(default, test, 'col_3 Nullable(Int8)') => DBGInvoke __rename_tidb_table(default, test, test1) diff --git a/tests/delta-merge-test/raft/txn_mock/partition_table.test b/tests/delta-merge-test/raft/txn_mock/partition_table.test index 3967d34b48e..2f8e67a61a8 100644 --- a/tests/delta-merge-test/raft/txn_mock/partition_table.test +++ b/tests/delta-merge-test/raft/txn_mock/partition_table.test @@ -94,20 +94,19 @@ │ 0 │ └──────────────┘ -# FIXME: No support rename after PR 4850 (PageStorage V3) -# => DBGInvoke __rename_tidb_table(default, test, test1) -# => DBGInvoke __refresh_schemas() -# => select count(*) from default.test1_9997 -# ┌─count()─┐ -# │ 2 │ -# └─────────┘ - -# => DBGInvoke __drop_tidb_table(default, test1) -# => DBGInvoke __refresh_schemas() -# => DBGInvoke is_tombstone(default, test1_9999) -# ┌─is_tombstone(default, test_9999)─┐ -# │ true │ -# └──────────────────────────────────┘ +=> DBGInvoke __rename_tidb_table(default, test, test1) +=> DBGInvoke __refresh_schemas() +=> select count(*) from default.test1_9997 +┌─count()─┐ +│ 2 │ +└─────────┘ + +=> DBGInvoke __drop_tidb_table(default, test1) +=> DBGInvoke __refresh_schemas() +=> DBGInvoke is_tombstone(default, test1_9999) +┌─is_tombstone(default, test_9999)─┐ +│ true │ +└──────────────────────────────────┘ => drop table if exists default.test => drop table if exists default.test1 From 4e783e389cc2a7e93c37a8176cbc2c410c084ea4 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Wed, 18 May 2022 19:08:38 +0800 Subject: [PATCH 034/127] doc: add pagestorage v3 design doc (#4804) ref pingcap/tiflash#3594 --- ...hitecture-of-storage-engine-pagestorage.md | 184 ++++++++++++++++++ .../design/images/tiflash-dt-architecture.png | Bin 0 -> 16580 bytes .../images/tiflash-ps-v3-architecture.png | Bin 0 -> 80904 bytes docs/design/images/tiflash-ps-v3-freemap.png | Bin 0 -> 34714 bytes .../design/images/tiflash-ps-v3-wal-store.png | Bin 0 -> 30384 bytes 5 files changed, 184 insertions(+) create mode 100644 docs/design/2022-04-28-architecture-of-storage-engine-pagestorage.md create mode 100644 docs/design/images/tiflash-dt-architecture.png create mode 100644 docs/design/images/tiflash-ps-v3-architecture.png create mode 100644 docs/design/images/tiflash-ps-v3-freemap.png create mode 100644 docs/design/images/tiflash-ps-v3-wal-store.png diff --git a/docs/design/2022-04-28-architecture-of-storage-engine-pagestorage.md b/docs/design/2022-04-28-architecture-of-storage-engine-pagestorage.md new file mode 100644 index 00000000000..ed297ed9b29 --- /dev/null +++ b/docs/design/2022-04-28-architecture-of-storage-engine-pagestorage.md @@ -0,0 +1,184 @@ +# Architecture Of Storage Engine - PageStorage + +- Authors (order by last name): [JaySon-Huang](https://github.com/JaySon-Huang), [flowbehappy](https://github.com/flowbehappy), [Jiaqi Zhou](https://github.com/jiaqizho) +- Editorial reviewer: [shichun-0415](https://github.com/shichun-0415) + +## Introduction + +`PageStorage` is where Delta Tree (DT) engine actually stores data. The latest data (i.e. delta data), and the metadata in the engine are serialized into Pages. The main data (i.e. stable data), is written in DTFiles format and managed as `ExternalPage`s in PageStorage. + +The following figure describes the "Delta ValueSpace" and "Stable ValueSpace" of DT. The data in "Delta ValueSpace" is continuously updated. After `delta merge` occurs, the delta data in PageStorage will be read and compacted into the "Stable ValueSpace". + +![tiflash-dt-architecture](./images/tiflash-dt-architecture.png) + +As one of the important components of DT, PageStorage mainly provides a KV storage service that also supports MVCC. Unlike other KV services, the KV interface provided by PageStorage is limited. Specifically, Key is limited to uint64_t, and Value is limited to a buffer or an array of buffers (also called fields) or null. +## Capability + +PageStorage supports: + +- Disk-based store +- Write/Read operation atomicity +- Full MVCC function +- KV store function +- GC +## Background + +Currently, there are three versions of PageStorage (V1, V2, V3). This document does not cover the details of the V1/V2 versions. The V3 version is recommended because the V2 design and implementation might lead to high write amplification and CPU usage in some scenarios. + + +The following are problems of V2 in a production environment: + +- **There is a risk of data loss in the meta part**. As long as the `checksum` and `buffer size` fields in a single meta buffer are damaged at the same time, the subsequent buffers will be unavailable. This situation may occur upon a disk failure or the meta part is changed by an unexpected operation. +- **The snapshot of MVCC needs to be optimized**. First, the CPU usage occupied by the snapshot should be reduced. Second, the implementation of the MVCC structure is too complex to be understood and maintained. +- **The GC write amplification in the data part is high and the GC task is too heavy**. Because the data part is composed of append write, `compact gc` is frequently triggered. Besides, the meta part is bound to a data part by "PageFile", meaning that we have to apply `compact gc` to compact the small data in the meta part. `compact gc` means that PageStorage needs to read all the valid data from the disk, then rewrite the data into a new file. Each round of GC brings additional read and write overhead. + +Besides resolving the preceding problems of V2, the V3 version also optimized the `lock` and `CRC` implementation. + +## Design + +![tiflash-ps-v3-architecture](./images/tiflash-ps-v3-architecture.png) + +The V3 version of PageStorage is composed of three main components, `PageDirectory`, `WALStore`, and `BlobStore`. + +- BlobStore: provides space management by using address multiplexing to manage the data part. +- PageDirectory: provides the function of MVCC. It features smaller memory usage and faster speed than V2. +- WALStore (Write Ahead Log Store): uses the write-ahead log file format to manage the meta part. + +### BlobStore + +BlobStore mainly stores blob data. BlobStore consists of three parts: + +- **BlobFile**: The file that stores blob data. +- **BlobStat**: A space manager used to find/allocate/free space in BlobFile. It has one-to-one mapping with the BlobFile. +- **BlobStats**: Manage all BlobStat. It is used to schedule all write requests. + +Instead of storing data with append write, BlobStore uses a data structure called SpaceMap to manage the free space and perform random write in BlobFile. + +![tiflash-ps-v3-freemap](./images/tiflash-ps-v3-freemap.png) + +The design is inspired by [Space Maps in Ext4 filesystem](https://www.kernel.org/doc/ols/2010/ols2010-pages-121-132.pdf). Each node in RB-tree has a space that is represented by (offset, size). The difference is that bitmap uses a node to record used locations, but BlobStore uses a node to record free locations. Recording free locations is a better way to find a free space by the data size. + +`BlobStat` is represented by a SpaceMap to reuse the space after being reclaimed, thereby reducing write amplification. + +In order to avoid getting some simple statistics by traversing the full SpaceMap, BlobStat provides external statistical status, such as the valid rate of the current BlobFile, the maximum capacity, and so on. These statistics are useful for: + +1. BlobStats can determine whether to create a new Blobfile or reuse the old one. +2. BlobStats choose the lowest valid rate among all BlobFile to reuse the reclaimed space whenever possible, avoid creating new BlobFiles +3. GC routine can use these statistics to quickly determine whether the current BlobFile needs to perform `compact GC` to avoid severe space amplification +4. GC routine can perform truncate to free the spaces at the end of BlobFiles on disk. Reducing space amplification + +What's more, when a write request happens, BlobStore will ask BlobStats for unused space from BlobStats. Since all disk spaces are scheduled by BlobStats and happen in memory, once the location is allocated from BlobStats, all actual write operations to the disk can be parallelized. So BlobStore allows maximum IO parallelism to utilise disk bandwidth. + + +### PageDirectory + +PageDirectory supports the function of MVCC, providing a read-only snapshot that does not block writes. It is mainly composed of an RB-tree map with the key is `page id` and the value is the `version chain`. + +The `version chain` is another RB-tree map with key `PageVersion` and value `PageEntry`. `PageEntry` represents the location of the data in `BlobStore` and is sorted by `PageVersion`(``) in the `version chain`. `PageVersion` is used for filtering by a read-only snapshot. PageDirectory will increase the `sequence` in serial when applying WriteBatches. The page entries created in the same WriteBatch use the same `sequence`, and the epoch is initialized to 0. After we applied "full GC" that moves the data into another location, we will create page entries with the same sequence but the epoch is last epoch + 1. + +Creating a snapshot for PageStorage is simply atomically getting the `sequence id` from PageDirectory. And the `sequence id` in the snapshot will be used to filter the result and provide an immutable result from PageDirectory. PageDirectory always return the latest entry version that is less than `sequence id + 1`. + +Here is a example: + +``` +page id 1 : {[(seq 1, epoch 0), entry 1], [(seq 2, epoch 0), entry 2], [(seq 3, epoch 1), entry 3]} +page id 2 : {[(seq 1, epoch 0), entry 4], [(seq 2, epoch 0), entry 5], [(seq 5, epoch 0), entry 6]} +page id 100 : {[(seq 1, epoch 0), entry 7], [(seq 10, epoch 2), entry 8]} +``` + +In this example, PageDirectory have 3 page with different id. Page 1 has 3 different versions corresponding to 3 entries. page 100 has 2 different versions corresponding to 2 entries. If caller get a entry for page id 2 with a snapshot that sequence is 2, PageDirectory will return the entry 5. + + +### WALStore + +WALStore provides two main interfaces: + +- **apply**: After `apply`, the WriteBatch info will be atomicity serialized on disks. This happens after writing data to BlobStore. +- **read**: Read serialized meta info from disks. This will happen when `PageStorage` is being restored. + +In order to control the time of restoring WriteBatches and reconstruct the PageDirectory while startups, PageDirectory will dump its snapshot into WALStore and clear the old log files by the snapshot. + +#### File format +![tiflash-ps-v3-walstore](./images/tiflash-ps-v3-wal-store.png) + +WALStore builds upon its serialized write batches (the meta part) base on log file format. The log file format is similar to the log file format as rocksdb [Write Ahead Log File Format](https://github.com/facebook/rocksdb/wiki/Write-Ahead-Log-File-Format#log-file-format). + +This log file format is convenient to detect disk failure. If a disk failure happens, we can stop TiFlash from startup and returning wrong results. In the worst case when CRC and the length of a meta record are broken at the same time, we can try to discard the broken data by the fixed-length block, other blocks can be preserved and try to recover some data. + +WALStore serializes the WriteBatch into an atomic record and writes it to the log file upon on "log file format". As the log file reader ensure we can get a complete record from the log file format, we don't need to serialize the byte length of the WriteBatch into record. The serialize structure of WriteBatch: + +buffer(WriteBatch) +Bits | Name | Description. | +--------------------|-------------------|-----------------------| +0:32 | WriteBatch version| Write batch version | +32:N | Operations | A series of operations| + +buffer(operate:put) + +Bits | Name | Description. | +--------------------|------------------|-----------------------| +0:8 | Write Type | Write batch operation type | +8:16 | Flag | A preserved flag | +16:144 | Page Id | The combine of namespace id and page id | +144:272 | Version | The combine of sequence and epoch | +272:336 | Ref count | The page being ref count | +272:N | Page entry | The page entry | + +Page entry + +Bits | Name | Description. | +--------------------|------------------|-----------------------| +0:32 | Blob File id | The Blob File Id | +32:96 | Offset | The Page Entry offset | +96:160 | Size | The Page Entry size | +160:224 | Checksum | The Page Entry checksum | +224:288 | Tag | The Page tag | +288:352 | Field offsets length | The length of field offset | +352:N | Field offsets | The length field offsets | + +Field offsets + +Bits | Name | Description. | +--------------------|------------------|-----------------------| +0:64 | Field Offset | The field offset | +64:128 | Field Checksum | The field checksum | + +buffer(operate:ref) + +Bits | Name | Description. | +--------------------|------------------|-----------------------| +0:8 | Write Type | Write batch operation type | +8:132 | Page Id | The combine of namespace id and page id | +132:260 | Origin Page Id | The combine of namespace id and page id | +260:388 | Version | The combine of sequence and epoch | + +buffer(operate:put_ext) + +Bits | Name | Description. | +--------------------|------------------|-----------------------| +0:8 | Write Type | Write batch operation type | +8:132 | Page Id | The combine of namespace id and page id | +132:260 | Version | The combine of sequence and epoch | +260:324 | Ref count | The page being ref count | + +buffer(operate:del) + +Bits | Name | Description. | +--------------------|------------------|-----------------------| +0:8 | Write Type | Write batch operation type | +8:132 | Page Id | The combine of namespace id and page id | +132:260 | Version | The combine of sequence and epoch | + + + +### The GC routine of PageStorage + +In the beginning, in order to control the time of restoring WriteBatches and reconstructing the PageDirectory while startups, PageStorage needs to apply GC on WALStore. When the log files of WALStore reach a certain number, PageDirectory will serialize the latest version of entries and dump the entries as a record into a new log file. After that, the log files generated before the PageDirectory snapshot (except the log file is being written) will be removed. Usually, this part is lightweight since we traverse the PageDirectory will small lock granularity. And the PageDirectory's snapshot size dumped to disk is relatively small. + +After that, PageStorage applies GC on PageDirectory. It will clean up the expired snapshot and expired page entries. Those expired page entries will be marked as removed from BlobStore. All related space maps in BlobStore will be updated. + +After the space maps are updated, BlobStore will check all BlobStat to find out whether there are BlobFiles with a low valid rate. In order to reduce the space amplification brought by those low valid rate BlobFiles, PageStorage will perform "full GC" on those BlobFiles. The full GC will copy the valid data to the new BlobFile(s). + +If "full GC" does happen in BlobStore, then BlobStore informs PageDirectory that some pages have been migrated and where are the new locations of those pages. PageDirectory applies the changes from BlobStore. + +In practice, the probability of BlobStore triggering "full GC" is very low, which means that we will not generate too many read and write IO during the GC process. diff --git a/docs/design/images/tiflash-dt-architecture.png b/docs/design/images/tiflash-dt-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..40b28a1296d0a714fa6874e00661218b76c1b200 GIT binary patch literal 16580 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 U)>kp$uM=P>$*IYfN}C7%A6r!@WdHyG literal 0 HcmV?d00001 diff --git a/docs/design/images/tiflash-ps-v3-architecture.png b/docs/design/images/tiflash-ps-v3-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..adef9fd268262464452e67c451d0a3232dfc773f GIT binary patch literal 80904 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%0AZlAQgMLElv= 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 ZMX`Kx{yHv%M*9x>^k0rA+c5wD literal 0 HcmV?d00001 diff --git a/docs/design/images/tiflash-ps-v3-freemap.png b/docs/design/images/tiflash-ps-v3-freemap.png new file mode 100644 index 0000000000000000000000000000000000000000..5fde00f2ae59d6c72f17a4e66a06b4cc5cbf1b28 GIT binary patch literal 34714 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 literal 0 HcmV?d00001 diff --git a/docs/design/images/tiflash-ps-v3-wal-store.png b/docs/design/images/tiflash-ps-v3-wal-store.png new file mode 100644 index 0000000000000000000000000000000000000000..d398ffc13ede956053b8eea5c7be237675df5556 GIT binary patch literal 30384 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

Vu! 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 literal 0 HcmV?d00001 From 75ac6f9163ee91524ddd87f7043a94228c6b232f Mon Sep 17 00:00:00 2001 From: JaySon Date: Wed, 18 May 2022 20:08:38 +0800 Subject: [PATCH 035/127] Fix some bugs in grafana panels (#4919) close pingcap/tiflash#4917, close pingcap/tiflash#4918 --- metrics/grafana/tiflash_summary.json | 557 ++++++++++++++++++++------- 1 file changed, 420 insertions(+), 137 deletions(-) diff --git a/metrics/grafana/tiflash_summary.json b/metrics/grafana/tiflash_summary.json index 5b534e27f01..a6d1abac46f 100644 --- a/metrics/grafana/tiflash_summary.json +++ b/metrics/grafana/tiflash_summary.json @@ -33,6 +33,12 @@ "id": "prometheus", "name": "Prometheus", "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" } ], "annotations": { @@ -52,7 +58,7 @@ "gnetId": null, "graphTooltip": 1, "id": null, - "iteration": 1647230387563, + "iteration": 1652861766192, "links": [], "panels": [ { @@ -76,7 +82,12 @@ "description": "The storage size per TiFlash instance.\n(Not including some disk usage of TiFlash-Proxy by now)", "editable": true, "error": false, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 5, + "fillGradient": 0, "grid": {}, "gridPos": { "h": 8, @@ -84,6 +95,7 @@ "x": 0, "y": 1 }, + "hiddenSeries": false, "id": 53, "legend": { "alignAsTable": true, @@ -103,7 +115,11 @@ "linewidth": 0, "links": [], "nullPointMode": "null", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -173,7 +189,12 @@ "description": "The available capacity size per TiFlash instance", "editable": true, "error": false, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 5, + "fillGradient": 0, "grid": {}, "gridPos": { "h": 8, @@ -181,6 +202,7 @@ "x": 8, "y": 1 }, + "hiddenSeries": false, "id": 54, "legend": { "alignAsTable": true, @@ -200,7 +222,11 @@ "linewidth": 0, "links": [], "nullPointMode": "null", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -270,7 +296,12 @@ "description": "The capacity size per TiFlash instance", "editable": true, "error": false, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 5, + "fillGradient": 0, "grid": {}, "gridPos": { "h": 8, @@ -278,6 +309,7 @@ "x": 16, "y": 1 }, + "hiddenSeries": false, "id": 55, "legend": { "alignAsTable": true, @@ -297,7 +329,11 @@ "linewidth": 0, "links": [], "nullPointMode": "null", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -367,7 +403,12 @@ "description": "TiFlash uptime since last restart", "editable": true, "error": false, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 1, + "fillGradient": 0, "grid": {}, "gridPos": { "h": 8, @@ -375,6 +416,7 @@ "x": 0, "y": 9 }, + "hiddenSeries": false, "id": 21, "legend": { "alignAsTable": true, @@ -391,8 +433,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -466,13 +511,19 @@ "dashes": false, "datasource": "${DS_TEST-CLUSTER}", "description": "The memory usage per TiFlash instance", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 1, + "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 9 }, + "hiddenSeries": false, "id": 10, "legend": { "alignAsTable": true, @@ -489,7 +540,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -636,7 +691,12 @@ "description": "TiFlash CPU usage calculated with process CPU running seconds.", "editable": true, "error": false, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 1, + "fillGradient": 0, "grid": {}, "gridPos": { "h": 7, @@ -644,6 +704,7 @@ "x": 0, "y": 17 }, + "hiddenSeries": false, "id": 51, "legend": { "alignAsTable": false, @@ -660,7 +721,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -735,13 +800,19 @@ "dashes": false, "datasource": "${DS_TEST-CLUSTER}", "description": "The number of fsync operations.\n(Only counting storage engine of TiFlash by now. Not including TiFlash-Proxy)", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 0, + "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 12, "y": 17 }, + "hiddenSeries": false, "id": 52, "legend": { "alignAsTable": false, @@ -758,7 +829,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -824,13 +899,19 @@ "dashes": false, "datasource": "${DS_TEST-CLUSTER}", "description": "The number of open file descriptors action.\n(Only counting storage engine of TiFlash by now. Not including TiFlash-Proxy)", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 1, + "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 24 }, + "hiddenSeries": false, "id": 22, "legend": { "alignAsTable": false, @@ -849,7 +930,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -921,13 +1006,19 @@ "dashes": false, "datasource": "${DS_TEST-CLUSTER}", "description": "The number of currently opened file descriptors.\n(Only counting storage engine of TiFlash by now. Not including TiFlash-Proxy)", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 1, + "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 24 }, + "hiddenSeries": false, "id": 50, "legend": { "alignAsTable": true, @@ -946,7 +1037,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -1051,13 +1146,19 @@ "dashLength": 10, "dashes": false, "datasource": "${DS_TEST-CLUSTER}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 0, + "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 0, "y": 2 }, + "hiddenSeries": false, "id": 9, "legend": { "alignAsTable": true, @@ -1074,7 +1175,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -1139,13 +1244,19 @@ "dashLength": 10, "dashes": false, "datasource": "${DS_TEST-CLUSTER}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 0, + "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 12, "y": 2 }, + "hiddenSeries": false, "id": 2, "legend": { "alignAsTable": true, @@ -1162,7 +1273,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -1226,13 +1341,19 @@ "dashLength": 10, "dashes": false, "datasource": "${DS_TEST-CLUSTER}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 1, + "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 0, "y": 9 }, + "hiddenSeries": false, "id": 11, "legend": { "alignAsTable": false, @@ -1249,7 +1370,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -1334,13 +1459,19 @@ "dashLength": 10, "dashes": false, "datasource": "${DS_TEST-CLUSTER}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 0, + "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 12, "y": 9 }, + "hiddenSeries": false, "id": 12, "legend": { "alignAsTable": true, @@ -1357,7 +1488,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -1421,13 +1556,19 @@ "dashLength": 10, "dashes": false, "datasource": "${DS_TEST-CLUSTER}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 1, + "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 0, "y": 16 }, + "hiddenSeries": false, "id": 13, "legend": { "alignAsTable": false, @@ -1444,7 +1585,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -1529,13 +1674,19 @@ "dashLength": 10, "dashes": false, "datasource": "${DS_TEST-CLUSTER}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 0, + "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 12, "y": 16 }, + "hiddenSeries": false, "id": 14, "legend": { "alignAsTable": true, @@ -1552,7 +1703,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -1616,13 +1771,19 @@ "dashLength": 10, "dashes": false, "datasource": "${DS_TEST-CLUSTER}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 1, + "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 0, "y": 23 }, + "hiddenSeries": false, "id": 63, "legend": { "avg": false, @@ -1637,7 +1798,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 2, "points": false, "renderer": "flot", @@ -1722,13 +1887,19 @@ "dashLength": 10, "dashes": false, "datasource": "${DS_TEST-CLUSTER}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 0, + "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 12, "y": 23 }, + "hiddenSeries": false, "id": 77, "legend": { "alignAsTable": true, @@ -1745,7 +1916,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -1843,7 +2018,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -1942,7 +2117,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -2041,7 +2216,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -2140,7 +2315,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -3346,7 +3521,7 @@ "h": 8, "w": 12, "x": 0, - "y": 4 + "y": 5 }, "hiddenSeries": false, "id": 41, @@ -3369,7 +3544,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -3459,7 +3634,7 @@ "h": 8, "w": 12, "x": 12, - "y": 4 + "y": 5 }, "hiddenSeries": false, "id": 38, @@ -3482,7 +3657,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -3509,17 +3684,21 @@ "refId": "A" }, { + "exemplar": true, "expr": "sum((rate(tiflash_system_profile_event_PSMWriteBytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[5m]) + rate(tiflash_system_profile_event_WriteBufferFromFileDescriptorWriteBytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[5m]) + rate(tiflash_system_profile_event_WriteBufferAIOWriteBytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[5m])) / (rate(tiflash_system_profile_event_DMWriteBytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[5m]))) by (instance)", "format": "time_series", "hide": false, + "interval": "", "intervalFactor": 1, "legendFormat": "5min-{{instance}}", "refId": "B" }, { + "exemplar": true, "expr": "sum((rate(tiflash_system_profile_event_PSMWriteBytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[10m]) + rate(tiflash_system_profile_event_WriteBufferFromFileDescriptorWriteBytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[10m]) + rate(tiflash_system_profile_event_WriteBufferAIOWriteBytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[10m])) / (rate(tiflash_system_profile_event_DMWriteBytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[10m]))) by (instance)", "format": "time_series", "hide": true, + "interval": "", "intervalFactor": 1, "legendFormat": "10min-{{instance}}", "refId": "C" @@ -3608,7 +3787,7 @@ "h": 8, "w": 24, "x": 0, - "y": 12 + "y": 13 }, "hiddenSeries": false, "id": 40, @@ -3631,7 +3810,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -3708,7 +3887,7 @@ "h": 8, "w": 12, "x": 0, - "y": 20 + "y": 21 }, "hiddenSeries": false, "id": 39, @@ -3731,7 +3910,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -3821,7 +4000,7 @@ "h": 8, "w": 12, "x": 12, - "y": 20 + "y": 21 }, "hiddenSeries": false, "id": 42, @@ -3846,7 +4025,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -3955,7 +4134,7 @@ "h": 8, "w": 12, "x": 0, - "y": 28 + "y": 29 }, "hiddenSeries": false, "id": 43, @@ -3978,7 +4157,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -4061,7 +4240,7 @@ "h": 8, "w": 12, "x": 12, - "y": 28 + "y": 29 }, "heatmap": {}, "hideZeroBuckets": true, @@ -4124,7 +4303,7 @@ "h": 8, "w": 12, "x": 0, - "y": 36 + "y": 37 }, "hiddenSeries": false, "id": 46, @@ -4149,7 +4328,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -4247,7 +4426,7 @@ "h": 8, "w": 12, "x": 12, - "y": 36 + "y": 37 }, "hiddenSeries": false, "id": 47, @@ -4272,7 +4451,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -4371,7 +4550,7 @@ "h": 8, "w": 12, "x": 0, - "y": 44 + "y": 45 }, "height": "", "hiddenSeries": false, @@ -4400,7 +4579,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -4501,7 +4680,7 @@ "h": 8, "w": 12, "x": 12, - "y": 44 + "y": 45 }, "height": "", "hiddenSeries": false, @@ -4530,7 +4709,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -4629,7 +4808,7 @@ "h": 8, "w": 12, "x": 0, - "y": 52 + "y": 53 }, "hiddenSeries": false, "id": 88, @@ -4652,7 +4831,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 2, "points": false, "renderer": "flot", @@ -4829,7 +5008,7 @@ "h": 8, "w": 12, "x": 12, - "y": 52 + "y": 53 }, "hiddenSeries": false, "id": 67, @@ -4852,7 +5031,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -4943,7 +5122,7 @@ "h": 8, "w": 12, "x": 0, - "y": 60 + "y": 61 }, "hiddenSeries": false, "id": 84, @@ -4966,7 +5145,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 2, "points": false, "renderer": "flot", @@ -5043,7 +5222,7 @@ "h": 8, "w": 12, "x": 12, - "y": 60 + "y": 61 }, "hiddenSeries": false, "id": 86, @@ -5066,7 +5245,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 2, "points": false, "renderer": "flot", @@ -5204,11 +5383,11 @@ "renderer": "flot", "seriesOverrides": [ { - "alias": "BlobAllocated", + "alias": "/^BlobAllocated/", "yaxis": 1 }, { - "alias": "BlobReusedRate", + "alias": "/^BlobExpandRate/", "yaxis": 2 } ], @@ -5223,17 +5402,17 @@ "hide": false, "interval": "", "intervalFactor": 2, - "legendFormat": "BlobAllocated", + "legendFormat": "BlobAllocated-{{instance}}", "refId": "A" }, { "exemplar": true, - "expr": "sum(rate(tiflash_system_profile_event_PSV3MBlobReused{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m]) / (rate(tiflash_system_profile_event_PSV3MBlobExpansion{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m]) + rate(tiflash_system_profile_event_PSV3MBlobReused{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m]))) by (instance)", + "expr": "sum(rate(tiflash_system_profile_event_PSV3MBlobExpansion{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m]) / (rate(tiflash_system_profile_event_PSV3MBlobExpansion{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m]) + rate(tiflash_system_profile_event_PSV3MBlobReused{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m]))) by (instance)", "format": "time_series", "hide": false, "interval": "", "intervalFactor": 1, - "legendFormat": "BlobReusedRate", + "legendFormat": "BlobExpandRate-{{instance}}", "refId": "B" } ], @@ -5241,7 +5420,7 @@ "timeFrom": null, "timeRegions": [], "timeShift": null, - "title": "The BlobStore Status", + "title": "BlobStore Status", "tooltip": { "shared": true, "sort": 0, @@ -5313,7 +5492,7 @@ "h": 9, "w": 24, "x": 0, - "y": 5 + "y": 6 }, "height": "", "hiddenSeries": false, @@ -5342,7 +5521,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -5450,7 +5629,7 @@ "h": 8, "w": 24, "x": 0, - "y": 14 + "y": 15 }, "hiddenSeries": false, "id": 62, @@ -5473,7 +5652,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -5562,7 +5741,7 @@ "h": 9, "w": 24, "x": 0, - "y": 22 + "y": 23 }, "height": "", "hiddenSeries": false, @@ -5591,7 +5770,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -5684,7 +5863,7 @@ "h": 9, "w": 24, "x": 0, - "y": 31 + "y": 32 }, "hiddenSeries": false, "id": 90, @@ -5707,7 +5886,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -5801,13 +5980,19 @@ "dashLength": 10, "dashes": false, "datasource": "${DS_TEST-CLUSTER}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 0, + "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 0, - "y": 6 + "y": 7 }, + "hiddenSeries": false, "id": 35, "legend": { "alignAsTable": false, @@ -5824,7 +6009,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -5889,13 +6078,19 @@ "dashLength": 10, "dashes": false, "datasource": "${DS_TEST-CLUSTER}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 1, + "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 12, - "y": 6 + "y": 7 }, + "hiddenSeries": false, "id": 36, "legend": { "alignAsTable": false, @@ -5912,7 +6107,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -5997,13 +6196,19 @@ "dashLength": 10, "dashes": false, "datasource": "${DS_TEST-CLUSTER}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 1, + "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 0, - "y": 13 + "y": 14 }, + "hiddenSeries": false, "id": 37, "legend": { "alignAsTable": true, @@ -6022,7 +6227,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -6121,13 +6330,19 @@ "dashes": false, "datasource": "${DS_TEST-CLUSTER}", "description": "The number of currently applying snapshots.", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 1, + "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 12, - "y": 13 + "y": 14 }, + "hiddenSeries": false, "id": 75, "legend": { "alignAsTable": true, @@ -6146,7 +6361,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -6214,14 +6433,20 @@ "description": "Duration of applying Raft write logs", "editable": true, "error": false, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 1, + "fillGradient": 0, "grid": {}, "gridPos": { "h": 7, "w": 24, "x": 0, - "y": 20 + "y": 21 }, + "hiddenSeries": false, "id": 82, "legend": { "alignAsTable": true, @@ -6240,7 +6465,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -6360,11 +6589,15 @@ "dataFormat": "tsbuckets", "datasource": "${DS_TEST-CLUSTER}", "description": "Duration of applying Raft write logs", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "gridPos": { "h": 7, "w": 12, "x": 0, - "y": 27 + "y": 28 }, "heatmap": {}, "hideZeroBuckets": true, @@ -6426,11 +6659,15 @@ "dataFormat": "tsbuckets", "datasource": "${DS_TEST-CLUSTER}", "description": "Duration of applying Raft write logs", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "gridPos": { "h": 7, "w": 12, "x": 12, - "y": 27 + "y": 28 }, "heatmap": {}, "hideZeroBuckets": true, @@ -6492,11 +6729,15 @@ "dataFormat": "tsbuckets", "datasource": "${DS_TEST-CLUSTER}", "description": "Duration of pre-decode when applying region snapshot", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "gridPos": { "h": 7, "w": 12, "x": 0, - "y": 34 + "y": 35 }, "heatmap": {}, "hideZeroBuckets": true, @@ -6558,16 +6799,20 @@ "dataFormat": "tsbuckets", "datasource": "${DS_TEST-CLUSTER}", "description": "Duration of SST to DT in pre-decode when applying region snapshot", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "gridPos": { "h": 7, "w": 12, "x": 12, - "y": 34 + "y": 35 }, "heatmap": {}, "hideZeroBuckets": true, "highlightCards": true, - "id": 100, + "id": 127, "legend": { "show": true }, @@ -6608,72 +6853,6 @@ "yBucketNumber": null, "yBucketSize": null }, - { - "cards": { - "cardPadding": null, - "cardRound": null - }, - "color": { - "cardColor": "#b4ff00", - "colorScale": "sqrt", - "colorScheme": "interpolateSpectral", - "exponent": 0.5, - "min": 0, - "mode": "spectrum" - }, - "dataFormat": "tsbuckets", - "datasource": "${DS_TEST-CLUSTER}", - "description": "Duration of pre-decode when applying region snapshot", - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 34 - }, - "heatmap": {}, - "hideZeroBuckets": true, - "highlightCards": true, - "id": 73, - "legend": { - "show": true - }, - "links": [], - "reverseYBuckets": false, - "targets": [ - { - "expr": "sum(delta(tiflash_raft_command_duration_seconds_bucket{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\", type=\"snapshot_flush\"}[1m])) by (le)", - "format": "heatmap", - "intervalFactor": 2, - "legendFormat": "{{le}}", - "refId": "B" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Snapshot Flush Duration", - "tooltip": { - "show": true, - "showHistogram": true - }, - "type": "heatmap", - "xAxis": { - "show": true - }, - "xBucketNumber": null, - "xBucketSize": null, - "yAxis": { - "decimals": 0, - "format": "s", - "logBase": 1, - "max": null, - "min": null, - "show": true, - "splitFactor": null - }, - "yBucketBound": "upper", - "yBucketNumber": null, - "yBucketSize": null - }, { "aliasColors": {}, "bars": false, @@ -6682,14 +6861,20 @@ "datasource": "${DS_TEST-CLUSTER}", "decimals": 1, "description": "The keys flow of different kinds of Raft operations", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 1, + "fillGradient": 0, "gridPos": { "h": 7, - "w": 12, + "w": 24, "x": 0, - "y": 41 + "y": 42 }, "height": "", + "hiddenSeries": false, "id": 71, "legend": { "alignAsTable": true, @@ -6711,7 +6896,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -6772,6 +6961,76 @@ "alignLevel": null } }, + { + "cards": { + "cardPadding": null, + "cardRound": null + }, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateSpectral", + "exponent": 0.5, + "min": 0, + "mode": "spectrum" + }, + "dataFormat": "tsbuckets", + "datasource": "${DS_TEST-CLUSTER}", + "description": "Duration of pre-decode when applying region snapshot", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 49 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 73, + "legend": { + "show": true + }, + "links": [], + "reverseYBuckets": false, + "targets": [ + { + "expr": "sum(delta(tiflash_raft_command_duration_seconds_bucket{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\", type=\"snapshot_flush\"}[1m])) by (le)", + "format": "heatmap", + "intervalFactor": 2, + "legendFormat": "{{le}}", + "refId": "B" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Snapshot Flush Duration", + "tooltip": { + "show": true, + "showHistogram": true + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": null, + "xBucketSize": null, + "yAxis": { + "decimals": 0, + "format": "s", + "logBase": 1, + "max": null, + "min": null, + "show": true, + "splitFactor": null + }, + "yBucketBound": "upper", + "yBucketNumber": null, + "yBucketSize": null + }, { "cards": { "cardPadding": null, @@ -6787,11 +7046,15 @@ "dataFormat": "tsbuckets", "datasource": "${DS_TEST-CLUSTER}", "description": "Duration of ingesting SST", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "gridPos": { "h": 7, "w": 12, "x": 12, - "y": 41 + "y": 49 }, "heatmap": {}, "hideZeroBuckets": true, @@ -6853,11 +7116,15 @@ "dataFormat": "tsbuckets", "datasource": "${DS_TEST-CLUSTER}", "description": "Duration of decoding Region data into blocks when writing Region data to the storage layer. (Mixed with \"write logs\" and \"apply Snapshot\" operations)", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "gridPos": { "h": 7, "w": 12, "x": 0, - "y": 48 + "y": 56 }, "heatmap": {}, "hideZeroBuckets": true, @@ -6919,11 +7186,15 @@ "dataFormat": "tsbuckets", "datasource": "${DS_TEST-CLUSTER}", "description": "Duration of writing Region data blocks to the storage layer (Mixed with \"write logs\" and \"apply Snapshot\" operations)", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "gridPos": { "h": 7, "w": 12, "x": 12, - "y": 48 + "y": 56 }, "heatmap": {}, "hideZeroBuckets": true, @@ -6985,11 +7256,15 @@ "dataFormat": "tsbuckets", "datasource": "${DS_TEST-CLUSTER}", "description": "Latency that TiKV sends raft log to TiFlash.", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "gridPos": { "h": 7, "w": 12, "x": 0, - "y": 55 + "y": 63 }, "heatmap": {}, "hideZeroBuckets": true, @@ -7044,14 +7319,20 @@ "description": "Latency that TiKV sends raft log to TiFlash.", "editable": true, "error": false, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, "fill": 1, + "fillGradient": 0, "grid": {}, "gridPos": { "h": 7, "w": 12, "x": 12, - "y": 55 + "y": 63 }, + "hiddenSeries": false, "id": 91, "legend": { "alignAsTable": true, @@ -7070,7 +7351,11 @@ "linewidth": 1, "links": [], "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, "percentage": false, + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -7188,7 +7473,7 @@ "h": 8, "w": 12, "x": 0, - "y": 7 + "y": 80 }, "hiddenSeries": false, "id": 99, @@ -7211,7 +7496,7 @@ "alertThreshold": true }, "percentage": false, - "pluginVersion": "6.1.6", + "pluginVersion": "7.5.11", "pointradius": 5, "points": false, "renderer": "flot", @@ -7341,7 +7626,7 @@ "h": 8, "w": 12, "x": 12, - "y": 7 + "y": 80 }, "heatmap": {}, "hideZeroBuckets": true, @@ -7647,7 +7932,6 @@ }, "yaxes": [ { - "$$hashKey": "object:650", "format": "short", "label": null, "logBase": 1, @@ -7656,7 +7940,6 @@ "show": true }, { - "$$hashKey": "object:651", "format": "short", "label": null, "logBase": 1, From 312e2ea3dfb1b76fc112c6cf83d8f52502d90575 Mon Sep 17 00:00:00 2001 From: xufei Date: Wed, 18 May 2022 21:24:39 +0800 Subject: [PATCH 036/127] add design doc for mpp partition table support (#4913) ref pingcap/tiflash#3873 --- ...entation-of-mpp-partition-table-support.md | 130 ++++++++++++++++++ docs/design/images/new-mpp-partition.svg | 1 + docs/design/images/original-mpp-partition.svg | 1 + 3 files changed, 132 insertions(+) create mode 100644 docs/design/2022-05-17-new-implementation-of-mpp-partition-table-support.md create mode 100644 docs/design/images/new-mpp-partition.svg create mode 100644 docs/design/images/original-mpp-partition.svg diff --git a/docs/design/2022-05-17-new-implementation-of-mpp-partition-table-support.md b/docs/design/2022-05-17-new-implementation-of-mpp-partition-table-support.md new file mode 100644 index 00000000000..ad9f80671c4 --- /dev/null +++ b/docs/design/2022-05-17-new-implementation-of-mpp-partition-table-support.md @@ -0,0 +1,130 @@ +# New implementation of MPP partition table support + +- Author(s): [Fei Xu](http://github.com/windtalker) + +## Background + +TiDB already supports the partition table, and in the storage layer, the partition table can be synced to TiFlash successfully, however, in the computing layer, TiFlash's MPP engine still does not support the partition table robustly. From the persective of the computing layer, one of the most important characteristics of the partition table is partition pruning. Currently, TiDB has two mode of partition prune: + +### Static partition prune + +In this mode, partition prune happens at compile time. If TiDB needs to read multiple partitions from a partition table, it simply uses the `PartitionUnion` operator to union all the partition scans. A typical plan under static prune is + +```sql +mysql> explain select count(*) from employees where store_id > 10; ++------------------------------------+----------+-----------+-------------------------------+-----------------------------------+ +| id | estRows | task | access object | operator info | ++------------------------------------+----------+-----------+-------------------------------+-----------------------------------+ +| HashAgg_13 | 1.00 | root | | funcs:count(Column#10)->Column#9 | +| └─PartitionUnion_14 | 2.00 | root | | | +| ├─StreamAgg_29 | 1.00 | root | | funcs:count(Column#12)->Column#10 | +| │ └─TableReader_30 | 1.00 | root | | data:StreamAgg_18 | +| │ └─StreamAgg_18 | 1.00 | cop[tikv] | | funcs:count(1)->Column#12 | +| │ └─Selection_28 | 3333.33 | cop[tikv] | | gt(test.employees.store_id, 10) | +| │ └─TableFullScan_27 | 10000.00 | cop[tikv] | table:employees, partition:p2 | keep order:false, stats:pseudo | +| └─StreamAgg_48 | 1.00 | root | | funcs:count(Column#14)->Column#10 | +| └─TableReader_49 | 1.00 | root | | data:StreamAgg_37 | +| └─StreamAgg_37 | 1.00 | cop[tikv] | | funcs:count(1)->Column#14 | +| └─Selection_47 | 3333.33 | cop[tikv] | | gt(test.employees.store_id, 10) | +| └─TableFullScan_46 | 10000.00 | cop[tikv] | table:employees, partition:p3 | keep order:false, stats:pseudo | ++------------------------------------+----------+-----------+-------------------------------+-----------------------------------+ + +``` + +### Dynamic partition prune + +In this mode, partition prune happens when TiDB generates executor. Compared to static partition prune, dynamic partition prune has at least two advantages: + +- The plan is more elegant +- Using dynamic partition prune, TiDB can use some runtime information to do the partition prune. For example, in index join, if the probe side is a partition table, TiDB can use the data from build side to prune the partition. + +A typical plan under dynamic partition prune is + +```sql +mysql> explain select count(*) from employees where store_id > 10; ++------------------------------+----------+-----------+-----------------+----------------------------------+ +| id | estRows | task | access object | operator info | ++------------------------------+----------+-----------+-----------------+----------------------------------+ +| StreamAgg_20 | 1.00 | root | | funcs:count(Column#11)->Column#9 | +| └─TableReader_21 | 1.00 | root | partition:p2,p3 | data:StreamAgg_9 | +| └─StreamAgg_9 | 1.00 | cop[tikv] | | funcs:count(1)->Column#11 | +| └─Selection_19 | 3333.33 | cop[tikv] | | gt(test.employees.store_id, 10) | +| └─TableFullScan_18 | 10000.00 | cop[tikv] | table:employees | keep order:false, stats:pseudo | ++------------------------------+----------+-----------+-----------------+----------------------------------+ + +``` + +As we can see, there is no `PartitionUnion` operator, and the access object of `TableReader` operator shows the accessed partition info. + +## Problem + +Currently, MPP does not support `PartitionUnion` operator, so under static partition prune, all the operator above `PartitionUnion` operator can't be pushed to TiFlash: + +```sql +mysql> explain select count(*) from employees where store_id > 10; ++------------------------------------+----------+-------------------+-------------------------------+-----------------------------------+ +| id | estRows | task | access object | operator info | ++------------------------------------+----------+-------------------+-------------------------------+-----------------------------------+ +| HashAgg_15 | 1.00 | root | | funcs:count(Column#10)->Column#9 | +| └─PartitionUnion_17 | 2.00 | root | | | +| ├─StreamAgg_35 | 1.00 | root | | funcs:count(Column#12)->Column#10 | +| │ └─TableReader_36 | 1.00 | root | | data:StreamAgg_23 | +| │ └─StreamAgg_23 | 1.00 | batchCop[tiflash] | | funcs:count(1)->Column#12 | +| │ └─Selection_34 | 3333.33 | batchCop[tiflash] | | gt(test.employees.store_id, 10) | +| │ └─TableFullScan_33 | 10000.00 | batchCop[tiflash] | table:employees, partition:p2 | keep order:false, stats:pseudo | +| └─StreamAgg_56 | 1.00 | root | | funcs:count(Column#14)->Column#10 | +| └─TableReader_57 | 1.00 | root | | data:StreamAgg_44 | +| └─StreamAgg_44 | 1.00 | batchCop[tiflash] | | funcs:count(1)->Column#14 | +| └─Selection_55 | 3333.33 | batchCop[tiflash] | | gt(test.employees.store_id, 10) | +| └─TableFullScan_54 | 10000.00 | batchCop[tiflash] | table:employees, partition:p3 | keep order:false, stats:pseudo | ++------------------------------------+----------+-------------------+-------------------------------+-----------------------------------+ + +``` + +For dynamic partition mode, MPP has a naive support: + +```sql +mysql> explain select count(*) from employees where store_id > 10; ++--------------------------------+----------+--------------+-----------------+---------------------------------------------------------+ +| id | estRows | task | access object | operator info | ++--------------------------------+----------+--------------+-----------------+---------------------------------------------------------+ +| HashAgg_21 | 1.00 | root | | funcs:count(Column#11)->Column#9 | +| └─TableReader_23 | 1.00 | root | partition:p2,p3 | data:ExchangeSender_22 | +| └─ExchangeSender_22 | 1.00 | mpp[tiflash] | | ExchangeType: PassThrough | +| └─HashAgg_9 | 1.00 | mpp[tiflash] | | funcs:count(1)->Column#11 | +| └─Selection_20 | 3333.33 | mpp[tiflash] | | gt(test.employees.store_id, 10) | +| └─TableFullScan_19 | 10000.00 | mpp[tiflash] | table:employees | keep order:false, stats:pseudo | ++--------------------------------+----------+--------------+-----------------+---------------------------------------------------------+ + +``` + +In runtime, each partition scan will be instantiated as a MPP task, so when running the above sql, TiDB will generate two MPP task: + +

+ original mpp task for partition table +

+Under this implementation, an obvious problem is the number of the MPP tasks will increase as the number of partitions increases. Considering that it is common for a partition table with more than 100 partitions, a full table scan of the partition table may generate hundreds of MPP tasks. And currently, TiDB MPP does not have a sophisticated task schedule strategy, it simply dispatches all the MPP tasks at the same time to TiFlash, too many MPP tasks will cause some known issues in TiFlash(for example, the threads number will be out of control, and TiFlash server may even crash if there is no resource to allocate new thread), so TiFlash will not be able to provide services stably when the number of partitions is very large. + +## Solution + +Since the naive support of the partition table does not meet the GA standards, we need to redesign it. Considering that dynamic partition prune is the future of TiDB's partition prune strategy, we only support MPP in dynamic partition prune mode. + +Under dynamic partition prune, the biggest problem for MPP is the number of MPP tasks will increase as the number of partitions increases, to solve this problem, an intuitive idea is to hide the partition behind `TableScan` operator, we need to support a new table scan operator to read data from multiple partitions: + +

+ new mpp task for partition table +

+ +## Changes + +### Protocol layer + +Both [kvproto](https://github.com/pingcap/kvproto) and [tipb](https://github.com/pingcap/tipb) need to be enhanced. In tipb, a new executor `PartitionTableScan` needs to be added, it mainly records the partitions that need to be scanned. Furthermore, when TiDB scan a table/partition, it only scan a set of Regions in the table/partition, so in kvproto, we need to add all the needed to be scanned in DispatchMPPTaskRequest + +### TiDB + +The main changes will be how to generate MPP task after partition prune. Currently, after partition prune, for each partition, `constructMPPTasksForSinglePartitionTable` will generate a MPP task. In order to generate a MPP task for all the partitions, a new function like `constructMPPTasksForPartitionTable` should be added, it will convert the table scan to PartitionTableScan, and set the correctly, furthermore, it needs to consider the load balance strategy between all the TiFlash nodes for all the partitions. + +### TiFlash + +TiFlash needs to support a new executor: `PartitionTableScan`, it contains multiple partitions. `handleTableScan` in TiFlash only support one table/partition now, it has to be enhanced to support multiple partitions, and TiFlash also need to handle remote read for different partition table correctly. diff --git a/docs/design/images/new-mpp-partition.svg b/docs/design/images/new-mpp-partition.svg new file mode 100644 index 00000000000..1419491cc9f --- /dev/null +++ b/docs/design/images/new-mpp-partition.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/design/images/original-mpp-partition.svg b/docs/design/images/original-mpp-partition.svg new file mode 100644 index 00000000000..101cd9bc716 --- /dev/null +++ b/docs/design/images/original-mpp-partition.svg @@ -0,0 +1 @@ + \ No newline at end of file From 82ba2de75f83d00488bcd8a9399e209d81ce63f1 Mon Sep 17 00:00:00 2001 From: Calvin Neo Date: Wed, 18 May 2022 22:20:38 +0800 Subject: [PATCH 037/127] update tiflash proxy for unsafe recovery related PRs (#4921) ref pingcap/tiflash#4618 --- contrib/tiflash-proxy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tiflash-proxy b/contrib/tiflash-proxy index 5bd9c73fdb7..df26083dd55 160000 --- a/contrib/tiflash-proxy +++ b/contrib/tiflash-proxy @@ -1 +1 @@ -Subproject commit 5bd9c73fdb7db4df76958319f2d751490e2ce696 +Subproject commit df26083dd55211ec63ce028513a391652d1e035d From 74ab3d675d3bcbaf36532805fdf085e6ce316585 Mon Sep 17 00:00:00 2001 From: Meng Xin Date: Wed, 18 May 2022 23:36:38 +0800 Subject: [PATCH 038/127] add window function doc (#4877) close pingcap/tiflash#4200 --- docs/design/2022-05-12-window-function.md | 178 ++++++++++++++++++ .../tiflash-window-function-flow-chart.jpg | Bin 0 -> 155785 bytes 2 files changed, 178 insertions(+) create mode 100644 docs/design/2022-05-12-window-function.md create mode 100644 docs/design/images/tiflash-window-function-flow-chart.jpg diff --git a/docs/design/2022-05-12-window-function.md b/docs/design/2022-05-12-window-function.md new file mode 100644 index 00000000000..d04ab5edba9 --- /dev/null +++ b/docs/design/2022-05-12-window-function.md @@ -0,0 +1,178 @@ +# Window Function +- Author(s): [mengxin9014](http://github.com/mengxin9014), [LittleFall](http://github.com/LittleFall) + +## Introduction +Now more and more customers who use TiDB need to use window function, but TiDB does not support pushing down window function to TiFlash for execution, so once the customer's query has window function, it will prevent the subsequent expression from being pushed down to TiFlash, and TiDB Only one node can be used to process the window function, which will make the query with the window function less efficient and cannot effectively scale the cluster horizontally to improve the execution speed. Therefore, we need to support window function in TiFlash, so that the query with window function can be effectively pushed down to TiFlash. + +## Design + +### Syntax +The syntax is almost the same as mysql +``` +{window_desc} over_clause +over_clause: + {OVER (window_spec) | OVER window_name} +window_spec: + [window_name] [partition_clause] [order_clause] [frame_clause] +partition_clause: + PARTITION BY expr [, expr] ... +order_clause: + ORDER BY expr [ASC|DESC] [, expr [ASC|DESC]] ... +} +``` + +### Window Function Plan In MPP +Each Window Spec has three properties: PartitionBy, OrderBy, Frame; +- OrderBy is equal to PartitionBy + original OrderBy by default, because it needs to be sorted by PartitionBy attribute after dividing nodes. +#### Window Function Grouping +The PlanBuilder stage will group the windows by Window Spec (ie Partition By, Order By, Frame). Those with the same Window Spec will be put into the same Window operator for execution. +- Some functions may have specific requirements for Frame, such as row_number, etc., which will modify their own window spec and delete the frame attribute. +- Sort in window function is added by calling enforceProperty in enumeratePhysicalPlans4Task of Window. Window with the same Order By property can reuse the same Sort. +#### Partition +First, add Exchange and assign it to different nodes according to the Hash value of Partition By. +- When exhuast PhysicalWindow, childrenProperty is required to be of hash type and hash column to be PartitionBy column. When the pair needs to be executed as an MPP Task between multiple nodes. +- If there is no partitionBy clause, which means that all data is to be aggregated on the same node, the property is of type Single Partition. +#### sort +Then you need to push down Sort. Note that this Sort is sorted within a single node (hereinafter referred to as Partial Sort); and tiflash currently only supports this sort, not regular sorting. +- In order to safely distinguish Partial Sort and Sort, add a property SortItemsForPartition in the PhysicalProperty; and set both SortItems and SortItemsForPartition to the OrderBy column of Window when exhaust PhysicalWindow. +- When calling enforceProperty in enumeratePhysicalPlans4Task to generate Sort, MppTask will check whether the current property satisfies SortItems==SortItemsForPartition, and can add sort only if it is satisfied. +#### Push-Down Control +Add a push-down control list and connect to enforce_mpp, and return warnings when the push-down is blocked +Support WindowFuncDes in push-down blacklist. + +#### Example +```sql= +explain select *, + max(salary) over (), + row_number() over w_deptid_salary, + max(salary) over w_deptid_salary, + avg(salary) over w_deptid_salary_3, + rank() over w_deptid_empid, + row_number() over w_salarymod_deptid, + count(empid) over w_salarymod_deptid +from employee +window w_deptid_salary as (partition by deptid order by salary), + w_deptid_salary_3 as (partition by deptid order by salary rows between 1 preceding and 1 following), + w_deptid_empid as (partition by deptid order by empid), + w_salarymod_deptid as (partition by salary%10000 order by deptid); ++----------------------------------------------------------------------+---------+--------------+----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| id | estRows | task | access object | operator info | ++----------------------------------------------------------------------+---------+--------------+----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| TableReader_113 | 17.00 | root | | data:ExchangeSender_112 | +| └─ExchangeSender_112 | 17.00 | cop[tiflash] | | ExchangeType: PassThrough | +| └─Projection_19 | 17.00 | cop[tiflash] | | test.employee.empid, test.employee.deptid, test.employee.salary, Column#20, Column#18, Column#17, Column#16, Column#19, Column#15, Column#13 | +| └─Window_109 | 17.00 | cop[tiflash] | | max(test.employee.salary)->Column#20 over() | +| └─ExchangeReceiver_111 | 17.00 | cop[tiflash] | | | +| └─ExchangeSender_110 | 17.00 | cop[tiflash] | | ExchangeType: PassThrough | +| └─Window_22 | 17.00 | cop[tiflash] | | rank()->Column#19 over(partition by test.employee.deptid order by test.employee.empid) | +| └─Sort_45 | 17.00 | cop[tiflash] | | test.employee.deptid, test.employee.empid | +| └─Window_24 | 17.00 | cop[tiflash] | | row_number()->Column#18 over(partition by test.employee.deptid order by test.employee.salary rows between current row and current row) | +| └─Window_25 | 17.00 | cop[tiflash] | | max(test.employee.salary)->Column#17 over(partition by test.employee.deptid order by test.employee.salary range between unbounded preceding and current row) | +| └─Window_27 | 17.00 | cop[tiflash] | | avg(test.employee.salary)->Column#16 over(partition by test.employee.deptid order by test.employee.salary rows between 1 preceding and 1 following) | +| └─Sort_42 | 17.00 | cop[tiflash] | | test.employee.deptid, test.employee.salary | +| └─ExchangeReceiver_41 | 17.00 | cop[tiflash] | | | +| └─ExchangeSender_40 | 17.00 | cop[tiflash] | | ExchangeType: HashPartition, Hash Cols: [name: test.employee.deptid, collate: N/A] | +| └─Window_29 | 17.00 | cop[tiflash] | | row_number()->Column#15 over(partition by Column#14 order by test.employee.deptid rows between current row and current row) | +| └─Sort_39 | 17.00 | cop[tiflash] | | Column#14, test.employee.deptid | +| └─ExchangeReceiver_38 | 17.00 | cop[tiflash] | | | +| └─ExchangeSender_37 | 17.00 | cop[tiflash] | | ExchangeType: HashPartition, Hash Cols: [name: Column#14, collate: N/A] | +| └─Projection_30 | 17.00 | cop[tiflash] | | test.employee.empid, test.employee.deptid, test.employee.salary, Column#13, mod(test.employee.salary, 10000)->Column#14 | +| └─Window_31 | 17.00 | cop[tiflash] | | count(test.employee.empid)->Column#13 over(partition by Column#12 order by test.employee.deptid range between unbounded preceding and current row) | +| └─Sort_36 | 17.00 | cop[tiflash] | | Column#12, test.employee.deptid | +| └─ExchangeReceiver_35 | 17.00 | cop[tiflash] | | | +| └─ExchangeSender_34 | 17.00 | cop[tiflash] | | ExchangeType: HashPartition, Hash Cols: [name: Column#12, collate: N/A] | +| └─Projection_32 | 17.00 | cop[tiflash] | | test.employee.empid, test.employee.deptid, test.employee.salary, mod(test.employee.salary, 10000)->Column#12 | +| └─TableFullScan_33 | 17.00 | cop[tiflash] | table:employee | keep order:false, stats:pseudo | ++----------------------------------------------------------------------+---------+--------------+----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------+ +25 rows in set (0.01 sec) +``` +#### cost (tentative) +Children task's cost *= 0.05 +#### TiPB Protocol change +[add window function related to executor.proto](https://github.com/pingcap/tipb/pull/256/files) + +### The Logic of Window Function in TiFlash +#### Major additions +- IWindowFunction + Defines execution functions of a window function(row_number, rank, dense_rank). +- WindowDescription + Define a window, including the name, frame, functions of a window. +- WindowBlockInputStream + Define a stream executing window functions. +#### The Execution Logic in WindowBlockInputStream +##### Main Methods +- advancePartitionEnd() + Used to find the position of partition end in Blocks. +- advanceFrameStart() + Used to find the position of frame start in Blocks, which is always current row in pure window functions. +- advanceFrameEnd() + Used to find the position of frame end in Blocks, which is always the next of current row in pure window functions. +- writeOutCurrentRow() + Used to write the result to the output blocks. +##### Executive Process +1. If input_is_finished is true meaning that there are no input blocks, we jump to 12 to handle the last output blocks, or we jump to 2 to read the new block. +2. Try to get that if there is output block, if there is returned it or jump to 3. +3. Read a block from source stream(may sort or receiver stream) through stream->read(); +4. If the block is null set input_is_finished to true, Or append block to input blocks and initialize the output blocks. +5. Find the position of partition end in input blocks, update partition_end and set partition_ended to true. +6. Determine if the current row is smaller than partition end and go to the next step if so, or means that this partition has already finished, Initializing the corresponding variables and jumping to 5. +7. Find the position of frame start in input blocks, update frame_start. +8. Find the position of frame end in input blocks, update frame_end and set frame_ended to true. +9. Write the result to output blocks. +10. Determine if the partition_ended is true and go to the next step if so, or means that the partition_end is in the next blocks, so we jump to 1 and get new input block. +11. Advance current row to the next,and jump to 6. +12. It means finish when input_is_finished is true, and we return the last output blocks. If the output blocks are empty, we return null. +##### Flow Chart +![tiflash-window-function-flow-chart](./images/tiflash-window-function-flow-chart.jpg) +### Support and Limit +- Compared with TiDB, currently only row_number, rank, dense_rank functions are supported. + +| Feature | support | +| ----- | ----- | +| ad hoc window specification | Supported | +| WINDOW clause (select ... from table window w as (partiton by col1)) | Supported | +| ROWS frame | Supported | +| RANGE frame | Not supported | +| INTERVAL syntax for DateTime RANGE OFFSET frame | Not supported | +| Calculating aggregate functions over a frame | Not supported | +| rank(), dense_rank(), row_number() | Supported | +| lag/lead(value, offset, default) | Not supported | + +- Compared with TiDB or MySQL, the result may have some differences. + For example, compare the calculation results of TiFlash and TiDB: +```sql= +select *,row_number() over w1 from window_function_0 window w1 as (partition by i order by dt) limit 10; + +TiDB result: ++------+------+-------------------------+--------------+------+-----------------------+ +| id | i | r | s | dt | row_number() over w1 | ++------+------+-------------------------+--------------+------+-----------------------+ +| 193 | NULL | 2147483646 | basdbga | NULL | 1 | +| 9211 | NULL | -2.225073858507201e-308 | %**^@!#^!@# | NULL | 2 | +| 9205 | NULL | -2.225073858507201e-308 | @!@%sdaf$@$ | NULL | 3 | +| 9199 | NULL | -2.225073858507201e-308 | qwe sdfg sd | NULL | 4 | +| 9193 | NULL | -2.225073858507201e-308 | basdbga | NULL | 5 | +| 9187 | NULL | -2.225073858507201e-308 | aaaaab | NULL | 6 | +| 7 | NULL | NULL | aaaaab | NULL | 7 | +| 9181 | NULL | -2.225073858507201e-308 | NULL | NULL | 8 | +| 9175 | NULL | 2.225073858507201e-308 | %**^@!#^!@# | NULL | 9 | +| 9169 | NULL | 2.225073858507201e-308 | @!@%sdaf$@$ | NULL | 10 | ++------+------+-------------------------+--------------+------+-----------------------+ + +TiFlash result: ++------+------+-------------+--------------+------+-----------------------+ +| id | i | r | s | dt | row_number() over w1 | ++------+------+-------------+--------------+------+-----------------------+ +| 49 | NULL | 0 | basdbga | NULL | 1 | +| 8749 | NULL | -1.1 | NULL | NULL | 2 | +| 25 | NULL | NULL | @!@%sdaf$@$ | NULL | 3 | +| 8809 | NULL | 2147483647 | @!@%sdaf$@$ | NULL | 4 | +| 19 | NULL | NULL | qwe sdfg sd | NULL | 5 | +| 8803 | NULL | 2147483647 | qwe sdfg sd | NULL | 6 | +| 31 | NULL | NULL | %**^@!#^!@# | NULL | 7 | +| 241 | NULL | -2147483648 | @!@%sdaf$@$ | NULL | 8 | +| 37 | NULL | 0 | NULL | NULL | 9 | +| 8797 | NULL | 2147483647 | basdbga | NULL | 10 | ++------+------+-------------+--------------+------+-----------------------+ +``` +The results are inconsistent because in the case that **all the values of partition key and order key are the same**, row number, rank, dense rank functions **are calculated with the order of data storage**, which may be different between TiFlash and TiDB after shuffle. We think it is what we expect in a distributed database. \ No newline at end of file diff --git a/docs/design/images/tiflash-window-function-flow-chart.jpg b/docs/design/images/tiflash-window-function-flow-chart.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ee868ea4d2276fe8f52e23b2cf7b7fdcd3eb729f GIT binary patch literal 155785 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

FWFrUE&LehRyde-v28xm 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%0A~)Vgy?*-$idQnd43>W~A&M!>JwZh!>CZ$Zygoa= zn0sYH-X9DTg*g>j?gjR3Za0X(lX_2+CJ0m4Q+w!oEtj*pDm?+7layzQnCUJhc z6rFy?hhDd1Vd6B*i`S4kN~{jNP4TN=9astISW5-A{j%fHP1?k+aie3)(%Y_$55KsT z4F2ePJg<^yxzJ2J_9@n~+8EB9{BpR*D0A-Q3J3YVPO-OD0#qnj4xGbBH+cr6JrDs_ z9I|NaIFZ$F;3Hq!*E3E`nLmsKCf4uPdBY)$Xo!DSOMFL31u@G`Fp5@S&_N^ufty|0 zh=}m)94iovpc^J_jc3@>?SbVIF{#JgwP4JF9lO}G@u{V2!#HrLM6oW1 ze+%7klW(KJ1|g6IXkC~Rf2kbkS|^<=Qnh18ayJGZ9PKq7picQ)$3Wi>A=8F$QmsuuxtXe1^Lf=Sm5MHlW-& zz@wDuJR;g$NZ2aMoF)|^(@M>H3l^g(g6P>8-TVM#2fN1nLRX;ZINyah+&ed*hq+7M zd+;H*X^I6+MZtpWm{j>it z@c*M z60HKwP6N*rpL?C*YM=-WJ%(&=%+N(3{QB+u=Q3T`z*mx+V=Xt-zU~&g7=2dVJp-4@ zy!FSzuhL%&zkf0PH#Oli&VTUg`t*53iS0ADuHDaVe?R1BZl716|I5?6Mz15~m-L|=Z_)&V6hN4Bp2B=v=c=@HW*2Po$s4!@c@r-=gBijQ XfJF7iu2$;xf7$=}?-A(y?-PFp$1#CL literal 0 HcmV?d00001 From deb7a86628045f667f2fabca093c0fec49a416c5 Mon Sep 17 00:00:00 2001 From: Wenxuan Date: Thu, 19 May 2022 00:42:38 +0800 Subject: [PATCH 039/127] feat: support the new compact RPC request (#4885) ref pingcap/tiflash#4897 --- .clang-tidy | 7 + README.md | 9 +- contrib/kvproto | 2 +- dbms/src/Common/FailPoint.cpp | 4 +- dbms/src/Common/TiFlashMetrics.h | 5 +- dbms/src/Flash/BatchCoprocessorHandler.cpp | 1 + dbms/src/Flash/CMakeLists.txt | 5 +- dbms/src/Flash/Coprocessor/DAGUtils.cpp | 19 - dbms/src/Flash/Coprocessor/DAGUtils.h | 1 - dbms/src/Flash/CoprocessorHandler.cpp | 1 + dbms/src/Flash/FlashService.cpp | 31 +- dbms/src/Flash/FlashService.h | 9 + dbms/src/Flash/Management/ManualCompact.cpp | 243 +++++++++++ dbms/src/Flash/Management/ManualCompact.h | 100 +++++ .../Management/tests/gtest_manual_compact.cpp | 390 ++++++++++++++++++ dbms/src/Flash/ServiceUtils.cpp | 40 ++ dbms/src/Flash/ServiceUtils.h | 24 ++ .../Interpreters/InterpreterManageQuery.cpp | 4 +- dbms/src/Interpreters/Settings.h | 6 +- .../Storages/DeltaMerge/DeltaMergeStore.cpp | 46 ++- .../src/Storages/DeltaMerge/DeltaMergeStore.h | 8 + dbms/src/Storages/DeltaMerge/RowKeyRange.h | 16 + .../DeltaMerge/tests/MultiSegmentTestUtil.h | 160 +++++++ .../DeltaMerge/tests/dm_basic_include.h | 154 ++++++- .../tests/gtest_dm_delta_merge_store.cpp | 217 +++++++++- dbms/src/Storages/IManageableStorage.h | 4 - dbms/src/Storages/StorageDeltaMerge.cpp | 7 +- dbms/src/Storages/StorageDeltaMerge.h | 16 +- etc/config-template.toml | 25 +- 29 files changed, 1484 insertions(+), 70 deletions(-) mode change 100755 => 100644 .clang-tidy create mode 100644 dbms/src/Flash/Management/ManualCompact.cpp create mode 100644 dbms/src/Flash/Management/ManualCompact.h create mode 100644 dbms/src/Flash/Management/tests/gtest_manual_compact.cpp create mode 100644 dbms/src/Flash/ServiceUtils.cpp create mode 100644 dbms/src/Flash/ServiceUtils.h create mode 100644 dbms/src/Storages/DeltaMerge/tests/MultiSegmentTestUtil.h diff --git a/.clang-tidy b/.clang-tidy old mode 100755 new mode 100644 index 99a845d8633..cde27d68c8a --- a/.clang-tidy +++ b/.clang-tidy @@ -18,9 +18,11 @@ Checks: '-*, modernize-redundant-void-arg, modernize-replace-auto-ptr, modernize-replace-random-shuffle, + modernize-use-auto, modernize-use-bool-literals, modernize-use-nullptr, modernize-use-using, + modernize-use-override, modernize-use-equals-default, modernize-use-equals-delete, @@ -160,6 +162,11 @@ Checks: '-*, clang-analyzer-unix.cstring.NullArg, boost-use-to-string, + + cppcoreguidelines-pro-type-cstyle-cast, + cppcoreguidelines-pro-type-member-init, + cppcoreguidelines-no-malloc, + cppcoreguidelines-virtual-class-destructor, ' WarningsAsErrors: '*' diff --git a/README.md b/README.md index a4ccba7d848..4876ccce1ec 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ The following are platform-specific prerequisites. Click to expand details: TiFlash can be built using either LLVM or GCC toolchain on Linux. LLVM toolchain is our official one for releasing. > But for GCC, only GCC 7.x is supported as far, and is not planned to be a long term support. So it may get broken some day, silently. - + - LLVM 13.0.0+ TiFlash compiles using full LLVM toolchain (`clang/compiler-rt/libc++/libc++abi`) by default. You can use a system-wise toolchain if `clang/compiler-rt/libc++/libc++abi` can be installed in your environment. @@ -195,11 +195,12 @@ To run unit tests, you need to build with `-DCMAKE_BUILD_TYPE=DEBUG`: ```shell cd $BUILD cmake $WORKSPACE/tiflash -GNinja -DCMAKE_BUILD_TYPE=DEBUG -ninja gtests_dbms +ninja gtests_dbms # Most TiFlash unit tests +ninja gtests_libdaemon # Settings related tests ninja gtests_libcommon -ninja gtests_libdaemon ``` -And the unit-test executables are at `$BUILD/dbms/gtests_dbms`, `$BUILD/libs/libcommon/src/tests/gtests_libcommon` and `$BUILD/libs/libdaemon/src/tests/gtests_libdaemon`. + +And the unit-test executables are at `$BUILD/dbms/gtests_dbms`, `$BUILD/libs/libdaemon/src/tests/gtests_libdaemon` and `$BUILD/libs/libcommon/src/tests/gtests_libcommon`. ## Run Sanitizer Tests diff --git a/contrib/kvproto b/contrib/kvproto index d229fcc888c..12e2f5a9d16 160000 --- a/contrib/kvproto +++ b/contrib/kvproto @@ -1 +1 @@ -Subproject commit d229fcc888c88506e1a81be0bc19df56623b99da +Subproject commit 12e2f5a9d167f46602804840857ddc8ff06dc695 diff --git a/dbms/src/Common/FailPoint.cpp b/dbms/src/Common/FailPoint.cpp index 602a86642fb..ae6e6308055 100644 --- a/dbms/src/Common/FailPoint.cpp +++ b/dbms/src/Common/FailPoint.cpp @@ -68,6 +68,7 @@ std::unordered_map> FailPointHelper::f M(exception_after_drop_segment) #define APPLY_FOR_FAILPOINTS(M) \ + M(skip_check_segment_update) \ M(force_set_page_file_write_errno) \ M(force_split_io_size_4k) \ M(minimum_block_size_for_cross_join) \ @@ -99,7 +100,8 @@ std::unordered_map> FailPointHelper::f M(pause_when_writing_to_dt_store) \ M(pause_when_ingesting_to_dt_store) \ M(pause_when_altering_dt_store) \ - M(pause_after_copr_streams_acquired) + M(pause_after_copr_streams_acquired) \ + M(pause_before_server_merge_one_delta) namespace FailPoints { diff --git a/dbms/src/Common/TiFlashMetrics.h b/dbms/src/Common/TiFlashMetrics.h index b3ddbd7fe5c..9aa826e0e30 100644 --- a/dbms/src/Common/TiFlashMetrics.h +++ b/dbms/src/Common/TiFlashMetrics.h @@ -115,13 +115,16 @@ namespace DB M(tiflash_storage_command_count, "Total number of storage's command, such as delete range / shutdown /startup", Counter, \ F(type_delete_range, {"type", "delete_range"}), F(type_ingest, {"type", "ingest"})) \ M(tiflash_storage_subtask_count, "Total number of storage's sub task", Counter, F(type_delta_merge, {"type", "delta_merge"}), \ - F(type_delta_merge_fg, {"type", "delta_merge_fg"}), F(type_delta_merge_bg_gc, {"type", "delta_merge_bg_gc"}), \ + F(type_delta_merge_fg, {"type", "delta_merge_fg"}), \ + F(type_delta_merge_fg_rpc, {"type", "delta_merge_fg_rpc"}), \ + F(type_delta_merge_bg_gc, {"type", "delta_merge_bg_gc"}), \ F(type_delta_compact, {"type", "delta_compact"}), F(type_delta_flush, {"type", "delta_flush"}), \ F(type_seg_split, {"type", "seg_split"}), F(type_seg_split_fg, {"type", "seg_split_fg"}), \ F(type_seg_merge, {"type", "seg_merge"}), F(type_place_index_update, {"type", "place_index_update"})) \ M(tiflash_storage_subtask_duration_seconds, "Bucketed histogram of storage's sub task duration", Histogram, \ F(type_delta_merge, {{"type", "delta_merge"}}, ExpBuckets{0.0005, 2, 20}), \ F(type_delta_merge_fg, {{"type", "delta_merge_fg"}}, ExpBuckets{0.0005, 2, 20}), \ + F(type_delta_merge_fg_rpc, {{"type", "delta_merge_fg_rpc"}}, ExpBuckets{0.0005, 2, 20}), \ F(type_delta_merge_bg_gc, {{"type", "delta_merge_bg_gc"}}, ExpBuckets{0.0005, 2, 20}), \ F(type_delta_compact, {{"type", "delta_compact"}}, ExpBuckets{0.0005, 2, 20}), \ F(type_delta_flush, {{"type", "delta_flush"}}, ExpBuckets{0.0005, 2, 20}), \ diff --git a/dbms/src/Flash/BatchCoprocessorHandler.cpp b/dbms/src/Flash/BatchCoprocessorHandler.cpp index dd40a154fcc..273ceec8f08 100644 --- a/dbms/src/Flash/BatchCoprocessorHandler.cpp +++ b/dbms/src/Flash/BatchCoprocessorHandler.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/dbms/src/Flash/CMakeLists.txt b/dbms/src/Flash/CMakeLists.txt index 1b9e3e0aaf8..b32202fa6c5 100644 --- a/dbms/src/Flash/CMakeLists.txt +++ b/dbms/src/Flash/CMakeLists.txt @@ -18,11 +18,12 @@ add_headers_and_sources(flash_service .) add_headers_and_sources(flash_service ./Coprocessor) add_headers_and_sources(flash_service ./Mpp) add_headers_and_sources(flash_service ./Statistics) +add_headers_and_sources(flash_service ./Management) add_library(flash_service ${flash_service_headers} ${flash_service_sources}) target_link_libraries(flash_service dbms) if (ENABLE_TESTS) - add_subdirectory (Coprocessor/tests) - add_subdirectory (tests) + add_subdirectory(Coprocessor/tests) + add_subdirectory(tests) endif () diff --git a/dbms/src/Flash/Coprocessor/DAGUtils.cpp b/dbms/src/Flash/Coprocessor/DAGUtils.cpp index 3be0ac4f55e..4ae6672a619 100644 --- a/dbms/src/Flash/Coprocessor/DAGUtils.cpp +++ b/dbms/src/Flash/Coprocessor/DAGUtils.cpp @@ -29,14 +29,6 @@ #include namespace DB { -namespace ErrorCodes -{ -extern const int NOT_IMPLEMENTED; -extern const int UNKNOWN_USER; -extern const int WRONG_PASSWORD; -extern const int REQUIRED_PASSWORD; -extern const int IP_ADDRESS_NOT_ALLOWED; -} // namespace ErrorCodes const Int8 VAR_SIZE = 0; @@ -1360,17 +1352,6 @@ bool hasUnsignedFlag(const tipb::FieldType & tp) return tp.flag() & TiDB::ColumnFlagUnsigned; } -grpc::StatusCode tiflashErrorCodeToGrpcStatusCode(int error_code) -{ - /// do not use switch statement because ErrorCodes::XXXX is not a compile time constant - if (error_code == ErrorCodes::NOT_IMPLEMENTED) - return grpc::StatusCode::UNIMPLEMENTED; - if (error_code == ErrorCodes::UNKNOWN_USER || error_code == ErrorCodes::WRONG_PASSWORD || error_code == ErrorCodes::REQUIRED_PASSWORD - || error_code == ErrorCodes::IP_ADDRESS_NOT_ALLOWED) - return grpc::StatusCode::UNAUTHENTICATED; - return grpc::StatusCode::INTERNAL; -} - void assertBlockSchema( const DataTypes & expected_types, const Block & block, diff --git a/dbms/src/Flash/Coprocessor/DAGUtils.h b/dbms/src/Flash/Coprocessor/DAGUtils.h index ab59ec1d07d..4d6a62bbe6f 100644 --- a/dbms/src/Flash/Coprocessor/DAGUtils.h +++ b/dbms/src/Flash/Coprocessor/DAGUtils.h @@ -70,7 +70,6 @@ bool isUnsupportedEncodeType(const std::vector & types, tipb::E TiDB::TiDBCollatorPtr getCollatorFromExpr(const tipb::Expr & expr); TiDB::TiDBCollatorPtr getCollatorFromFieldType(const tipb::FieldType & field_type); bool hasUnsignedFlag(const tipb::FieldType & tp); -grpc::StatusCode tiflashErrorCodeToGrpcStatusCode(int error_code); void assertBlockSchema( const DataTypes & expected_types, diff --git a/dbms/src/Flash/CoprocessorHandler.cpp b/dbms/src/Flash/CoprocessorHandler.cpp index 98bea6b73f6..e432dd37083 100644 --- a/dbms/src/Flash/CoprocessorHandler.cpp +++ b/dbms/src/Flash/CoprocessorHandler.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/dbms/src/Flash/FlashService.cpp b/dbms/src/Flash/FlashService.cpp index c66d6f480bb..422c6b47cfa 100644 --- a/dbms/src/Flash/FlashService.cpp +++ b/dbms/src/Flash/FlashService.cpp @@ -22,11 +22,14 @@ #include #include #include +#include #include #include #include +#include #include #include +#include #include #include @@ -45,29 +48,34 @@ FlashService::FlashService(IServer & server_) : server(server_) , security_config(server_.securityConfig()) , log(&Poco::Logger::get("FlashService")) + , manual_compact_manager(std::make_unique( + server_.context().getGlobalContext(), + server_.context().getGlobalContext().getSettingsRef())) { auto settings = server_.context().getSettingsRef(); enable_local_tunnel = settings.enable_local_tunnel; enable_async_grpc_client = settings.enable_async_grpc_client; const size_t default_size = 2 * getNumberOfPhysicalCPUCores(); - size_t cop_pool_size = static_cast(settings.cop_pool_size); + auto cop_pool_size = static_cast(settings.cop_pool_size); cop_pool_size = cop_pool_size ? cop_pool_size : default_size; LOG_FMT_INFO(log, "Use a thread pool with {} threads to handle cop requests.", cop_pool_size); cop_pool = std::make_unique(cop_pool_size, [] { setThreadName("cop-pool"); }); - size_t batch_cop_pool_size = static_cast(settings.batch_cop_pool_size); + auto batch_cop_pool_size = static_cast(settings.batch_cop_pool_size); batch_cop_pool_size = batch_cop_pool_size ? batch_cop_pool_size : default_size; LOG_FMT_INFO(log, "Use a thread pool with {} threads to handle batch cop requests.", batch_cop_pool_size); batch_cop_pool = std::make_unique(batch_cop_pool_size, [] { setThreadName("batch-cop-pool"); }); } +FlashService::~FlashService() = default; + // Use executeInThreadPool to submit job to thread pool which return grpc::Status. -grpc::Status executeInThreadPool(const std::unique_ptr & pool, std::function job) +grpc::Status executeInThreadPool(ThreadPool & pool, std::function job) { std::packaged_task task(job); std::future future = task.get_future(); - pool->schedule([&task] { task(); }); + pool.schedule([&task] { task(); }); return future.get(); } @@ -93,7 +101,7 @@ grpc::Status FlashService::Coprocessor( GET_METRIC(tiflash_coprocessor_response_bytes).Increment(response->ByteSizeLong()); }); - grpc::Status ret = executeInThreadPool(cop_pool, [&] { + grpc::Status ret = executeInThreadPool(*cop_pool, [&] { auto [context, status] = createDBContext(grpc_context); if (!status.ok()) { @@ -127,7 +135,7 @@ ::grpc::Status FlashService::BatchCoprocessor(::grpc::ServerContext * grpc_conte // TODO: update the value of metric tiflash_coprocessor_response_bytes. }); - grpc::Status ret = executeInThreadPool(batch_cop_pool, [&] { + grpc::Status ret = executeInThreadPool(*batch_cop_pool, [&] { auto [context, status] = createDBContext(grpc_context); if (!status.ok()) { @@ -418,4 +426,15 @@ std::tuple FlashService::createDBContext(const grpc::S } } +::grpc::Status FlashService::Compact(::grpc::ServerContext * grpc_context, const ::kvrpcpb::CompactRequest * request, ::kvrpcpb::CompactResponse * response) +{ + CPUAffinityManager::getInstance().bindSelfGrpcThread(); + if (!security_config.checkGrpcContext(grpc_context)) + { + return grpc::Status(grpc::PERMISSION_DENIED, tls_err_msg); + } + + return manual_compact_manager->handleRequest(request, response); +} + } // namespace DB diff --git a/dbms/src/Flash/FlashService.h b/dbms/src/Flash/FlashService.h index 916f0ef1296..2b39479ac49 100644 --- a/dbms/src/Flash/FlashService.h +++ b/dbms/src/Flash/FlashService.h @@ -36,6 +36,10 @@ namespace DB class IServer; class CallExecPool; class EstablishCallData; +namespace Management +{ +class ManualCompactManager; +} // namespace Management class FlashService : public tikvpb::Tikv::Service , public std::enable_shared_from_this @@ -44,6 +48,8 @@ class FlashService : public tikvpb::Tikv::Service public: explicit FlashService(IServer & server_); + ~FlashService() override; + grpc::Status Coprocessor( grpc::ServerContext * grpc_context, const coprocessor::Request * request, @@ -72,6 +78,7 @@ class FlashService : public tikvpb::Tikv::Service ::grpc::Status CancelMPPTask(::grpc::ServerContext * context, const ::mpp::CancelTaskRequest * request, ::mpp::CancelTaskResponse * response) override; + ::grpc::Status Compact(::grpc::ServerContext * context, const ::kvrpcpb::CompactRequest * request, ::kvrpcpb::CompactResponse * response) override; protected: std::tuple createDBContext(const grpc::ServerContext * grpc_context) const; @@ -83,6 +90,8 @@ class FlashService : public tikvpb::Tikv::Service bool enable_local_tunnel = false; bool enable_async_grpc_client = false; + std::unique_ptr manual_compact_manager; + // Put thread pool member(s) at the end so that ensure it will be destroyed firstly. std::unique_ptr cop_pool, batch_cop_pool; }; diff --git a/dbms/src/Flash/Management/ManualCompact.cpp b/dbms/src/Flash/Management/ManualCompact.cpp new file mode 100644 index 00000000000..54373fe4c79 --- /dev/null +++ b/dbms/src/Flash/Management/ManualCompact.cpp @@ -0,0 +1,243 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace DB +{ +namespace FailPoints +{ +extern const char pause_before_server_merge_one_delta[]; +} // namespace FailPoints + +namespace Management +{ + +ManualCompactManager::ManualCompactManager(const Context & global_context_, const Settings & settings_) + : global_context(global_context_.getGlobalContext()) + , settings(settings_) + , log(&Poco::Logger::get("ManualCompactManager")) +{ + worker_pool = std::make_unique(static_cast(settings.manual_compact_pool_size), [] { setThreadName("m-compact-pool"); }); +} + +grpc::Status ManualCompactManager::handleRequest(const ::kvrpcpb::CompactRequest * request, ::kvrpcpb::CompactResponse * response) +{ + { + std::lock_guard lock(mutex); + + // Check whether there are duplicated executions. + if (unsync_active_logical_table_ids.count(request->logical_table_id())) + { + response->mutable_error()->mutable_err_compact_in_progress(); + response->set_has_remaining(false); + return grpc::Status::OK; + } + + // Check whether exceeds the maximum number of concurrent executions. + if (unsync_running_or_pending_tasks > getSettingMaxConcurrency()) + { + response->mutable_error()->mutable_err_too_many_pending_tasks(); + response->set_has_remaining(false); + return grpc::Status::OK; + } + + unsync_active_logical_table_ids.insert(request->logical_table_id()); + unsync_running_or_pending_tasks++; + } + SCOPE_EXIT({ + std::lock_guard lock(mutex); + unsync_active_logical_table_ids.erase(request->logical_table_id()); + unsync_running_or_pending_tasks--; + }); + + std::packaged_task task([&] { + return this->doWorkWithCatch(request, response); + }); + std::future future = task.get_future(); + worker_pool->schedule([&task] { task(); }); + return future.get(); +} + +grpc::Status ManualCompactManager::doWorkWithCatch(const ::kvrpcpb::CompactRequest * request, ::kvrpcpb::CompactResponse * response) +{ + try + { + return this->doWork(request, response); + } + catch (Exception & e) + { + LOG_FMT_ERROR(log, "DB Exception: {}", e.message()); + return grpc::Status(tiflashErrorCodeToGrpcStatusCode(e.code()), e.message()); + } + catch (const std::exception & e) + { + LOG_FMT_ERROR(log, "std exception: {}", e.what()); + return grpc::Status(grpc::StatusCode::INTERNAL, e.what()); + } + catch (...) + { + LOG_FMT_ERROR(log, "other exception"); + return grpc::Status(grpc::StatusCode::INTERNAL, "other exception"); + } +} + +grpc::Status ManualCompactManager::doWork(const ::kvrpcpb::CompactRequest * request, ::kvrpcpb::CompactResponse * response) +{ + const auto & tmt_context = global_context.getTMTContext(); + auto storage = tmt_context.getStorages().get(request->physical_table_id()); + if (storage == nullptr) + { + response->mutable_error()->mutable_err_physical_table_not_exist(); + response->set_has_remaining(false); + return grpc::Status::OK; + } + + auto dm_storage = std::dynamic_pointer_cast(storage); + if (dm_storage == nullptr) + { + response->mutable_error()->mutable_err_physical_table_not_exist(); + response->set_has_remaining(false); + return grpc::Status::OK; + } + + DM::RowKeyValue start_key; + if (request->start_key().empty()) + { + if (dm_storage->isCommonHandle()) + { + start_key = DM::RowKeyValue::COMMON_HANDLE_MIN_KEY; + } + else + { + start_key = DM::RowKeyValue::INT_HANDLE_MIN_KEY; + } + } + else + { + try + { + ReadBufferFromString buf(request->start_key()); + + // TODO: This function seems to be not safe for accepting arbitrary client inputs. + // When client passes a string with "invalid length" encoded, e.g. a very large length, we will allocate memory first and then discover the input is invalid. + // This will cause OOM. + // Also it is not a good idea to use Try-Catch for this scenario. + start_key = DM::RowKeyValue::deserialize(buf); + + if (start_key.is_common_handle != dm_storage->isCommonHandle()) + { + response->mutable_error()->mutable_err_invalid_start_key(); + response->set_has_remaining(false); + return grpc::Status::OK; + } + } + catch (...) + { + response->mutable_error()->mutable_err_invalid_start_key(); + response->set_has_remaining(false); + return grpc::Status::OK; + } + } + + size_t compacted_segments = 0; + bool has_remaining = true; + std::optional compacted_start_key; + std::optional compacted_end_key; + + Stopwatch timer; + + LOG_FMT_INFO(log, "Manual compaction begin for table {}, start_key = {}", request->physical_table_id(), start_key.toDebugString()); + + // Repeatedly merge multiple segments as much as possible. + while (true) + { + FAIL_POINT_PAUSE(FailPoints::pause_before_server_merge_one_delta); + auto compacted_range = dm_storage->mergeDeltaBySegment(global_context, start_key, DM::DeltaMergeStore::TaskRunThread::ForegroundRPC); + + if (compacted_range == std::nullopt) + { + // Segment not found according to current start key + has_remaining = false; + break; + } + + compacted_segments++; + if (compacted_start_key == std::nullopt) + { + compacted_start_key = compacted_range->start; + } + compacted_end_key = compacted_range->end; + + if (timer.elapsedMilliseconds() < getSettingCompactMoreUntilMs()) + { + // Let's compact next segment, since the elapsed time is short. This saves round trip. + start_key = compacted_range->end; + } + else + { + break; + } + } + + if (unlikely(has_remaining && (compacted_start_key == std::nullopt || compacted_end_key == std::nullopt || compacted_segments == 0))) + { + LOG_FMT_ERROR(log, "Assert failed: has_remaining && (compacted_start_key == std::nullopt || compacted_end_key == std::nullopt || compacted_segments == 0)"); + throw Exception("Assert failed", ErrorCodes::LOGICAL_ERROR); + } + + LOG_FMT_INFO(log, "Manual compaction finished for table {}, compacted_start_key = {}, compacted_end_key = {}, has_remaining = {}, compacted_segments = {}, elapsed_ms = {}", request->physical_table_id(), compacted_start_key ? compacted_start_key->toDebugString() : "(null)", compacted_end_key ? compacted_end_key->toDebugString() : "(null)", has_remaining, compacted_segments, timer.elapsedMilliseconds()); + + response->clear_error(); + response->set_has_remaining(has_remaining); + if (compacted_start_key != std::nullopt) + { + WriteBufferFromOwnString wb; + compacted_start_key->serialize(wb); + response->set_compacted_start_key(wb.releaseStr()); + } + if (compacted_end_key != std::nullopt) + { + WriteBufferFromOwnString wb; + compacted_end_key->serialize(wb); + response->set_compacted_end_key(wb.releaseStr()); + } + + return grpc::Status::OK; +} + +uint64_t ManualCompactManager::getSettingCompactMoreUntilMs() const +{ + return settings.manual_compact_more_until_ms.get(); +} + +uint64_t ManualCompactManager::getSettingMaxConcurrency() const +{ + auto current_thread_size = worker_pool->size(); + auto val = settings.manual_compact_max_concurrency.get(); + return std::max(val, current_thread_size); +} + +} // namespace Management + +} // namespace DB diff --git a/dbms/src/Flash/Management/ManualCompact.h b/dbms/src/Flash/Management/ManualCompact.h new file mode 100644 index 00000000000..68e8d77cfc6 --- /dev/null +++ b/dbms/src/Flash/Management/ManualCompact.h @@ -0,0 +1,100 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. +#pragma once + +#include +#include +#include +#include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#ifdef __clang__ +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif +#include +#include +#pragma GCC diagnostic pop + +#include +#include +#include +#include +#include + +namespace DB +{ + +namespace Management +{ + +/// Serves manual compact requests. Notice that the "compact" term here has different meanings compared to +/// the word "compact" in the DeltaMerge Store. The "compact request" here refer to the "delta merge" process +/// (major compact), while the "compact" in delta merge store refers to the "compact delta layer" process +/// (minor compact). +/// This class is thread safe. Every public member function can be called without synchronization. +class ManualCompactManager : private boost::noncopyable +{ +public: + explicit ManualCompactManager(const Context & global_context_, const Settings & settings_); + + ~ManualCompactManager() = default; + + /// Handles a request in the worker pool and wait for the result. + grpc::Status handleRequest(const ::kvrpcpb::CompactRequest * request, ::kvrpcpb::CompactResponse * response); + +#ifndef DBMS_PUBLIC_GTEST +private: +#endif + /// Returns manual_compact_more_until_ms. + uint64_t getSettingCompactMoreUntilMs() const; + + /// Returns manual_compact_max_concurrency. + uint64_t getSettingMaxConcurrency() const; + + /// Process a single manual compact request, with exception handlers wrapped. + /// Should be called in the worker pool. + grpc::Status doWorkWithCatch(const ::kvrpcpb::CompactRequest * request, ::kvrpcpb::CompactResponse * response); + + /// Process a single manual compact request. + /// Should be called in the worker pool. + grpc::Status doWork(const ::kvrpcpb::CompactRequest * request, ::kvrpcpb::CompactResponse * response); + +#ifndef DBMS_PUBLIC_GTEST +private: +#endif + std::mutex mutex; + // == Accessing members below must be synchronized == + + /// When there is a task containing the same logical_table running, + /// the task will be rejected. + std::set unsync_active_logical_table_ids = {}; + + size_t unsync_running_or_pending_tasks = 0; + + // == Accessing members above must be synchronized == + +#ifndef DBMS_PUBLIC_GTEST +private: +#endif + const Context & global_context; + const Settings & settings; + Poco::Logger * log; + + /// Placed last to be destroyed first. + std::unique_ptr worker_pool; +}; + +} // namespace Management + +} // namespace DB diff --git a/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp b/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp new file mode 100644 index 00000000000..922ced2470c --- /dev/null +++ b/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp @@ -0,0 +1,390 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace DB +{ +namespace FailPoints +{ +extern const char pause_before_server_merge_one_delta[]; +} // namespace FailPoints + +namespace tests +{ + + +class BasicManualCompactTest + : public DB::base::TiFlashStorageTestBasic + , public testing::WithParamInterface +{ +public: + static constexpr TableID TABLE_ID = 5; + + BasicManualCompactTest() + { + log = &Poco::Logger::get(DB::base::TiFlashStorageTestBasic::getCurrentFullTestName()); + pk_type = GetParam(); + } + + void SetUp() override + { + try + { + TiFlashStorageTestBasic::SetUp(); + + manager = std::make_unique(*db_context, db_context->getSettingsRef()); + + setupStorage(); + + // In tests let's only compact one segment. + db_context->setSetting("manual_compact_more_until_ms", UInt64(0)); + + // Split into 4 segments, and prepare some delta data for first 3 segments. + helper = std::make_unique(*db_context); + helper->prepareSegments(storage->getAndMaybeInitStore(), 50, pk_type); + prepareDataForFirstThreeSegments(); + } + CATCH + } + + void setupStorage() + { + auto columns = DM::tests::DMTestEnv::getDefaultTableColumns(pk_type); + auto table_info = DM::tests::DMTestEnv::getMinimalTableInfo(TABLE_ID, pk_type); + auto astptr = DM::tests::DMTestEnv::getPrimaryKeyExpr("test_table", pk_type); + + storage = StorageDeltaMerge::create("TiFlash", + "default" /* db_name */, + "test_table" /* table_name */, + std::ref(table_info), + ColumnsDescription{columns}, + astptr, + 0, + db_context->getGlobalContext()); + storage->startup(); + } + + void prepareDataForFirstThreeSegments() + { + // Write data to first 3 segments. + auto newly_written_rows = helper->rows_by_segments[0] + helper->rows_by_segments[1] + helper->rows_by_segments[2]; + Block block = DM::tests::DMTestEnv::prepareSimpleWriteBlock(0, newly_written_rows, false, pk_type, 5 /* new tso */); + storage->write(block, db_context->getSettingsRef()); + storage->flushCache(*db_context); + + helper->expected_delta_rows[0] += helper->rows_by_segments[0]; + helper->expected_delta_rows[1] += helper->rows_by_segments[1]; + helper->expected_delta_rows[2] += helper->rows_by_segments[2]; + helper->verifyExpectedRowsForAllSegments(); + } + + void TearDown() override + { + storage->drop(); + db_context->getTMTContext().getStorages().remove(TABLE_ID); + } + +protected: + std::unique_ptr helper; + StorageDeltaMergePtr storage; + std::unique_ptr manager; + + DM::tests::DMTestEnv::PkType pk_type; + + [[maybe_unused]] Poco::Logger * log; +}; + + +INSTANTIATE_TEST_CASE_P( + ByCommonHandle, + BasicManualCompactTest, + testing::Values( + DM::tests::DMTestEnv::PkType::HiddenTiDBRowID, + DM::tests::DMTestEnv::PkType::CommonHandle, + DM::tests::DMTestEnv::PkType::PkIsHandleInt64), + [](const testing::TestParamInfo & info) { + return DM::tests::DMTestEnv::PkTypeToString(info.param); + }); + + +TEST_P(BasicManualCompactTest, EmptyRequest) +try +{ + auto request = ::kvrpcpb::CompactRequest(); + auto response = ::kvrpcpb::CompactResponse(); + auto status_code = manager->handleRequest(&request, &response); + ASSERT_EQ(status_code.error_code(), grpc::StatusCode::OK); + ASSERT_TRUE(response.has_error()); + ASSERT_TRUE(response.error().has_err_physical_table_not_exist()); +} +CATCH + + +TEST_P(BasicManualCompactTest, NonExistTableID) +try +{ + auto request = ::kvrpcpb::CompactRequest(); + request.set_physical_table_id(9999); + auto response = ::kvrpcpb::CompactResponse(); + auto status_code = manager->handleRequest(&request, &response); + ASSERT_EQ(status_code.error_code(), grpc::StatusCode::OK); + ASSERT_TRUE(response.has_error()); + ASSERT_TRUE(response.error().has_err_physical_table_not_exist()); +} +CATCH + + +TEST_P(BasicManualCompactTest, InvalidStartKey) +try +{ + auto request = ::kvrpcpb::CompactRequest(); + request.set_physical_table_id(TABLE_ID); + request.set_start_key("abcd"); + auto response = ::kvrpcpb::CompactResponse(); + auto status_code = manager->handleRequest(&request, &response); + ASSERT_EQ(status_code.error_code(), grpc::StatusCode::OK); + ASSERT_TRUE(response.has_error()); + ASSERT_TRUE(response.error().has_err_invalid_start_key()); +} +CATCH + + +TEST_P(BasicManualCompactTest, MalformedStartKey) +try +{ + // Specify an int key for common handle table, and vise versa. + DM::RowKeyValue malformed_start_key; + switch (pk_type) + { + case DM::tests::DMTestEnv::PkType::HiddenTiDBRowID: + case DM::tests::DMTestEnv::PkType::PkIsHandleInt64: + malformed_start_key = DM::RowKeyValue::COMMON_HANDLE_MIN_KEY; + break; + case DM::tests::DMTestEnv::PkType::CommonHandle: + malformed_start_key = DM::RowKeyValue::INT_HANDLE_MIN_KEY; + break; + default: + throw Exception("Unknown pk type for test"); + } + + auto request = ::kvrpcpb::CompactRequest(); + request.set_physical_table_id(TABLE_ID); + WriteBufferFromOwnString wb; + malformed_start_key.serialize(wb); + request.set_start_key(wb.releaseStr()); + auto response = ::kvrpcpb::CompactResponse(); + auto status_code = manager->handleRequest(&request, &response); + ASSERT_EQ(status_code.error_code(), grpc::StatusCode::OK); + ASSERT_TRUE(response.has_error()); + ASSERT_TRUE(response.error().has_err_invalid_start_key()); +} +CATCH + + +// Start key is not specified. Should compact the first segment. +TEST_P(BasicManualCompactTest, NoStartKey) +try +{ + auto request = ::kvrpcpb::CompactRequest(); + request.set_physical_table_id(TABLE_ID); + auto response = ::kvrpcpb::CompactResponse(); + auto status_code = manager->handleRequest(&request, &response); + ASSERT_EQ(status_code.error_code(), grpc::StatusCode::OK); + ASSERT_FALSE(response.has_error()); + ASSERT_TRUE(response.has_remaining()); + + helper->expected_stable_rows[0] += helper->expected_delta_rows[0]; + helper->expected_delta_rows[0] = 0; + helper->verifyExpectedRowsForAllSegments(); +} +CATCH + + +// Start key is empty. Should compact the first segment. +TEST_P(BasicManualCompactTest, EmptyStartKey) +try +{ + auto request = ::kvrpcpb::CompactRequest(); + request.set_physical_table_id(TABLE_ID); + request.set_start_key(""); + auto response = ::kvrpcpb::CompactResponse(); + auto status_code = manager->handleRequest(&request, &response); + ASSERT_EQ(status_code.error_code(), grpc::StatusCode::OK); + ASSERT_FALSE(response.has_error()); + ASSERT_TRUE(response.has_remaining()); + + helper->expected_stable_rows[0] += helper->expected_delta_rows[0]; + helper->expected_delta_rows[0] = 0; + helper->verifyExpectedRowsForAllSegments(); +} +CATCH + + +// Specify a key in segment[1]. Should compact this segment. +TEST_P(BasicManualCompactTest, SpecifiedStartKey) +try +{ + // TODO: This test may be not appropriate. It highly relies on internal implementation: + // The encoding of the start key should be hidden from the caller. + + auto request = ::kvrpcpb::CompactRequest(); + request.set_physical_table_id(TABLE_ID); + { + WriteBufferFromOwnString wb; + auto seg0 = storage->getAndMaybeInitStore()->segments.begin()->second; + auto seg1_start_key = seg0->getRowKeyRange().end; + seg1_start_key.toPrefixNext().serialize(wb); + request.set_start_key(wb.releaseStr()); + } + auto response = ::kvrpcpb::CompactResponse(); + auto status_code = manager->handleRequest(&request, &response); + ASSERT_EQ(status_code.error_code(), grpc::StatusCode::OK); + ASSERT_FALSE(response.has_error()); + ASSERT_TRUE(response.has_remaining()); + + helper->expected_stable_rows[1] += helper->expected_delta_rows[1]; + helper->expected_delta_rows[1] = 0; + helper->verifyExpectedRowsForAllSegments(); +} +CATCH + + +TEST_P(BasicManualCompactTest, StartKeyFromPreviousResponse) +try +{ + ::kvrpcpb::CompactResponse response; + { + // Request 1 + auto request = ::kvrpcpb::CompactRequest(); + request.set_physical_table_id(TABLE_ID); + response = ::kvrpcpb::CompactResponse(); + auto status_code = manager->handleRequest(&request, &response); + ASSERT_EQ(status_code.error_code(), grpc::StatusCode::OK); + ASSERT_FALSE(response.has_error()); + ASSERT_TRUE(response.has_remaining()); + + helper->expected_stable_rows[0] += helper->expected_delta_rows[0]; + helper->expected_delta_rows[0] = 0; + helper->verifyExpectedRowsForAllSegments(); + } + { + // Request 2, use the start key from previous response. We should compact both segment 1 and segment 2. + auto request = ::kvrpcpb::CompactRequest(); + request.set_physical_table_id(TABLE_ID); + request.set_start_key(response.compacted_end_key()); + response = ::kvrpcpb::CompactResponse(); + auto status_code = manager->handleRequest(&request, &response); + ASSERT_EQ(status_code.error_code(), grpc::StatusCode::OK); + ASSERT_FALSE(response.has_error()); + ASSERT_TRUE(response.has_remaining()); + + helper->expected_stable_rows[1] += helper->expected_delta_rows[1]; + helper->expected_delta_rows[1] = 0; + helper->verifyExpectedRowsForAllSegments(); + } +} +CATCH + + +TEST_P(BasicManualCompactTest, CompactMultiple) +try +{ + db_context->setSetting("manual_compact_more_until_ms", UInt64(60 * 1000)); // Hope it's long enough! + + auto request = ::kvrpcpb::CompactRequest(); + request.set_physical_table_id(TABLE_ID); + auto response = ::kvrpcpb::CompactResponse(); + auto status_code = manager->handleRequest(&request, &response); + ASSERT_EQ(status_code.error_code(), grpc::StatusCode::OK); + ASSERT_FALSE(response.has_error()); + ASSERT_FALSE(response.has_remaining()); + + // All segments should be compacted. + for (size_t i = 0; i < 4; ++i) + { + helper->expected_stable_rows[i] += helper->expected_delta_rows[i]; + helper->expected_delta_rows[i] = 0; + } + helper->verifyExpectedRowsForAllSegments(); +} +CATCH + + +// When there are duplicated logical id while processing, the later one should return error immediately. +TEST_P(BasicManualCompactTest, DuplicatedLogicalId) +try +{ + using namespace std::chrono_literals; + + FailPointHelper::enableFailPoint(FailPoints::pause_before_server_merge_one_delta); + + auto thread_1_is_ready = std::promise(); + std::thread t_req1([&]() { + // req1 + auto request = ::kvrpcpb::CompactRequest(); + request.set_physical_table_id(TABLE_ID); + request.set_logical_table_id(2); + auto response = ::kvrpcpb::CompactResponse(); + thread_1_is_ready.set_value(); + auto status_code = manager->handleRequest(&request, &response); + ASSERT_EQ(status_code.error_code(), grpc::StatusCode::OK); + ASSERT_FALSE(response.has_error()); + helper->expected_stable_rows[0] += helper->expected_delta_rows[0]; + helper->expected_delta_rows[0] = 0; + helper->verifyExpectedRowsForAllSegments(); + }); + + { + // send req1, wait request being processed. + thread_1_is_ready.get_future().wait(); + std::this_thread::sleep_for(500ms); // TODO: Maybe better to use sync_channel to avoid hardcoded wait duration. + + // req2: Now let's send another request with the same logical id. + // Although worker pool size is 1, this request will be returned immediately, but with an error. + auto request = ::kvrpcpb::CompactRequest(); + request.set_physical_table_id(TABLE_ID); + request.set_logical_table_id(2); + auto response = ::kvrpcpb::CompactResponse(); + auto status_code = manager->handleRequest(&request, &response); + ASSERT_EQ(status_code.error_code(), grpc::StatusCode::OK); + ASSERT_TRUE(response.has_error()); + ASSERT_TRUE(response.error().has_err_compact_in_progress()); + helper->verifyExpectedRowsForAllSegments(); + } + + // Now let's continue req1's work + FailPointHelper::disableFailPoint(FailPoints::pause_before_server_merge_one_delta); + t_req1.join(); +} +CATCH + + +} // namespace tests +} // namespace DB diff --git a/dbms/src/Flash/ServiceUtils.cpp b/dbms/src/Flash/ServiceUtils.cpp new file mode 100644 index 00000000000..b9d24923f4e --- /dev/null +++ b/dbms/src/Flash/ServiceUtils.cpp @@ -0,0 +1,40 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include + +namespace DB +{ +namespace ErrorCodes +{ +extern const int NOT_IMPLEMENTED; +extern const int UNKNOWN_USER; +extern const int WRONG_PASSWORD; +extern const int REQUIRED_PASSWORD; +extern const int IP_ADDRESS_NOT_ALLOWED; +} // namespace ErrorCodes + +grpc::StatusCode tiflashErrorCodeToGrpcStatusCode(int error_code) +{ + /// do not use switch statement because ErrorCodes::XXXX is not a compile time constant + if (error_code == ErrorCodes::NOT_IMPLEMENTED) + return grpc::StatusCode::UNIMPLEMENTED; + if (error_code == ErrorCodes::UNKNOWN_USER || error_code == ErrorCodes::WRONG_PASSWORD || error_code == ErrorCodes::REQUIRED_PASSWORD + || error_code == ErrorCodes::IP_ADDRESS_NOT_ALLOWED) + return grpc::StatusCode::UNAUTHENTICATED; + return grpc::StatusCode::INTERNAL; +} + +} // namespace DB \ No newline at end of file diff --git a/dbms/src/Flash/ServiceUtils.h b/dbms/src/Flash/ServiceUtils.h new file mode 100644 index 00000000000..9201c67165c --- /dev/null +++ b/dbms/src/Flash/ServiceUtils.h @@ -0,0 +1,24 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +#include + +namespace DB +{ + +grpc::StatusCode tiflashErrorCodeToGrpcStatusCode(int error_code); + +} // namespace DB \ No newline at end of file diff --git a/dbms/src/Interpreters/InterpreterManageQuery.cpp b/dbms/src/Interpreters/InterpreterManageQuery.cpp index d0969a8fade..6764fe61745 100644 --- a/dbms/src/Interpreters/InterpreterManageQuery.cpp +++ b/dbms/src/Interpreters/InterpreterManageQuery.cpp @@ -27,14 +27,14 @@ BlockIO InterpreterManageQuery::execute() const ASTManageQuery & ast = typeid_cast(*query_ptr); StoragePtr table = context.getTable(ast.database, ast.table); - IManageableStorage * manageable_storage; + StorageDeltaMerge * manageable_storage; if (table->getName() == MutableSupport::delta_tree_storage_name) { manageable_storage = &dynamic_cast(*table); } else { - throw Exception("Manage operation can only be applied to DeltaMerge engine tables"); + throw Exception("Manage operation can only be applied to DeltaTree engine tables"); } switch (ast.operation) diff --git a/dbms/src/Interpreters/Settings.h b/dbms/src/Interpreters/Settings.h index d08644bd83c..87d007c101f 100644 --- a/dbms/src/Interpreters/Settings.h +++ b/dbms/src/Interpreters/Settings.h @@ -360,7 +360,11 @@ struct Settings M(SettingBool, enable_async_server, true, "Enable async rpc server.") \ M(SettingUInt64, async_pollers_per_cq, 200, "grpc async pollers per cqs") \ M(SettingUInt64, async_cqs, 1, "grpc async cqs") \ - M(SettingUInt64, preallocated_request_count_per_poller, 20, "grpc preallocated_request_count_per_poller") + M(SettingUInt64, preallocated_request_count_per_poller, 20, "grpc preallocated_request_count_per_poller") \ + \ + M(SettingUInt64, manual_compact_pool_size, 1, "The number of worker threads to handle manual compact requests.") \ + M(SettingUInt64, manual_compact_max_concurrency, 10, "Max concurrent tasks. It should be larger than pool size.") \ + M(SettingUInt64, manual_compact_more_until_ms, 60000, "Continuously compact more segments until reaching specified elapsed time. If 0 is specified, only one segment will be compacted each round.") // clang-format on #define DECLARE(TYPE, NAME, DEFAULT, DESCRIPTION) TYPE NAME{DEFAULT}; diff --git a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp index f20239638b3..5583b0039b7 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp +++ b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp @@ -12,13 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include #include #include #include #include #include -#include #include #include #include @@ -31,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -84,6 +81,7 @@ extern const int LOGICAL_ERROR; namespace FailPoints { +extern const char skip_check_segment_update[]; extern const char pause_before_dt_background_delta_merge[]; extern const char pause_until_dt_background_delta_merge[]; extern const char pause_when_writing_to_dt_store[]; @@ -1028,6 +1026,40 @@ void DeltaMergeStore::mergeDeltaAll(const Context & context) } } +std::optional DeltaMergeStore::mergeDeltaBySegment(const Context & context, const RowKeyValue & start_key, const TaskRunThread run_thread) +{ + updateGCSafePoint(); + auto dm_context = newDMContext(context, context.getSettingsRef(), + /*tracing_id*/ fmt::format("mergeDeltaBySegment_{}", latest_gc_safe_point.load(std::memory_order_relaxed))); + + while (true) + { + SegmentPtr segment; + { + std::shared_lock lock(read_write_mutex); + const auto segment_it = segments.upper_bound(start_key.toRowKeyValueRef()); + if (segment_it == segments.end()) + { + return std::nullopt; + } + segment = segment_it->second; + } + + const auto new_segment = segmentMergeDelta(*dm_context, segment, run_thread); + if (new_segment) + { + const auto segment_end = new_segment->getRowKeyRange().end; + if (unlikely(*segment_end.value <= *start_key.value)) + { + // The next start key must be > current start key + LOG_FMT_ERROR(log, "Assert new_segment.end {} > start {} failed", segment_end.toDebugString(), start_key.toDebugString()); + throw Exception("Assert segment range failed", ErrorCodes::LOGICAL_ERROR); + } + return new_segment->getRowKeyRange(); + } + } +} + void DeltaMergeStore::compact(const Context & db_context, const RowKeyRange & range) { auto dm_context = newDMContext(db_context, db_context.getSettingsRef(), /*tracing_id*/ "compact"); @@ -1259,6 +1291,8 @@ void DeltaMergeStore::waitForDeleteRange(const DB::DM::DMContextPtr &, const DB: void DeltaMergeStore::checkSegmentUpdate(const DMContextPtr & dm_context, const SegmentPtr & segment, ThreadType thread_type) { + fiu_do_on(FailPoints::skip_check_segment_update, { return; }); + if (segment->hasAbandoned()) return; const auto & delta = segment->getDelta(); @@ -2076,6 +2110,9 @@ SegmentPtr DeltaMergeStore::segmentMergeDelta( case TaskRunThread::Foreground: GET_METRIC(tiflash_storage_subtask_count, type_delta_merge_fg).Increment(); break; + case TaskRunThread::ForegroundRPC: + GET_METRIC(tiflash_storage_subtask_count, type_delta_merge_fg_rpc).Increment(); + break; case TaskRunThread::BackgroundGCThread: GET_METRIC(tiflash_storage_subtask_count, type_delta_merge_bg_gc).Increment(); break; @@ -2093,6 +2130,9 @@ SegmentPtr DeltaMergeStore::segmentMergeDelta( case TaskRunThread::Foreground: GET_METRIC(tiflash_storage_subtask_duration_seconds, type_delta_merge_fg).Observe(watch_delta_merge.elapsedSeconds()); break; + case TaskRunThread::ForegroundRPC: + GET_METRIC(tiflash_storage_subtask_duration_seconds, type_delta_merge_fg_rpc).Observe(watch_delta_merge.elapsedSeconds()); + break; case TaskRunThread::BackgroundGCThread: GET_METRIC(tiflash_storage_subtask_duration_seconds, type_delta_merge_bg_gc).Observe(watch_delta_merge.elapsedSeconds()); break; diff --git a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h index a8c34073f50..705481ca107 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h +++ b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.h @@ -183,10 +183,12 @@ class DeltaMergeStore : private boost::noncopyable PlaceIndex, }; + // TODO: Rename to MergeDeltaThreadType enum TaskRunThread { BackgroundThreadPool, Foreground, + ForegroundRPC, BackgroundGCThread, }; @@ -379,6 +381,12 @@ class DeltaMergeStore : private boost::noncopyable /// This function is called when using `MANAGE TABLE [TABLE] MERGE DELTA` from TiFlash Client. void mergeDeltaAll(const Context & context); + /// Merge delta into the stable layer for one segment located by the specified start key. + /// Returns the range of the merged segment, which can be used to merge the remaining segments incrementally (new_start_key = old_end_key). + /// If there is no segment found by the start key, nullopt is returned. + /// + /// This function is called when using `ALTER TABLE [TABLE] COMPACT ...` from TiDB. + std::optional mergeDeltaBySegment(const Context & context, const DM::RowKeyValue & start_key, const TaskRunThread run_thread); /// Compact the delta layer, merging multiple fragmented delta files into larger ones. /// This is a minor compaction as it does not merge the delta into stable layer. diff --git a/dbms/src/Storages/DeltaMerge/RowKeyRange.h b/dbms/src/Storages/DeltaMerge/RowKeyRange.h index ef2afbfdb32..5b6d2db0210 100644 --- a/dbms/src/Storages/DeltaMerge/RowKeyRange.h +++ b/dbms/src/Storages/DeltaMerge/RowKeyRange.h @@ -164,6 +164,22 @@ struct RowKeyValue return RowKeyValue(is_common_handle, prefix_value, prefix_int_value); } + void serialize(WriteBuffer & buf) const + { + writeBoolText(is_common_handle, buf); + writeStringBinary(*value, buf); + } + + static RowKeyValue deserialize(ReadBuffer & buf) + { + bool is_common_handle; + String value; + readBoolText(is_common_handle, buf); + readStringBinary(value, buf); + HandleValuePtr start_ptr = std::make_shared(value); + return RowKeyValue(is_common_handle, start_ptr); + } + bool is_common_handle; /// In case of non common handle, the value field is redundant in most cases, except that int_value == Int64::max_value, /// because RowKeyValue is an end point of RowKeyRange, assuming that RowKeyRange = [start_value, end_value), since the diff --git a/dbms/src/Storages/DeltaMerge/tests/MultiSegmentTestUtil.h b/dbms/src/Storages/DeltaMerge/tests/MultiSegmentTestUtil.h new file mode 100644 index 00000000000..bc6b7d5c3e6 --- /dev/null +++ b/dbms/src/Storages/DeltaMerge/tests/MultiSegmentTestUtil.h @@ -0,0 +1,160 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dm_basic_include.h" + +namespace DB +{ +namespace FailPoints +{ +extern const char skip_check_segment_update[]; +} // namespace FailPoints + +namespace DM +{ +namespace tests +{ + +/// Helper class to test with multiple segments. +/// You can call `prepareSegments` to prepare multiple segments. After that, +/// you can use `verifyExpectedRowsForAllSegments` to verify the expectation for each segment. +class MultiSegmentTestUtil : private boost::noncopyable +{ +protected: + String tracing_id; + Context & db_context; + DeltaMergeStorePtr store; + +public: + std::map rows_by_segments; + std::map expected_stable_rows; + std::map expected_delta_rows; + + explicit MultiSegmentTestUtil(Context & db_context_) + : tracing_id(DB::base::TiFlashStorageTestBasic::getCurrentFullTestName()) + , db_context(db_context_) + { + FailPointHelper::enableFailPoint(FailPoints::skip_check_segment_update); + } + + ~MultiSegmentTestUtil() + { + FailPointHelper::disableFailPoint(FailPoints::skip_check_segment_update); + } + + /// Prepare segments * 4. The rows of each segment will be roughly close to n_avg_rows_per_segment. + /// The exact rows will be recorded in rows_by_segments. + void prepareSegments(DeltaMergeStorePtr store_, size_t n_avg_rows_per_segment, DMTestEnv::PkType pk_type) + { + store = store_; + + auto * log = &Poco::Logger::get(tracing_id); + auto dm_context = store->newDMContext(db_context, db_context.getSettingsRef(), /*tracing_id*/ tracing_id); + { + // Write [0, 4*N) data with tso=2. + Block block = DMTestEnv::prepareSimpleWriteBlock(0, n_avg_rows_per_segment * 4, false, pk_type, 2); + store->write(db_context, db_context.getSettingsRef(), block); + store->flushCache(dm_context, RowKeyRange::newAll(store->isCommonHandle(), store->getRowKeyColumnSize())); + } + { + // Check there is only one segment + ASSERT_EQ(store->segments.size(), 1); + const auto & [_key, seg] = *store->segments.begin(); + ASSERT_EQ(seg->getDelta()->getRows(), n_avg_rows_per_segment * 4); + ASSERT_EQ(seg->getStable()->getRows(), 0); + + // Split the segment, now we have two segments with n_rows_per_segment * 2 rows per segment. + forceForegroundSplit(0); + ASSERT_EQ(store->segments.size(), 2); + } + { + // Split the 2 segments again. + forceForegroundSplit(1); + forceForegroundSplit(0); + ASSERT_EQ(store->segments.size(), 4); + } + { + std::shared_lock lock(store->read_write_mutex); + // Now we have 4 segments. + auto total_stable_rows = 0; + auto segment_idx = 0; + for (auto & [_key, seg] : store->segments) + { + LOG_FMT_INFO(log, "Segment #{}: Range = {}", segment_idx, seg->getRowKeyRange().toDebugString()); + ASSERT_EQ(seg->getDelta()->getRows(), 0); + ASSERT_GT(seg->getStable()->getRows(), 0); // We don't check the exact rows of each segment. + total_stable_rows += seg->getStable()->getRows(); + rows_by_segments[segment_idx] = seg->getStable()->getRows(); + expected_stable_rows[segment_idx] = seg->getStable()->getRows(); + expected_delta_rows[segment_idx] = seg->getDelta()->getRows(); // = 0 + segment_idx++; + } + ASSERT_EQ(total_stable_rows, 4 * n_avg_rows_per_segment); + } + verifyExpectedRowsForAllSegments(); + } + + /// Retry until a segment at index is successfully split. + void forceForegroundSplit(size_t segment_idx) const + { + auto dm_context = store->newDMContext(db_context, db_context.getSettingsRef(), tracing_id); + while (true) + { + store->read_write_mutex.lock(); + auto seg = std::next(store->segments.begin(), segment_idx)->second; + store->read_write_mutex.unlock(); + auto result = store->segmentSplit(*dm_context, seg, /*is_foreground*/ true); + if (result.first) + { + break; + } + } + } + + /// Checks whether current rows in segments meets our expectation. + void verifyExpectedRowsForAllSegments() + { + std::shared_lock lock(store->read_write_mutex); + ASSERT_EQ(store->segments.size(), 4); + auto segment_idx = 0; + for (auto & [_key, seg] : store->segments) + { + ASSERT_EQ(seg->getDelta()->getRows(), expected_delta_rows[segment_idx]) << "Assert failed for segment #" << segment_idx; + ASSERT_EQ(seg->getStable()->getRows(), expected_stable_rows[segment_idx]) << "Assert failed for segment #" << segment_idx; + segment_idx++; + } + } +}; + +} // namespace tests +} // namespace DM +} // namespace DB \ No newline at end of file diff --git a/dbms/src/Storages/DeltaMerge/tests/dm_basic_include.h b/dbms/src/Storages/DeltaMerge/tests/dm_basic_include.h index 5554f269381..b35dae0cbe2 100644 --- a/dbms/src/Storages/DeltaMerge/tests/dm_basic_include.h +++ b/dbms/src/Storages/DeltaMerge/tests/dm_basic_include.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -107,6 +108,8 @@ class DMTestEnv static constexpr const char * PK_NAME_PK_IS_HANDLE = "id"; + static constexpr ColId PK_ID_PK_IS_HANDLE = 2; + enum class PkType { // If the primary key is composed of multiple columns and non-clustered-index, @@ -150,10 +153,10 @@ class DMTestEnv columns->emplace_back(getExtraHandleColumnDefine(/*is_common_handle=*/true)); break; case PkType::PkIsHandleInt64: - columns->emplace_back(ColumnDefine{2, PK_NAME_PK_IS_HANDLE, EXTRA_HANDLE_COLUMN_INT_TYPE}); + columns->emplace_back(ColumnDefine{PK_ID_PK_IS_HANDLE, PK_NAME_PK_IS_HANDLE, EXTRA_HANDLE_COLUMN_INT_TYPE}); break; case PkType::PkIsHandleInt32: - columns->emplace_back(ColumnDefine{2, PK_NAME_PK_IS_HANDLE, DataTypeFactory::instance().get("Int32")}); + columns->emplace_back(ColumnDefine{PK_ID_PK_IS_HANDLE, PK_NAME_PK_IS_HANDLE, DataTypeFactory::instance().get("Int32")}); break; default: throw Exception("Unknown pk type for test"); @@ -163,6 +166,96 @@ class DMTestEnv return columns; } + /// Returns a NamesAndTypesList that can be used to construct StorageDeltaMerge. + static NamesAndTypesList getDefaultTableColumns(PkType pk_type = PkType::HiddenTiDBRowID) + { + NamesAndTypesList columns; + switch (pk_type) + { + case PkType::HiddenTiDBRowID: + columns.push_back({EXTRA_HANDLE_COLUMN_NAME, EXTRA_HANDLE_COLUMN_INT_TYPE}); + break; + case PkType::CommonHandle: + columns.push_back({PK_NAME_PK_IS_HANDLE, EXTRA_HANDLE_COLUMN_STRING_TYPE}); // For common handle, there must be a user-given primary key. + columns.push_back({EXTRA_HANDLE_COLUMN_NAME, EXTRA_HANDLE_COLUMN_STRING_TYPE}); // For common handle, a _tidb_rowid is also constructed. + break; + case PkType::PkIsHandleInt64: + columns.emplace_back(PK_NAME_PK_IS_HANDLE, EXTRA_HANDLE_COLUMN_INT_TYPE); + break; + case PkType::PkIsHandleInt32: + throw Exception("PkIsHandleInt32 is unsupported"); + default: + throw Exception("Unknown pk type for test"); + } + return columns; + } + + /// Returns a TableInfo that can be used to construct StorageDeltaMerge. + static TiDB::TableInfo getMinimalTableInfo(TableID table_id, PkType pk_type = PkType::HiddenTiDBRowID) + { + TiDB::TableInfo table_info; + table_info.id = table_id; + switch (pk_type) + { + case PkType::HiddenTiDBRowID: + table_info.is_common_handle = false; + table_info.pk_is_handle = false; + break; + case PkType::CommonHandle: + { + table_info.is_common_handle = true; + table_info.pk_is_handle = false; + ColumnInfo pk_column; // For common handle, there must be a user-given primary key. + pk_column.id = PK_ID_PK_IS_HANDLE; + pk_column.name = PK_NAME_PK_IS_HANDLE; + pk_column.setPriKeyFlag(); + table_info.columns.push_back(pk_column); + break; + } + case PkType::PkIsHandleInt64: + { + table_info.is_common_handle = false; + table_info.pk_is_handle = true; + ColumnInfo pk_column; + pk_column.id = PK_ID_PK_IS_HANDLE; + pk_column.name = PK_NAME_PK_IS_HANDLE; + pk_column.setPriKeyFlag(); + table_info.columns.push_back(pk_column); + break; + } + case PkType::PkIsHandleInt32: + throw Exception("PkIsHandleInt32 is unsupported"); + default: + throw Exception("Unknown pk type for test"); + } + return table_info; + } + + /// Return a ASTPtr that can be used to construct StorageDeltaMerge. + static ASTPtr getPrimaryKeyExpr(const String & table_name, PkType pk_type = PkType::HiddenTiDBRowID) + { + ASTPtr astptr(new ASTIdentifier(table_name, ASTIdentifier::Kind::Table)); + String name; + switch (pk_type) + { + case PkType::HiddenTiDBRowID: + name = EXTRA_HANDLE_COLUMN_NAME; + break; + case PkType::CommonHandle: + name = EXTRA_HANDLE_COLUMN_NAME; + break; + case PkType::PkIsHandleInt64: + name = PK_NAME_PK_IS_HANDLE; + break; + case PkType::PkIsHandleInt32: + throw Exception("PkIsHandleInt32 is unsupported"); + default: + throw Exception("Unknown pk type for test"); + } + astptr->children.emplace_back(new ASTIdentifier(name)); + return astptr; + } + /** * Create a simple block with 3 columns: * * `pk` - Int64 / `version` / `tag` @@ -179,7 +272,8 @@ class DMTestEnv ColumnID pk_col_id = EXTRA_HANDLE_COLUMN_ID, DataTypePtr pk_type = EXTRA_HANDLE_COLUMN_INT_TYPE, bool is_common_handle = false, - size_t rowkey_column_size = 1) + size_t rowkey_column_size = 1, + bool with_internal_columns = true) { Block block; const size_t num_rows = (end - beg); @@ -221,19 +315,53 @@ class DMTestEnv EXTRA_HANDLE_COLUMN_ID}); } } - // version_col - block.insert(DB::tests::createColumn( - std::vector(num_rows, tso), - VERSION_COLUMN_NAME, - VERSION_COLUMN_ID)); - // tag_col - block.insert(DB::tests::createColumn( - std::vector(num_rows, 0), - TAG_COLUMN_NAME, - TAG_COLUMN_ID)); + if (with_internal_columns) + { + // version_col + block.insert(DB::tests::createColumn( + std::vector(num_rows, tso), + VERSION_COLUMN_NAME, + VERSION_COLUMN_ID)); + // tag_col + block.insert(DB::tests::createColumn( + std::vector(num_rows, 0), + TAG_COLUMN_NAME, + TAG_COLUMN_ID)); + } return block; } + /** + * Create a simple block with 3 columns: + * * `pk` - Int64 / `version` / `tag` + * @param beg `pk`'s value begin + * @param end `pk`'s value end (not included) + * @param reversed increasing/decreasing insert `pk`'s value + * @return + */ + static Block prepareSimpleWriteBlock(size_t beg, + size_t end, + bool reversed, + PkType pk_type, + UInt64 tso = 2, + bool with_internal_columns = true) + { + switch (pk_type) + { + case PkType::HiddenTiDBRowID: + return prepareSimpleWriteBlock(beg, end, reversed, tso, EXTRA_HANDLE_COLUMN_NAME, EXTRA_HANDLE_COLUMN_ID, EXTRA_HANDLE_COLUMN_INT_TYPE, false, 1, with_internal_columns); + case PkType::CommonHandle: + return prepareSimpleWriteBlock(beg, end, reversed, tso, EXTRA_HANDLE_COLUMN_NAME, EXTRA_HANDLE_COLUMN_ID, EXTRA_HANDLE_COLUMN_STRING_TYPE, true, 1, with_internal_columns); + case PkType::PkIsHandleInt64: + return prepareSimpleWriteBlock(beg, end, reversed, tso, PK_NAME_PK_IS_HANDLE, PK_ID_PK_IS_HANDLE, EXTRA_HANDLE_COLUMN_INT_TYPE, false, 1, with_internal_columns); + break; + case PkType::PkIsHandleInt32: + throw Exception("PkIsHandleInt32 is unsupported"); + default: + throw Exception("Unknown pk type for test"); + } + } + /** * Create a simple block with 3 columns: * * `pk` - Int64 / `version` / `tag` diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp index b759d1fe9ef..1d0e00a5b58 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp @@ -32,9 +32,11 @@ #include #include +#include #include #include +#include "MultiSegmentTestUtil.h" #include "dm_basic_include.h" namespace DB @@ -133,7 +135,7 @@ class DeltaMergeStoreRWTest , public testing::WithParamInterface { public: - void SetUp() override + DeltaMergeStoreRWTest() { mode = GetParam(); @@ -148,7 +150,10 @@ class DeltaMergeStoreRWTest setStorageFormat(2); break; } + } + void SetUp() override + { TiFlashStorageTestBasic::SetUp(); store = reload(); } @@ -341,7 +346,6 @@ try } CATCH - TEST_P(DeltaMergeStoreRWTest, SimpleWriteRead) try { @@ -3360,6 +3364,215 @@ INSTANTIATE_TEST_CASE_P( testing::Values(TestMode::V1_BlockOnly, TestMode::V2_BlockOnly, TestMode::V2_FileOnly, TestMode::V2_Mix), testModeToString); + +class DeltaMergeStoreMergeDeltaBySegmentTest + : public DB::base::TiFlashStorageTestBasic + , public testing::WithParamInterface> +{ +public: + DeltaMergeStoreMergeDeltaBySegmentTest() + { + log = &Poco::Logger::get(DB::base::TiFlashStorageTestBasic::getCurrentFullTestName()); + std::tie(ps_ver, pk_type) = GetParam(); + } + + void SetUp() override + { + try + { + setStorageFormat(ps_ver); + TiFlashStorageTestBasic::SetUp(); + + setupDMStore(); + + // Split into 4 segments. + helper = std::make_unique(*db_context); + helper->prepareSegments(store, 50, pk_type); + } + CATCH + } + + void setupDMStore() + { + auto cols = DMTestEnv::getDefaultColumns(pk_type); + store = std::make_shared(*db_context, + false, + "test", + DB::base::TiFlashStorageTestBasic::getCurrentFullTestName(), + 101, + *cols, + (*cols)[0], + pk_type == DMTestEnv::PkType::CommonHandle, + 1, + DeltaMergeStore::Settings()); + dm_context = store->newDMContext(*db_context, db_context->getSettingsRef(), DB::base::TiFlashStorageTestBasic::getCurrentFullTestName()); + } + +protected: + std::unique_ptr helper; + DeltaMergeStorePtr store; + DMContextPtr dm_context; + + UInt64 ps_ver; + DMTestEnv::PkType pk_type; + + [[maybe_unused]] Poco::Logger * log; +}; + +INSTANTIATE_TEST_CASE_P( + ByPsVerAndPkType, + DeltaMergeStoreMergeDeltaBySegmentTest, + ::testing::Combine( + ::testing::Values(2, 3), + ::testing::Values(DMTestEnv::PkType::HiddenTiDBRowID, DMTestEnv::PkType::CommonHandle, DMTestEnv::PkType::PkIsHandleInt64)), + [](const testing::TestParamInfo> & info) { + const auto [ps_ver, pk_type] = info.param; + return fmt::format("PsV{}_{}", ps_ver, DMTestEnv::PkTypeToString(pk_type)); + }); + + +// The given key is the boundary of the segment. +TEST_P(DeltaMergeStoreMergeDeltaBySegmentTest, BoundaryKey) +try +{ + { + // Write data to first 3 segments. + auto newly_written_rows = helper->rows_by_segments[0] + helper->rows_by_segments[1] + helper->rows_by_segments[2]; + Block block = DMTestEnv::prepareSimpleWriteBlock(0, newly_written_rows, false, pk_type, 5 /* new tso */); + store->write(*db_context, db_context->getSettingsRef(), block); + store->flushCache(dm_context, RowKeyRange::newAll(store->isCommonHandle(), store->getRowKeyColumnSize())); + + helper->expected_delta_rows[0] += helper->rows_by_segments[0]; + helper->expected_delta_rows[1] += helper->rows_by_segments[1]; + helper->expected_delta_rows[2] += helper->rows_by_segments[2]; + helper->verifyExpectedRowsForAllSegments(); + } + if (store->isCommonHandle()) + { + // Specifies MAX_KEY. nullopt should be returned. + auto result = store->mergeDeltaBySegment(*db_context, RowKeyValue::COMMON_HANDLE_MAX_KEY, DeltaMergeStore::TaskRunThread::Foreground); + ASSERT_EQ(result, std::nullopt); + } + else + { + // Specifies MAX_KEY. nullopt should be returned. + auto result = store->mergeDeltaBySegment(*db_context, RowKeyValue::INT_HANDLE_MAX_KEY, DeltaMergeStore::TaskRunThread::Foreground); + ASSERT_EQ(result, std::nullopt); + } + std::optional result_1; + { + // Specifies MIN_KEY. In this case, the first segment should be processed. + if (store->isCommonHandle()) + { + result_1 = store->mergeDeltaBySegment(*db_context, RowKeyValue::COMMON_HANDLE_MIN_KEY, DeltaMergeStore::TaskRunThread::Foreground); + } + else + { + result_1 = store->mergeDeltaBySegment(*db_context, RowKeyValue::INT_HANDLE_MIN_KEY, DeltaMergeStore::TaskRunThread::Foreground); + } + // The returned range is the same as first segment's range. + ASSERT_NE(result_1, std::nullopt); + ASSERT_EQ(*result_1, store->segments.begin()->second->getRowKeyRange()); + + helper->expected_stable_rows[0] += helper->expected_delta_rows[0]; + helper->expected_delta_rows[0] = 0; + helper->verifyExpectedRowsForAllSegments(); + } + { + // Compact the first segment again, nothing should change. + auto result = store->mergeDeltaBySegment(*db_context, result_1->start, DeltaMergeStore::TaskRunThread::Foreground); + ASSERT_EQ(*result, *result_1); + + helper->verifyExpectedRowsForAllSegments(); + } + std::optional result_2; + { + // Compact again using the end key just returned. The second segment should be processed. + result_2 = store->mergeDeltaBySegment(*db_context, result_1->end, DeltaMergeStore::TaskRunThread::Foreground); + ASSERT_NE(result_2, std::nullopt); + ASSERT_EQ(*result_2, std::next(store->segments.begin())->second->getRowKeyRange()); + + helper->expected_stable_rows[1] += helper->expected_delta_rows[1]; + helper->expected_delta_rows[1] = 0; + helper->verifyExpectedRowsForAllSegments(); + } +} +CATCH + +TEST_P(DeltaMergeStoreMergeDeltaBySegmentTest, InvalidKey) +{ + // Expect exceptions when invalid key is given. + EXPECT_ANY_THROW({ + if (store->isCommonHandle()) + { + // For common handle, give int handle key and have a try + store->mergeDeltaBySegment(*db_context, RowKeyValue::INT_HANDLE_MIN_KEY, DeltaMergeStore::TaskRunThread::Foreground); + } + else + { + // For int handle, give common handle key and have a try + store->mergeDeltaBySegment(*db_context, RowKeyValue::COMMON_HANDLE_MIN_KEY, DeltaMergeStore::TaskRunThread::Foreground); + } + }); +} + + +// Give the last segment key. +TEST_P(DeltaMergeStoreMergeDeltaBySegmentTest, LastSegment) +try +{ + std::optional result; + { + auto it = std::next(store->segments.begin(), 3); + ASSERT_NE(it, store->segments.end()); + auto seg = it->second; + + result = store->mergeDeltaBySegment(*db_context, seg->getRowKeyRange().start, DeltaMergeStore::TaskRunThread::Foreground); + ASSERT_NE(result, std::nullopt); + helper->verifyExpectedRowsForAllSegments(); + } + { + // As we are the last segment, compact "next segment" should result in failure. A nullopt is returned. + auto result2 = store->mergeDeltaBySegment(*db_context, result->end, DeltaMergeStore::TaskRunThread::Foreground); + ASSERT_EQ(result2, std::nullopt); + helper->verifyExpectedRowsForAllSegments(); + } +} +CATCH + + +// The given key is not the boundary of the segment. +TEST_P(DeltaMergeStoreMergeDeltaBySegmentTest, NonBoundaryKey) +try +{ + { + // Write data to first 3 segments. + auto newly_written_rows = helper->rows_by_segments[0] + helper->rows_by_segments[1] + helper->rows_by_segments[2]; + Block block = DMTestEnv::prepareSimpleWriteBlock(0, newly_written_rows, false, pk_type, 5 /* new tso */); + store->write(*db_context, db_context->getSettingsRef(), block); + store->flushCache(dm_context, RowKeyRange::newAll(store->isCommonHandle(), store->getRowKeyColumnSize())); + + helper->expected_delta_rows[0] += helper->rows_by_segments[0]; + helper->expected_delta_rows[1] += helper->rows_by_segments[1]; + helper->expected_delta_rows[2] += helper->rows_by_segments[2]; + helper->verifyExpectedRowsForAllSegments(); + } + { + // Compact segment[1] by giving a prefix-next key. + auto range = std::next(store->segments.begin())->second->getRowKeyRange(); + auto compact_key = range.start.toPrefixNext(); + + auto result = store->mergeDeltaBySegment(*db_context, compact_key, DeltaMergeStore::TaskRunThread::Foreground); + ASSERT_NE(result, std::nullopt); + + helper->expected_stable_rows[1] += helper->expected_delta_rows[1]; + helper->expected_delta_rows[1] = 0; + helper->verifyExpectedRowsForAllSegments(); + } +} +CATCH + + } // namespace tests } // namespace DM } // namespace DB diff --git a/dbms/src/Storages/IManageableStorage.h b/dbms/src/Storages/IManageableStorage.h index 23dc4cc447a..e41d092ca87 100644 --- a/dbms/src/Storages/IManageableStorage.h +++ b/dbms/src/Storages/IManageableStorage.h @@ -82,10 +82,6 @@ class IManageableStorage : public IStorage /// Return true is data dir exist virtual bool initStoreIfDataDirExist() { throw Exception("Unsupported"); } - virtual void mergeDelta(const Context &) { throw Exception("Unsupported"); } - - virtual BlockInputStreamPtr listSegments(const Context &) { throw Exception("Unsupported"); } - virtual ::TiDB::StorageEngine engineType() const = 0; virtual String getDatabaseName() const = 0; diff --git a/dbms/src/Storages/StorageDeltaMerge.cpp b/dbms/src/Storages/StorageDeltaMerge.cpp index c61842e4353..38a947a027f 100644 --- a/dbms/src/Storages/StorageDeltaMerge.cpp +++ b/dbms/src/Storages/StorageDeltaMerge.cpp @@ -88,8 +88,6 @@ StorageDeltaMerge::StorageDeltaMerge( if (primary_expr_ast_->children.empty()) throw Exception("No primary key"); - is_common_handle = false; - pk_is_handle = false; // save schema from TiDB if (table_info_) { @@ -790,6 +788,11 @@ void StorageDeltaMerge::mergeDelta(const Context & context) getAndMaybeInitStore()->mergeDeltaAll(context); } +std::optional StorageDeltaMerge::mergeDeltaBySegment(const Context & context, const DM::RowKeyValue & start_key, const DM::DeltaMergeStore::TaskRunThread run_thread) +{ + return getAndMaybeInitStore()->mergeDeltaBySegment(context, start_key, run_thread); +} + void StorageDeltaMerge::deleteRange(const DM::RowKeyRange & range_to_delete, const Settings & settings) { GET_METRIC(tiflash_storage_command_count, type_delete_range).Increment(); diff --git a/dbms/src/Storages/StorageDeltaMerge.h b/dbms/src/Storages/StorageDeltaMerge.h index b9598b77a86..e304c713b7b 100644 --- a/dbms/src/Storages/StorageDeltaMerge.h +++ b/dbms/src/Storages/StorageDeltaMerge.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -77,7 +78,14 @@ class StorageDeltaMerge /// Merge delta into the stable layer for all segments. /// /// This function is called when using `MANAGE TABLE [TABLE] MERGE DELTA` from TiFlash Client. - void mergeDelta(const Context & context) override; + void mergeDelta(const Context & context); + + /// Merge delta into the stable layer for one segment located by the specified start key. + /// Returns the range of the merged segment, which can be used to merge the remaining segments incrementally (new_start_key = old_end_key). + /// If there is no segment found by the start key, nullopt is returned. + /// + /// This function is called when using `ALTER TABLE [TABLE] COMPACT ...` from TiDB. + std::optional mergeDeltaBySegment(const Context & context, const DM::RowKeyValue & start_key, const DM::DeltaMergeStore::TaskRunThread run_thread); void deleteRange(const DM::RowKeyRange & range_to_delete, const Settings & settings); @@ -220,9 +228,9 @@ class StorageDeltaMerge DM::DeltaMergeStorePtr _store; Strings pk_column_names; // TODO: remove it. Only use for debug from ch-client. - bool is_common_handle; - bool pk_is_handle; - size_t rowkey_column_size; + bool is_common_handle = false; + bool pk_is_handle = false; + size_t rowkey_column_size = 0; OrderedNameSet hidden_columns; // The table schema synced from TiDB diff --git a/etc/config-template.toml b/etc/config-template.toml index 8805c5b5bc2..cad45dc8105 100644 --- a/etc/config-template.toml +++ b/etc/config-template.toml @@ -145,21 +145,38 @@ ## The memory usage limit for the generated intermediate data when a single ## coprocessor query is executed. The default value is 0, which means no limit. # max_memory_usage = 0 + ## The memory usage limit for the generated intermediate data when all queries ## are executed. The default value is 0 (in bytes), which means no limit. # max_memory_usage_for_all_queries = 0 -## New in v5.0. This item specifies the maximum number of cop requests that TiFlash Coprocessor executes at the same time. If the number of requests exceeds the specified value, the exceeded requests will queue. If the configuration value is set to 0 or not set, the default value is used, which is twice the number of physical cores. + +## New in v5.0. This item specifies the maximum number of cop requests that TiFlash +## Coprocessor executes at the same time. If the number of requests exceeds the specified value, +## the exceeded requests will queue. If the configuration value is set to 0 or not set, +## the default value is used, which is twice the number of physical cores. # cop_pool_size = 0 -## New in v5.0. This item specifies the maximum number of batch requests that TiFlash Coprocessor executes at the same time. If the number of requests exceeds the specified value, the exceeded requests will queue. If the configuration value is set to 0 or not set, the default value is used, which is twice the number of physical cores. + +## New in v5.0. This item specifies the maximum number of batch requests that TiFlash +## Coprocessor executes at the same time. If the number of requests exceeds the specified value, +## the exceeded requests will queue. If the configuration value is set to 0 or not set, +## the default value is used, which is twice the number of physical cores. # batch_cop_pool_size = 0 + +## New in v6.1. +## Specifies the maximum number of manual compact requests executes at the same time. +## If value is not set, or set to 0, the default value (1) will be used. +# manual_compact_pool_size = 1 + ## The default value is false. This parameter determines whether the segment ## of DeltaTree Storage Engine uses logical split. ## Using the logical split can reduce the write amplification, and improve the write speed. ## However, these are at the cost of disk space waste. # dt_enable_logical_split = false -# Compression algorithm of DeltaTree Storage Engine + +## Compression algorithm of DeltaTree Storage Engine # dt_compression_method = lz4 -# Compression level of DeltaTree Storage Engine + +## Compression level of DeltaTree Storage Engine # dt_compression_level = 1 ## Security settings take effect starting from v4.0.5. From 8971ba8aec3f5af6b0e613c4e393c2e5f4563241 Mon Sep 17 00:00:00 2001 From: Zhi Qi <30543181+LittleFall@users.noreply.github.com> Date: Thu, 19 May 2022 02:30:38 +0800 Subject: [PATCH 040/127] feat: Support scalar function `from_days` and `to_days` (#4905) close pingcap/tiflash#4678 --- dbms/src/Common/MyTime.cpp | 15 ++- dbms/src/Common/MyTime.h | 1 + dbms/src/Flash/Coprocessor/DAGUtils.cpp | 4 +- dbms/src/Functions/FunctionsDateTime.cpp | 120 ++++++++++++++++- dbms/src/Functions/FunctionsDateTime.h | 37 ++++++ dbms/src/Functions/tests/gtest_from_days.cpp | 130 +++++++++++++++++++ dbms/src/Functions/tests/gtest_to_days.cpp | 99 ++++++++++++++ tests/fullstack-test/expr/from_days.test | 59 +++++++++ 8 files changed, 459 insertions(+), 6 deletions(-) create mode 100644 dbms/src/Functions/tests/gtest_from_days.cpp create mode 100644 dbms/src/Functions/tests/gtest_to_days.cpp create mode 100644 tests/fullstack-test/expr/from_days.test diff --git a/dbms/src/Common/MyTime.cpp b/dbms/src/Common/MyTime.cpp index 420709ec129..95168e52180 100644 --- a/dbms/src/Common/MyTime.cpp +++ b/dbms/src/Common/MyTime.cpp @@ -876,7 +876,7 @@ String MyDateTime::toString(int fsp) const //TODO: we can use modern c++ api instead. MyDateTime MyDateTime::getSystemDateTimeByTimezone(const TimezoneInfo & timezoneInfo, UInt8 fsp) { - struct timespec ts; + struct timespec ts; // NOLINT(cppcoreguidelines-pro-type-member-init) clock_gettime(CLOCK_REALTIME, &ts); time_t second = ts.tv_sec; @@ -1202,6 +1202,11 @@ void fromDayNum(MyDateTime & t, int day_num) // the day number of the last 100 years should be DAY_NUM_PER_100_YEARS + 1 // so can not use day_num % DAY_NUM_PER_100_YEARS day_num = day_num - (num_of_100_years * DAY_NUM_PER_100_YEARS); + if (num_of_100_years == 4) + { + num_of_100_years = 3; + day_num = DAY_NUM_PER_100_YEARS; + } int num_of_4_years = day_num / DAY_NUM_PER_4_YEARS; // can not use day_num % DAY_NUM_PER_4_YEARS @@ -1211,6 +1216,12 @@ void fromDayNum(MyDateTime & t, int day_num) // can not use day_num % DAY_NUM_PER_YEARS day_num = day_num - (num_of_years * DAY_NUM_PER_YEARS); + if (num_of_years == 4) + { + num_of_years = 3; + day_num = DAY_NUM_PER_YEARS; + } + year = 1 + num_of_400_years * 400 + num_of_100_years * 100 + num_of_4_years * 4 + num_of_years; } static const int ACCUMULATED_DAYS_PER_MONTH[] = {30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364}; @@ -1246,7 +1257,7 @@ void addMonths(MyDateTime & t, Int64 months) Int64 current_month = t.month - 1; current_month += months; Int64 current_year = 0; - Int64 year = static_cast(t.year); + auto year = static_cast(t.year); if (current_month >= 0) { current_year = current_month / 12; diff --git a/dbms/src/Common/MyTime.h b/dbms/src/Common/MyTime.h index ecbaed60445..8f8ffb85df3 100644 --- a/dbms/src/Common/MyTime.h +++ b/dbms/src/Common/MyTime.h @@ -195,6 +195,7 @@ MyDateTime convertUTC2TimeZoneByOffset(time_t utc_ts, UInt32 micro_second, Int64 std::pair roundTimeByFsp(time_t second, UInt64 nano_second, UInt8 fsp); int calcDayNum(int year, int month, int day); +void fromDayNum(MyDateTime & t, int day_num); // returns seconds since '0000-00-00' UInt64 calcSeconds(int year, int month, int day, int hour, int minute, int second); diff --git a/dbms/src/Flash/Coprocessor/DAGUtils.cpp b/dbms/src/Flash/Coprocessor/DAGUtils.cpp index 4ae6672a619..66f5d7031d7 100644 --- a/dbms/src/Flash/Coprocessor/DAGUtils.cpp +++ b/dbms/src/Flash/Coprocessor/DAGUtils.cpp @@ -564,7 +564,7 @@ const std::unordered_map scalar_func_map({ //{tipb::ScalarFuncSig::SecToTime, "cast"}, //{tipb::ScalarFuncSig::TimeToSec, "cast"}, //{tipb::ScalarFuncSig::TimestampAdd, "cast"}, - //{tipb::ScalarFuncSig::ToDays, "cast"}, + {tipb::ScalarFuncSig::ToDays, "tidbToDays"}, {tipb::ScalarFuncSig::ToSeconds, "tidbToSeconds"}, //{tipb::ScalarFuncSig::UTCTimeWithArg, "cast"}, //{tipb::ScalarFuncSig::UTCTimestampWithoutArg, "cast"}, @@ -599,7 +599,7 @@ const std::unordered_map scalar_func_map({ //{tipb::ScalarFuncSig::SubDateDatetimeString, "cast"}, {tipb::ScalarFuncSig::SubDateDatetimeInt, "date_sub"}, - //{tipb::ScalarFuncSig::FromDays, "cast"}, + {tipb::ScalarFuncSig::FromDays, "tidbFromDays"}, //{tipb::ScalarFuncSig::TimeFormat, "cast"}, {tipb::ScalarFuncSig::TimestampDiff, "tidbTimestampDiff"}, diff --git a/dbms/src/Functions/FunctionsDateTime.cpp b/dbms/src/Functions/FunctionsDateTime.cpp index 112a8050b4d..dd072a00f76 100644 --- a/dbms/src/Functions/FunctionsDateTime.cpp +++ b/dbms/src/Functions/FunctionsDateTime.cpp @@ -43,7 +43,7 @@ std::string extractTimeZoneNameFromFunctionArguments(const ColumnsWithTypeAndNam return {}; /// If time zone is attached to an argument of type DateTime. - if (const DataTypeDateTime * type = checkAndGetDataType(arguments[datetime_arg_num].type.get())) + if (const auto * type = checkAndGetDataType(arguments[datetime_arg_num].type.get())) return type->getTimeZone().getTimeZone(); return {}; @@ -60,13 +60,127 @@ const DateLUTImpl & extractTimeZoneFromFunctionArguments(Block & block, const Co return DateLUT::instance(); /// If time zone is attached to an argument of type DateTime. - if (const DataTypeDateTime * type = checkAndGetDataType(block.getByPosition(arguments[datetime_arg_num]).type.get())) + if (const auto * type = checkAndGetDataType(block.getByPosition(arguments[datetime_arg_num]).type.get())) return type->getTimeZone(); return DateLUT::instance(); } } +class FunctionTiDBFromDays : public IFunction +{ +public: + static constexpr auto name = "tidbFromDays"; + + explicit FunctionTiDBFromDays(const Context &) {} + + static FunctionPtr create(const Context & context) { return std::make_shared(context); } + + String getName() const override { return name; } + bool isVariadic() const override { return false; } + size_t getNumberOfArguments() const override { return 1; } + bool useDefaultImplementationForNulls() const override { return false; } + bool useDefaultImplementationForConstants() const override { return true; } + + DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override + { + const auto * arg = removeNullable(arguments[0]).get(); + if (!arg->isInteger()) + throw Exception( + fmt::format("Illegal argument type {} of function {}, should be integer", arg->getName(), getName()), + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + return std::make_shared(std::make_shared()); + } + + template + void dispatch(Block & block, const ColumnNumbers & arguments, const size_t result) const + { + size_t rows = block.rows(); + + auto col_to = ColumnVector::create(rows); + auto & vec_to = col_to->getData(); + auto result_null_map = ColumnUInt8::create(rows, 0); + ColumnUInt8::Container & vec_result_null_map = result_null_map->getData(); + + auto col_from = block.getByPosition(arguments[0]).column; + if (block.getByPosition(arguments[0]).type->isNullable()) + { + Block temporary_block = createBlockWithNestedColumns(block, arguments, result); + col_from = temporary_block.getByPosition(arguments[0]).column; + } + const auto & vec_from = checkAndGetColumn>(col_from.get())->getData(); + + for (size_t i = 0; i < rows; ++i) + { + try + { + IntType val = vec_from[i]; + MyDateTime date(0); + if (val >= 0) + { + fromDayNum(date, val); + } + vec_to[i] = date.toPackedUInt(); + } + catch (const Exception &) + { + vec_result_null_map[i] = 1; + } + } + + if (block.getByPosition(arguments[0]).type->isNullable()) + { + auto column = block.getByPosition(arguments[0]).column; + for (size_t i = 0; i < rows; i++) + { + vec_result_null_map[i] |= column->isNullAt(i); + } + } + block.getByPosition(result).column = ColumnNullable::create(std::move(col_to), std::move(result_null_map)); + } + + void executeImpl(Block & block, const ColumnNumbers & arguments, const size_t result) const override + { + if (block.getByPosition(arguments[0]).type->onlyNull()) + { + block.getByPosition(result).column = block.getByPosition(result).type->createColumnConst(block.rows(), Null()); + return; + } + + auto type_index = removeNullable(block.getByPosition(arguments[0]).type)->getTypeId(); + switch (type_index) + { + case TypeIndex::UInt8: + dispatch(block, arguments, result); + break; + case TypeIndex::UInt16: + dispatch(block, arguments, result); + break; + case TypeIndex::UInt32: + dispatch(block, arguments, result); + break; + case TypeIndex::UInt64: + dispatch(block, arguments, result); + break; + case TypeIndex::Int8: + dispatch(block, arguments, result); + break; + case TypeIndex::Int16: + dispatch(block, arguments, result); + break; + case TypeIndex::Int32: + dispatch(block, arguments, result); + break; + case TypeIndex::Int64: + dispatch(block, arguments, result); + break; + default: + throw Exception(fmt::format("argument type of {} is invalid, expect integer, got {}", getName(), type_index), ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + }; + } +}; + void registerFunctionsDateTime(FunctionFactory & factory) { factory.registerFunction>(); @@ -138,6 +252,8 @@ void registerFunctionsDateTime(FunctionFactory & factory) factory.registerFunction(); factory.registerFunction(); factory.registerFunction(); + factory.registerFunction(); + factory.registerFunction(); factory.registerFunction(); factory.registerFunction(); diff --git a/dbms/src/Functions/FunctionsDateTime.h b/dbms/src/Functions/FunctionsDateTime.h index 9ae4eeaaad9..df579a1bab8 100644 --- a/dbms/src/Functions/FunctionsDateTime.h +++ b/dbms/src/Functions/FunctionsDateTime.h @@ -3313,6 +3313,42 @@ struct TiDBToSecondsTransformerImpl } }; +template +struct TiDBToDaysTransformerImpl +{ + static constexpr auto name = "tidbToDays"; + + static void execute(const Context & context, + const ColumnVector::Container & vec_from, + typename ColumnVector::Container & vec_to, + typename ColumnVector::Container & vec_null_map) + { + bool is_null = false; + for (size_t i = 0; i < vec_from.size(); ++i) + { + MyTimeBase val(vec_from[i]); + vec_to[i] = execute(context, val, is_null); + vec_null_map[i] = is_null; + is_null = false; + } + } + + static ToFieldType execute(const Context & context, const MyTimeBase & val, bool & is_null) + { + // TiDB returns normal value if one of month/day is zero for to_seconds function, while MySQL return null if either of them is zero. + // TiFlash aligns with MySQL to align the behavior with other functions like last_day. + if (val.month == 0 || val.day == 0) + { + context.getDAGContext()->handleInvalidTime( + fmt::format("Invalid time value: month({}) or day({}) is zero", val.month, val.day), + Errors::Types::WrongValue); + is_null = true; + return 0; + } + return static_cast(calcDayNum(val.year, val.month, val.day)); + } +}; + // Similar to FunctionDateOrDateTimeToSomething, but also handle nullable result and mysql sql mode. template class Transformer, bool return_nullable> class FunctionMyDateOrMyDateTimeToSomething : public IFunction @@ -3413,6 +3449,7 @@ using FunctionToTiDBDayOfWeek = FunctionMyDateOrMyDateTimeToSomething; using FunctionToTiDBWeekOfYear = FunctionMyDateOrMyDateTimeToSomething; using FunctionToTiDBToSeconds = FunctionMyDateOrMyDateTimeToSomething; +using FunctionToTiDBToDays = FunctionMyDateOrMyDateTimeToSomething; using FunctionToRelativeYearNum = FunctionDateOrDateTimeToSomething; using FunctionToRelativeQuarterNum = FunctionDateOrDateTimeToSomething; diff --git a/dbms/src/Functions/tests/gtest_from_days.cpp b/dbms/src/Functions/tests/gtest_from_days.cpp new file mode 100644 index 00000000000..cff51346bf5 --- /dev/null +++ b/dbms/src/Functions/tests/gtest_from_days.cpp @@ -0,0 +1,130 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include +#include +#include + +#include +#include + +namespace DB::tests +{ +class TestFromDays : public DB::tests::FunctionTest +{ +}; + +TEST_F(TestFromDays, TestAll) +try +{ + DAGContext * dag_context = context.getDAGContext(); + UInt64 ori_flags = dag_context->getFlags(); + dag_context->addFlag(TiDBSQLFlags::TRUNCATE_AS_WARNING); + /// ColumnVector(nullable) + const String func_name = "tidbFromDays"; + static auto const nullable_date_type_ptr = makeNullable(std::make_shared()); + auto data_col_ptr = createColumn>( + { + {}, // Null + 0, // Zero date + MyDate(1969, 1, 2).toPackedUInt(), + MyDate(2000, 12, 31).toPackedUInt(), + MyDate(2022, 3, 13).toPackedUInt(), + }) + .column; + auto output_col = ColumnWithTypeAndName(data_col_ptr, nullable_date_type_ptr, "input"); + auto input_col = createColumn>({{}, 1, 719164, 730850, 738592}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + + /// ColumnVector(non-null) + data_col_ptr = createColumn>( + { + MyDate(0, 0, 0).toPackedUInt(), + MyDate(1969, 1, 2).toPackedUInt(), + MyDate(2000, 12, 31).toPackedUInt(), + MyDate(2022, 3, 13).toPackedUInt(), + }) + .column; + output_col = ColumnWithTypeAndName(data_col_ptr, nullable_date_type_ptr, "input"); + input_col = createColumn({1, 719164, 730850, 738592}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + + /// ColumnConst(non-null) + output_col = ColumnWithTypeAndName(createConstColumn>(1, MyDate(2022, 3, 13).toPackedUInt()).column, nullable_date_type_ptr, "input"); + input_col = createConstColumn(1, {738592}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + + /// ColumnConst(nullable) + output_col = ColumnWithTypeAndName(createConstColumn>(1, MyDate(2022, 3, 13).toPackedUInt()).column, nullable_date_type_ptr, "input"); + input_col = createConstColumn>(1, {738592}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + + /// ColumnConst(nullable(null)) + output_col = ColumnWithTypeAndName(createConstColumn>(1, {}).column, nullable_date_type_ptr, "input"); + input_col = createConstColumn>(1, {}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + dag_context->setFlags(ori_flags); +} +CATCH + +TEST_F(TestFromDays, ConversionFromDaysAndDate) +try +{ + ASSERT_EQ(calcDayNum(9999, 12, 31), 3652424); + for (int i = 366; i <= 3652424; ++i) + { + MyDateTime tmp(0); + fromDayNum(tmp, i); + int calced_daynum = calcDayNum(tmp.year, tmp.month, tmp.day); + ASSERT_EQ(i, calced_daynum); + } +} +CATCH + +TEST_F(TestFromDays, ConversionFromDateAndDays) +try +{ + auto is_valid_date = [](int year, int month, int day) { + bool is_leap_year = year % 400 == 0 || (year % 4 == 0 && year % 100 != 0); + constexpr int max_day_for_year[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + constexpr int max_day_for_leap_year[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + if (is_leap_year) + { + return max_day_for_leap_year[month - 1] >= day; + } + else + { + return max_day_for_year[month - 1] >= day; + } + }; + + for (int year = 1; year <= 2037; year++) + for (int month = 1; month <= 12; month++) + for (int day = 1; day <= 31; day++) + { + if (is_valid_date(year, month, day)) + { + auto day_num = calcDayNum(year, month, day); + MyDateTime t(0); + fromDayNum(t, day_num); + ASSERT_EQ(t.year, year); + ASSERT_EQ(t.month, month); + ASSERT_EQ(t.day, day); + } + } +} +CATCH + +} // namespace DB::tests diff --git a/dbms/src/Functions/tests/gtest_to_days.cpp b/dbms/src/Functions/tests/gtest_to_days.cpp new file mode 100644 index 00000000000..2857106ed75 --- /dev/null +++ b/dbms/src/Functions/tests/gtest_to_days.cpp @@ -0,0 +1,99 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include +#include +#include + +#include +#include + +namespace DB::tests +{ +class TestToDays : public DB::tests::FunctionTest +{ +}; + +TEST_F(TestToDays, TestAll) +try +{ + DAGContext * dag_context = context.getDAGContext(); + UInt64 ori_flags = dag_context->getFlags(); + dag_context->addFlag(TiDBSQLFlags::TRUNCATE_AS_WARNING); + /// ColumnVector(nullable) + const String func_name = "tidbToDays"; + static auto const nullable_datetime_type_ptr = makeNullable(std::make_shared(6)); + static auto const datetime_type_ptr = std::make_shared(6); + static auto const date_type_ptr = std::make_shared(); + auto data_col_ptr = createColumn>( + { + {}, // Null + MyDateTime(0, 0, 0, 0, 0, 0, 0).toPackedUInt(), + MyDateTime(0, 1, 1, 0, 0, 0, 0).toPackedUInt(), + MyDateTime(1969, 1, 2, 1, 1, 1, 1).toPackedUInt(), + MyDateTime(2000, 12, 31, 10, 10, 10, 700).toPackedUInt(), + MyDateTime(2022, 3, 13, 6, 7, 8, 9).toPackedUInt(), + }) + .column; + auto input_col = ColumnWithTypeAndName(data_col_ptr, nullable_datetime_type_ptr, "input"); + auto output_col = createColumn>({{}, {}, 1, 719164, 730850, 738592}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + + /// ColumnVector(non-null) + data_col_ptr = createColumn( + { + MyDateTime(0, 0, 0, 0, 0, 0, 0).toPackedUInt(), + MyDateTime(1969, 1, 2, 1, 1, 1, 1).toPackedUInt(), + MyDateTime(2000, 12, 31, 10, 10, 10, 700).toPackedUInt(), + MyDateTime(2022, 3, 13, 6, 7, 8, 9).toPackedUInt(), + }) + .column; + input_col = ColumnWithTypeAndName(data_col_ptr, datetime_type_ptr, "input"); + output_col = createColumn>({{}, 719164, 730850, 738592}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + + /// ColumnConst(non-null) + input_col = ColumnWithTypeAndName(createConstColumn(1, MyDateTime(2022, 3, 13, 6, 7, 8, 9).toPackedUInt()).column, datetime_type_ptr, "input"); + output_col = createConstColumn>(1, {738592}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + + /// ColumnConst(nullable) + input_col = ColumnWithTypeAndName(createConstColumn>(1, MyDateTime(2022, 3, 13, 6, 7, 8, 9).toPackedUInt()).column, nullable_datetime_type_ptr, "input"); + output_col = createConstColumn>(1, {738592}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + + /// ColumnConst(nullable(null)) + input_col = ColumnWithTypeAndName(createConstColumn>(1, {}).column, nullable_datetime_type_ptr, "input"); + output_col = createConstColumn>(1, {}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + + /// MyDate ColumnVector(non-null) + data_col_ptr = createColumn( + { + MyDate(0000, 0, 1).toPackedUInt(), + MyDate(0000, 1, 1).toPackedUInt(), + MyDate(1969, 1, 1).toPackedUInt(), + MyDate(2000, 12, 1).toPackedUInt(), + MyDate(2022, 3, 14).toPackedUInt(), + }) + .column; + input_col = ColumnWithTypeAndName(data_col_ptr, date_type_ptr, "input"); + output_col = createColumn>({{}, 1, 719163, 730820, 738593}); + ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); + dag_context->setFlags(ori_flags); +} +CATCH + +} // namespace DB::tests diff --git a/tests/fullstack-test/expr/from_days.test b/tests/fullstack-test/expr/from_days.test new file mode 100644 index 00000000000..bdc032afc9d --- /dev/null +++ b/tests/fullstack-test/expr/from_days.test @@ -0,0 +1,59 @@ +# Copyright 2022 PingCAP, Ltd. +# +# 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. + +mysql> drop table if exists test.t; +mysql> create table test.t (a int); +mysql> insert into test.t values (null),(-100),(0),(1),(365),(366),(367),(730850); +mysql> insert into test.t values (3652424),(3652425),(3652499),(3652500),(3652501); +mysql> alter table test.t set tiflash replica 1; +func> wait_table test t + +# we are compaitable with mysql +mysql> set tidb_enforce_mpp=1; select a, from_days(a), to_days(from_days(a)) from test.t; ++---------+--------------+-----------------------+ +| a | from_days(a) | to_days(from_days(a)) | ++---------+--------------+-----------------------+ +| NULL | NULL | NULL | +| -100 | 0000-00-00 | NULL | +| 0 | 0000-00-00 | NULL | +| 1 | 0000-00-00 | NULL | +| 365 | 0000-00-00 | NULL | +| 366 | 0001-01-01 | 366 | +| 367 | 0001-01-02 | 367 | +| 730850 | 2000-12-31 | 730850 | +| 3652424 | 9999-12-31 | 3652424 | +| 3652425 | NULL | NULL | +| 3652499 | NULL | NULL | +| 3652500 | NULL | NULL | +| 3652501 | NULL | NULL | ++---------+--------------+-----------------------+ + +mysql> set tidb_allow_mpp=0; select a, from_days(a), to_days(from_days(a)) from test.t; ++---------+--------------+-----------------------+ +| a | from_days(a) | to_days(from_days(a)) | ++---------+--------------+-----------------------+ +| NULL | NULL | NULL | +| -100 | 0000-00-00 | NULL | +| 0 | 0000-00-00 | NULL | +| 1 | 0000-00-00 | NULL | +| 365 | 0000-00-00 | NULL | +| 366 | 0001-01-01 | 366 | +| 367 | 0001-01-02 | 367 | +| 730850 | 2000-12-31 | 730850 | +| 3652424 | 9999-12-31 | 3652424 | +| 3652425 | 10000-01-01 | 3652425 | +| 3652499 | 10000-03-15 | 3652499 | +| 3652500 | 0000-00-00 | NULL | +| 3652501 | 0000-00-00 | NULL | ++---------+--------------+-----------------------+ From 52d00c3b86f7ab73c871434511df6745b9c97f05 Mon Sep 17 00:00:00 2001 From: xufei Date: Thu, 19 May 2022 08:14:38 +0800 Subject: [PATCH 041/127] fix bug of weekofyear (#4927) close pingcap/tiflash#4926 --- dbms/src/Common/MyTime.cpp | 2 +- dbms/src/Functions/tests/gtest_weekofyear.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dbms/src/Common/MyTime.cpp b/dbms/src/Common/MyTime.cpp index 95168e52180..d0425364d0d 100644 --- a/dbms/src/Common/MyTime.cpp +++ b/dbms/src/Common/MyTime.cpp @@ -429,7 +429,7 @@ std::tuple MyTimeBase::calcWeek(UInt32 mode) const if (week_year && days >= 52 * 7) { - week_day = (week_day + calcDaysInYear(year)) % 7; + week_day = (week_day + calcDaysInYear(ret_year)) % 7; if ((!first_week_day && week_day < 4) || (first_week_day && week_day == 0)) { ret_year++; diff --git a/dbms/src/Functions/tests/gtest_weekofyear.cpp b/dbms/src/Functions/tests/gtest_weekofyear.cpp index 6a7a4c122d5..73479c8a3fe 100644 --- a/dbms/src/Functions/tests/gtest_weekofyear.cpp +++ b/dbms/src/Functions/tests/gtest_weekofyear.cpp @@ -49,6 +49,7 @@ try MyDateTime(0, 1, 3, 0, 0, 0, 0).toPackedUInt(), MyDateTime(1969, 1, 1, 1, 1, 1, 1).toPackedUInt(), MyDateTime(1969, 1, 6, 1, 1, 1, 1).toPackedUInt(), + MyDateTime(2001, 1, 2, 1, 1, 1, 1).toPackedUInt(), MyDateTime(2022, 4, 28, 6, 7, 8, 9).toPackedUInt(), MyDateTime(2022, 5, 2, 9, 8, 7, 6).toPackedUInt(), MyDateTime(2022, 5, 9, 9, 8, 7, 6).toPackedUInt(), @@ -57,7 +58,7 @@ try }) .column; auto input_col = ColumnWithTypeAndName(data_col_ptr, nullable_datetime_type_ptr, "input"); - auto output_col = createColumn>({{}, {}, 52, 1, 1, 1, 2, 17, 18, 19, 52, 53}); + auto output_col = createColumn>({{}, {}, 52, 1, 1, 1, 2, 1, 17, 18, 19, 52, 53}); ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); /// ColumnVector(non-null) From aedee1c46d639fc2cb112fb1916bebd099172ff7 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Thu, 19 May 2022 09:04:38 +0800 Subject: [PATCH 042/127] Fix pageutils will truncate the file if write failed (#4925) ref pingcap/tiflash#3594 --- dbms/src/Storages/Page/PageUtil.h | 21 +++++++++++++------ dbms/src/Storages/Page/V2/PageFile.cpp | 14 ++++++++++--- .../Page/V2/tests/gtest_page_util.cpp | 10 ++++----- dbms/src/Storages/Page/V3/BlobFile.cpp | 4 ++-- dbms/src/Storages/Page/V3/BlobStore.cpp | 2 +- .../Storages/Page/V3/LogFile/LogWriter.cpp | 9 +++++++- 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/dbms/src/Storages/Page/PageUtil.h b/dbms/src/Storages/Page/PageUtil.h index 7edae303be7..cebcbdb27f2 100644 --- a/dbms/src/Storages/Page/PageUtil.h +++ b/dbms/src/Storages/Page/PageUtil.h @@ -152,7 +152,9 @@ void ftruncateFile(T & file, off_t length) DB::throwFromErrno(fmt::format("Cannot truncate file: {}. ", file->getFileName()), ErrorCodes::CANNOT_FTRUNCATE); } - +// TODO: split current api into V2 and V3. +// Too many args in this function. +// Also split read template void writeFile( T & file, @@ -161,6 +163,7 @@ void writeFile( size_t to_write, const WriteLimiterPtr & write_limiter = nullptr, const bool background = false, + const bool truncate_if_failed = true, [[maybe_unused]] bool enable_failpoint = false) { if (write_limiter) @@ -192,15 +195,21 @@ void writeFile( { ProfileEvents::increment(ProfileEvents::PSMWriteFailed); auto saved_errno = errno; - // If error occurs, apply `ftruncate` try to truncate the broken bytes we have written. - // Note that the result of this ftruncate is ignored, there is nothing we can do to - // handle ftruncate error. The errno may change after ftruncate called. - int truncate_res = ::ftruncate(file->getFd(), offset); + + int truncate_res = 0; + // If write failed in V3, Don't do truncate + if (truncate_if_failed) + { + // If error occurs, apply `ftruncate` try to truncate the broken bytes we have written. + // Note that the result of this ftruncate is ignored, there is nothing we can do to + // handle ftruncate error. The errno may change after ftruncate called. + truncate_res = ::ftruncate(file->getFd(), offset); + } DB::throwFromErrno(fmt::format("Cannot write to file {},[truncate_res = {}],[errno_after_truncate = {}]," "[bytes_written={},to_write={},offset = {}]", file->getFileName(), - truncate_res, + truncate_if_failed ? DB::toString(truncate_res) : "no need truncate", strerror(errno), bytes_written, to_write, diff --git a/dbms/src/Storages/Page/V2/PageFile.cpp b/dbms/src/Storages/Page/V2/PageFile.cpp index 5f2ef7c26e0..b6cff9bda2c 100644 --- a/dbms/src/Storages/Page/V2/PageFile.cpp +++ b/dbms/src/Storages/Page/V2/PageFile.cpp @@ -411,7 +411,7 @@ bool PageFile::LinkingMetaAdapter::linkToNewSequenceNext(WriteBatch::SequenceID if (binary_version == PageFormat::V2) { - const UInt64 num_fields = PageUtil::get(pos); + const auto num_fields = PageUtil::get(pos); entry.field_offsets.reserve(num_fields); for (size_t i = 0; i < num_fields; ++i) { @@ -649,7 +649,7 @@ void PageFile::MetaMergingReader::moveNext(PageFormat::Version * v) if (binary_version == PageFormat::V2) { - const UInt64 num_fields = PageUtil::get(pos); + const auto num_fields = PageUtil::get(pos); entry.field_offsets.reserve(num_fields); for (size_t i = 0; i < num_fields; ++i) { @@ -792,7 +792,15 @@ size_t PageFile::Writer::write(DB::WriteBatch & wb, PageEntriesEdit & edit, cons SCOPE_EXIT({ page_file.free(data_buf.begin(), data_buf.size()); }); auto write_buf = [&](WritableFilePtr & file, UInt64 offset, ByteBuffer buf, bool enable_failpoint) { - PageUtil::writeFile(file, offset, buf.begin(), buf.size(), write_limiter, background, enable_failpoint); + PageUtil::writeFile( + file, + offset, + buf.begin(), + buf.size(), + write_limiter, + background, + /*truncate_if_failed=*/true, + /*enable_failpoint=*/enable_failpoint); if (sync_on_write) PageUtil::syncFile(file); }; diff --git a/dbms/src/Storages/Page/V2/tests/gtest_page_util.cpp b/dbms/src/Storages/Page/V2/tests/gtest_page_util.cpp index 8f55a67e9f2..e72c7a87541 100644 --- a/dbms/src/Storages/Page/V2/tests/gtest_page_util.cpp +++ b/dbms/src/Storages/Page/V2/tests/gtest_page_util.cpp @@ -29,7 +29,7 @@ namespace tests { static const std::string FileName = "page_util_test"; -TEST(PageUtils_test, ReadWriteFile) +TEST(PageUtilsTest, ReadWriteFile) { ::remove(FileName.c_str()); @@ -41,7 +41,7 @@ TEST(PageUtils_test, ReadWriteFile) buff_write[i] = i % 0xFF; } WritableFilePtr file_for_write = std::make_shared(FileName, true, -1, 0666); - PageUtil::writeFile(file_for_write, 0, buff_write, buff_size, /*write_limiter*/ nullptr, /*background*/ false, /*enable_failpoint*/ true); + PageUtil::writeFile(file_for_write, 0, buff_write, buff_size, /*write_limiter*/ nullptr, /*background*/ false, /*truncate_if_failed*/ true, /*enable_failpoint*/ true); PageUtil::syncFile(file_for_write); file_for_write->close(); @@ -53,7 +53,7 @@ TEST(PageUtils_test, ReadWriteFile) ::remove(FileName.c_str()); } -TEST(PageUtils_test, FileNotExists) +TEST(PageUtilsTest, FileNotExists) { ::remove(FileName.c_str()); @@ -61,7 +61,7 @@ TEST(PageUtils_test, FileNotExists) ASSERT_EQ(fd, 0); } -TEST(PageUtils_test, BigReadWriteFile) +TEST(PageUtilsTest, BigReadWriteFile) { ::remove(FileName.c_str()); @@ -78,7 +78,7 @@ TEST(PageUtils_test, BigReadWriteFile) buff_write[i] = i % 0xFF; } - PageUtil::writeFile(file_for_write, 0, buff_write, buff_size, nullptr, /*background*/ false, /*enable_failpoint*/ false); + PageUtil::writeFile(file_for_write, 0, buff_write, buff_size, nullptr, /*background*/ false, /*truncate_if_failed*/ true, /*enable_failpoint*/ false); PageUtil::syncFile(file_for_write); file_for_write->close(); diff --git a/dbms/src/Storages/Page/V3/BlobFile.cpp b/dbms/src/Storages/Page/V3/BlobFile.cpp index dd45e981c38..b018150c605 100644 --- a/dbms/src/Storages/Page/V3/BlobFile.cpp +++ b/dbms/src/Storages/Page/V3/BlobFile.cpp @@ -91,9 +91,9 @@ void BlobFile::write(char * buffer, size_t offset, size_t size, const WriteLimit }); #ifndef NDEBUG - PageUtil::writeFile(wrfile, offset, buffer, size, write_limiter, background, true); + PageUtil::writeFile(wrfile, offset, buffer, size, write_limiter, background, /*truncate_if_failed=*/false, /*enable_failpoint=*/true); #else - PageUtil::writeFile(wrfile, offset, buffer, size, write_limiter, background, false); + PageUtil::writeFile(wrfile, offset, buffer, size, write_limiter, background, /*truncate_if_failed=*/false, /*enable_failpoint=*/false); #endif PageUtil::syncFile(wrfile); diff --git a/dbms/src/Storages/Page/V3/BlobStore.cpp b/dbms/src/Storages/Page/V3/BlobStore.cpp index 3ed1d1a69b1..5f50d85e369 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.cpp +++ b/dbms/src/Storages/Page/V3/BlobStore.cpp @@ -342,7 +342,7 @@ PageEntriesEdit BlobStore::write(DB::WriteBatch & wb, const WriteLimiterPtr & wr catch (DB::Exception & e) { removePosFromStats(blob_id, offset_in_file, actually_allocated_size); - LOG_FMT_ERROR(log, "[blob_id={}] [offset_in_file={}] [size={}] [actually_allocated_size={}] write failed.", blob_id, offset_in_file, all_page_data_size, actually_allocated_size); + LOG_FMT_ERROR(log, "[blob_id={}] [offset_in_file={}] [size={}] [actually_allocated_size={}] write failed [error={}]", blob_id, offset_in_file, all_page_data_size, actually_allocated_size, e.message()); throw e; } diff --git a/dbms/src/Storages/Page/V3/LogFile/LogWriter.cpp b/dbms/src/Storages/Page/V3/LogFile/LogWriter.cpp index c9dda390569..2954172cb21 100644 --- a/dbms/src/Storages/Page/V3/LogFile/LogWriter.cpp +++ b/dbms/src/Storages/Page/V3/LogFile/LogWriter.cpp @@ -69,7 +69,14 @@ size_t LogWriter::writtenBytes() const void LogWriter::flush(const WriteLimiterPtr & write_limiter, const bool background) { - PageUtil::writeFile(log_file, written_bytes, write_buffer.buffer().begin(), write_buffer.offset(), write_limiter, /*background*/ background, /*enable_failpoint*/ false); + PageUtil::writeFile(log_file, + written_bytes, + write_buffer.buffer().begin(), + write_buffer.offset(), + write_limiter, + /*background=*/background, + /*truncate_if_failed=*/false, + /*enable_failpoint=*/false); log_file->fsync(); written_bytes += write_buffer.offset(); From bb572eba5d4cf4db000861b7ccfb70ac32df11a9 Mon Sep 17 00:00:00 2001 From: Calvin Neo Date: Thu, 19 May 2022 12:00:38 +0800 Subject: [PATCH 043/127] update tiflash proxy for fix of pprof (#4931) ref pingcap/tiflash#4618 --- contrib/tiflash-proxy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tiflash-proxy b/contrib/tiflash-proxy index df26083dd55..4c10d3bcf95 160000 --- a/contrib/tiflash-proxy +++ b/contrib/tiflash-proxy @@ -1 +1 @@ -Subproject commit df26083dd55211ec63ce028513a391652d1e035d +Subproject commit 4c10d3bcf95b288d6bb12acdf1872f41004c512e From 7860ec34219d6d4833e71a20735cbdf04954a0dd Mon Sep 17 00:00:00 2001 From: Aolin Date: Thu, 19 May 2022 20:20:39 +0800 Subject: [PATCH 044/127] docs: add TiDB Cloud in README.md (#4935) close pingcap/tiflash#4936 --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4876ccce1ec..a70279f6f9f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,24 @@ # TiFlash + ![tiflash-architecture](tiflash-architecture.png) -[TiFlash](https://docs.pingcap.com/tidb/stable/tiflash-overview) is a columnar storage component of [TiDB](https://docs.pingcap.com/tidb/stable). It mainly plays the role of Analytical Processing (AP) in the Hybrid Transactional/Analytical Processing (HTAP) architecture of TiDB. +[TiFlash](https://docs.pingcap.com/tidb/stable/tiflash-overview) is a columnar storage component of [TiDB](https://docs.pingcap.com/tidb/stable) and [TiDB Cloud](https://en.pingcap.com/tidb-cloud/), the fully-managed service of TiDB. It mainly plays the role of Analytical Processing (AP) in the Hybrid Transactional/Analytical Processing (HTAP) architecture of TiDB. TiFlash stores data in columnar format and synchronizes data updates in real-time from [TiKV](https://github.com/tikv/tikv) by Raft logs with sub-second latency. Reads in TiFlash are guaranteed transactionally consistent with Snapshot Isolation level. TiFlash utilizes Massively Parallel Processing (MPP) computing architecture to accelerate the analytical workloads. -TiFlash repository is based on [ClickHouse](https://github.com/ClickHouse/ClickHouse). We appreciate the excellent work of ClickHouse team. +TiFlash repository is based on [ClickHouse](https://github.com/ClickHouse/ClickHouse). We appreciate the excellent work of the ClickHouse team. + +## Quick Start + +### Start with TiDB Cloud + +Quickly explore TiFlash with [a free trial of TiDB Cloud](https://tidbcloud.com/signup). + +See [TiDB Cloud Quick Start Guide](https://docs.pingcap.com/tidbcloud/tidb-cloud-quickstart). + +### Start with TiDB + +See [Quick Start with HTAP](https://docs.pingcap.com/tidb/stable/quick-start-with-htap) and [Use TiFlash](https://docs.pingcap.com/tidb/stable/use-tiflash). ## Build TiFlash From f2513bbc861200b1b168cd58619753a78327b78c Mon Sep 17 00:00:00 2001 From: Wenxuan Date: Thu, 19 May 2022 20:56:40 +0800 Subject: [PATCH 045/127] docs: git clone concurrently (#4930) ref pingcap/tiflash#4019 --- README.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index a70279f6f9f..3bca0aa6597 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,14 @@ See [Quick Start with HTAP](https://docs.pingcap.com/tidb/stable/quick-start-wit ## Build TiFlash TiFlash supports building on the following hardware architectures: -* x86-64/amd64 -* aarch64 + +- x86-64/amd64 +- aarch64 And the following operating systems: -* Linux -* MacOS + +- Linux +- MacOS ### 1. Checkout Source Code @@ -36,7 +38,7 @@ Assume `$WORKSPACE` to be the directory under which the TiFlash repo is placed. ```shell cd $WORKSPACE -git clone --recursive https://github.com/pingcap/tiflash.git +git clone https://github.com/pingcap/tiflash.git --recursive -j 20 ``` ### 2. Prepare Prerequisites @@ -57,11 +59,13 @@ The following packages are needed for all platforms: - Ninja or GNU Make The following are platform-specific prerequisites. Click to expand details: +
Linux specific prerequisites - TiFlash can be built using either LLVM or GCC toolchain on Linux. LLVM toolchain is our official one for releasing. - > But for GCC, only GCC 7.x is supported as far, and is not planned to be a long term support. So it may get broken some day, silently. +TiFlash can be built using either LLVM or GCC toolchain on Linux. LLVM toolchain is our official one for releasing. + +> But for GCC, only GCC 7.x is supported as far, and is not planned to be a long term support. So it may get broken some day, silently. - LLVM 13.0.0+ @@ -69,7 +73,6 @@ The following are platform-specific prerequisites. Click to expand details: Click sections below to see detailed instructions: -
Set up LLVM via package managers in Debian/Ubuntu @@ -225,13 +228,13 @@ To generate unit test executables with sanitizer enabled: cd $BUILD cmake $WORKSPACE/tiflash -GNinja -DENABLE_TESTS=ON -DCMAKE_BUILD_TYPE=ASan # or TSan ninja gtests_dbms -ninja gtests_libcommon ninja gtests_libdaemon +ninja gtests_libcommon ``` There are known false positives reported from leak sanitizer (which is included in address sanitizer). To suppress these errors, set the following environment variables before running the executables: -``` +```shell LSAN_OPTIONS=suppressions=$WORKSPACE/tiflash/test/sanitize/asan.suppression ``` From 952cb0a878973c867b4a94c33799e1283e8d5fa6 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Fri, 20 May 2022 01:12:39 +0800 Subject: [PATCH 046/127] Fix ref a del id failed in mix mode. (#4933) close pingcap/tiflash#4923 --- .../DeltaMerge/ColumnFile/ColumnFileBig.cpp | 10 +- .../DeltaMerge/ColumnFile/ColumnFileBig.h | 12 +- .../Delta/ColumnFilePersistedSet.cpp | 9 +- .../DeltaMerge/Delta/ColumnFilePersistedSet.h | 4 +- .../Storages/DeltaMerge/Delta/MemTableSet.cpp | 9 +- .../Storages/DeltaMerge/DeltaMergeStore.cpp | 12 +- dbms/src/Storages/DeltaMerge/File/DMFile.cpp | 6 +- dbms/src/Storages/DeltaMerge/File/DMFile.h | 18 ++- dbms/src/Storages/DeltaMerge/Segment.cpp | 21 +-- .../Storages/DeltaMerge/StableValueSpace.cpp | 12 +- dbms/src/Storages/DeltaMerge/StoragePool.cpp | 102 ++++++++++++++- dbms/src/Storages/DeltaMerge/StoragePool.h | 1 + .../DeltaMerge/tests/gtest_dm_file.cpp | 4 +- dbms/src/Storages/Page/PageStorage.cpp | 120 +++++++++++------- dbms/src/Storages/Page/PageStorage.h | 3 +- .../Page/V2/VersionSet/PageEntriesView.h | 6 +- dbms/src/Storages/Page/V3/PageDirectory.h | 1 + dbms/src/Storages/Page/V3/PageStorageImpl.cpp | 4 +- .../Page/V3/tests/gtest_page_storage.cpp | 28 ++++ .../V3/tests/gtest_page_storage_mix_mode.cpp | 79 ++++++++++++ 20 files changed, 358 insertions(+), 103 deletions(-) diff --git a/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileBig.cpp b/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileBig.cpp index 306f9470f5d..3328f60b15d 100644 --- a/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileBig.cpp +++ b/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileBig.cpp @@ -56,7 +56,7 @@ ColumnFileBig::getReader(const DMContext & context, const StorageSnapshotPtr & / void ColumnFileBig::serializeMetadata(WriteBuffer & buf, bool /*save_schema*/) const { - writeIntBinary(file->refId(), buf); + writeIntBinary(file->pageId(), buf); writeIntBinary(valid_rows, buf); writeIntBinary(valid_bytes, buf); } @@ -65,17 +65,17 @@ ColumnFilePersistedPtr ColumnFileBig::deserializeMetadata(DMContext & context, / const RowKeyRange & segment_range, ReadBuffer & buf) { - UInt64 file_ref_id; + UInt64 file_page_id; size_t valid_rows, valid_bytes; - readIntBinary(file_ref_id, buf); + readIntBinary(file_page_id, buf); readIntBinary(valid_rows, buf); readIntBinary(valid_bytes, buf); - auto file_id = context.storage_pool.dataReader()->getNormalPageId(file_ref_id); + auto file_id = context.storage_pool.dataReader()->getNormalPageId(file_page_id); auto file_parent_path = context.path_pool.getStableDiskDelegator().getDTFilePath(file_id); - auto dmfile = DMFile::restore(context.db_context.getFileProvider(), file_id, file_ref_id, file_parent_path, DMFile::ReadMetaMode::all()); + auto dmfile = DMFile::restore(context.db_context.getFileProvider(), file_id, file_page_id, file_parent_path, DMFile::ReadMetaMode::all()); auto * dp_file = new ColumnFileBig(dmfile, valid_rows, valid_bytes, segment_range); return std::shared_ptr(dp_file); diff --git a/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileBig.h b/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileBig.h index ced90112392..dba7eca7247 100644 --- a/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileBig.h +++ b/dbms/src/Storages/DeltaMerge/ColumnFile/ColumnFileBig.h @@ -33,8 +33,8 @@ class ColumnFileBig : public ColumnFilePersisted private: DMFilePtr file; - size_t valid_rows; - size_t valid_bytes; + size_t valid_rows = 0; + size_t valid_bytes = 0; RowKeyRange segment_range; @@ -67,17 +67,17 @@ class ColumnFileBig : public ColumnFilePersisted auto getFile() const { return file; } - PageId getDataPageId() { return file->refId(); } + PageId getDataPageId() { return file->pageId(); } size_t getRows() const override { return valid_rows; } size_t getBytes() const override { return valid_bytes; }; void removeData(WriteBatches & wbs) const override { - // Here we remove the ref id instead of file_id. - // Because a dmfile could be used in serveral places, and only after all ref_ids are removed, + // Here we remove the data id instead of file_id. + // Because a dmfile could be used in several places, and only after all page ids are removed, // then the file_id got removed. - wbs.removed_data.delPage(file->refId()); + wbs.removed_data.delPage(file->pageId()); } ColumnFileReaderPtr diff --git a/dbms/src/Storages/DeltaMerge/Delta/ColumnFilePersistedSet.cpp b/dbms/src/Storages/DeltaMerge/Delta/ColumnFilePersistedSet.cpp index 289caf5816c..33ef262d557 100644 --- a/dbms/src/Storages/DeltaMerge/Delta/ColumnFilePersistedSet.cpp +++ b/dbms/src/Storages/DeltaMerge/Delta/ColumnFilePersistedSet.cpp @@ -216,11 +216,14 @@ ColumnFilePersisteds ColumnFilePersistedSet::checkHeadAndCloneTail(DMContext & c else if (auto * b_file = column_file->tryToBigFile(); b_file) { auto delegator = context.path_pool.getStableDiskDelegator(); - auto new_ref_id = context.storage_pool.newDataPageIdForDTFile(delegator, __PRETTY_FUNCTION__); + auto new_page_id = context.storage_pool.newDataPageIdForDTFile(delegator, __PRETTY_FUNCTION__); + // Note that the file id may has already been mark as deleted. We must + // create a reference to the page id itself instead of create a reference + // to the file id. + wbs.data.putRefPage(new_page_id, b_file->getDataPageId()); auto file_id = b_file->getFile()->fileId(); - wbs.data.putRefPage(new_ref_id, file_id); auto file_parent_path = delegator.getDTFilePath(file_id); - auto new_file = DMFile::restore(context.db_context.getFileProvider(), file_id, /* ref_id= */ new_ref_id, file_parent_path, DMFile::ReadMetaMode::all()); + auto new_file = DMFile::restore(context.db_context.getFileProvider(), file_id, /* page_id= */ new_page_id, file_parent_path, DMFile::ReadMetaMode::all()); auto new_big_file = b_file->cloneWith(context, new_file, target_range); cloned_tail.push_back(new_big_file); diff --git a/dbms/src/Storages/DeltaMerge/Delta/ColumnFilePersistedSet.h b/dbms/src/Storages/DeltaMerge/Delta/ColumnFilePersistedSet.h index 5fcd9b8c618..1580ae747da 100644 --- a/dbms/src/Storages/DeltaMerge/Delta/ColumnFilePersistedSet.h +++ b/dbms/src/Storages/DeltaMerge/Delta/ColumnFilePersistedSet.h @@ -57,8 +57,8 @@ class ColumnFilePersistedSet : public std::enable_shared_from_this persisted_files_count; - std::atomic persisted_files_level_count; + std::atomic persisted_files_count = 0; + std::atomic persisted_files_level_count = 0; std::atomic rows = 0; std::atomic bytes = 0; diff --git a/dbms/src/Storages/DeltaMerge/Delta/MemTableSet.cpp b/dbms/src/Storages/DeltaMerge/Delta/MemTableSet.cpp index bab8f352cad..d339b699d8d 100644 --- a/dbms/src/Storages/DeltaMerge/Delta/MemTableSet.cpp +++ b/dbms/src/Storages/DeltaMerge/Delta/MemTableSet.cpp @@ -104,11 +104,14 @@ ColumnFiles MemTableSet::cloneColumnFiles(DMContext & context, const RowKeyRange else if (auto * f = column_file->tryToBigFile(); f) { auto delegator = context.path_pool.getStableDiskDelegator(); - auto new_ref_id = context.storage_pool.newDataPageIdForDTFile(delegator, __PRETTY_FUNCTION__); + auto new_page_id = context.storage_pool.newDataPageIdForDTFile(delegator, __PRETTY_FUNCTION__); + // Note that the file id may has already been mark as deleted. We must + // create a reference to the page id itself instead of create a reference + // to the file id. + wbs.data.putRefPage(new_page_id, f->getDataPageId()); auto file_id = f->getFile()->fileId(); - wbs.data.putRefPage(new_ref_id, file_id); auto file_parent_path = delegator.getDTFilePath(file_id); - auto new_file = DMFile::restore(context.db_context.getFileProvider(), file_id, /* ref_id= */ new_ref_id, file_parent_path, DMFile::ReadMetaMode::all()); + auto new_file = DMFile::restore(context.db_context.getFileProvider(), file_id, /* page_id= */ new_page_id, file_parent_path, DMFile::ReadMetaMode::all()); auto new_column_file = f->cloneWith(context, new_file, target_range); cloned_column_files.push_back(new_column_file); diff --git a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp index 5583b0039b7..997db601d1e 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp +++ b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp @@ -314,8 +314,8 @@ void DeltaMergeStore::setUpBackgroundTask(const DMContextPtr & dm_context) if (valid_ids.count(id)) continue; - // Note that ref_id is useless here. - auto dmfile = DMFile::restore(global_context.getFileProvider(), id, /* ref_id= */ 0, path, DMFile::ReadMetaMode::none()); + // Note that page_id is useless here. + auto dmfile = DMFile::restore(global_context.getFileProvider(), id, /* page_id= */ 0, path, DMFile::ReadMetaMode::none()); if (dmfile->canGC()) { delegate.removeDTFile(dmfile->fileId()); @@ -820,14 +820,14 @@ void DeltaMergeStore::ingestFiles( /// Generate DMFile instance with a new ref_id pointed to the file_id. auto file_id = file->fileId(); const auto & file_parent_path = file->parentPath(); - auto ref_id = storage_pool->newDataPageIdForDTFile(delegate, __PRETTY_FUNCTION__); + auto page_id = storage_pool->newDataPageIdForDTFile(delegate, __PRETTY_FUNCTION__); - auto ref_file = DMFile::restore(file_provider, file_id, ref_id, file_parent_path, DMFile::ReadMetaMode::all()); + auto ref_file = DMFile::restore(file_provider, file_id, page_id, file_parent_path, DMFile::ReadMetaMode::all()); auto column_file = std::make_shared(*dm_context, ref_file, segment_range); if (column_file->getRows() != 0) { column_files.emplace_back(std::move(column_file)); - wbs.data.putRefPage(ref_id, file_id); + wbs.data.putRefPage(page_id, file->pageId()); } } @@ -2343,7 +2343,7 @@ void DeltaMergeStore::restoreStableFiles() { for (const auto & file_id : DMFile::listAllInPath(file_provider, root_path, options)) { - auto dmfile = DMFile::restore(file_provider, file_id, /* ref_id= */ 0, root_path, DMFile::ReadMetaMode::diskSizeOnly()); + auto dmfile = DMFile::restore(file_provider, file_id, /* page_id= */ 0, root_path, DMFile::ReadMetaMode::diskSizeOnly()); path_delegate.addDTFile(file_id, dmfile->getBytesOnDisk(), root_path); } } diff --git a/dbms/src/Storages/DeltaMerge/File/DMFile.cpp b/dbms/src/Storages/DeltaMerge/File/DMFile.cpp index d40a6bf5bab..fe984ad519f 100644 --- a/dbms/src/Storages/DeltaMerge/File/DMFile.cpp +++ b/dbms/src/Storages/DeltaMerge/File/DMFile.cpp @@ -146,7 +146,7 @@ DMFilePtr DMFile::create(UInt64 file_id, const String & parent_path, bool single DMFilePtr DMFile::restore( const FileProviderPtr & file_provider, UInt64 file_id, - UInt64 ref_id, + UInt64 page_id, const String & parent_path, const ReadMetaMode & read_meta_mode) { @@ -154,7 +154,7 @@ DMFilePtr DMFile::restore( bool single_file_mode = Poco::File(path).isFile(); DMFilePtr dmfile(new DMFile( file_id, - ref_id, + page_id, parent_path, single_file_mode ? Mode::SINGLE_FILE : Mode::FOLDER, Status::READABLE, @@ -556,7 +556,7 @@ void DMFile::readMetadata(const FileProviderPtr & file_provider, const ReadMetaM DB::readIntBinary(footer.sub_file_num, buf); // initialize sub file state buf.seek(footer.sub_file_stat_offset, SEEK_SET); - SubFileStat sub_file_stat; + SubFileStat sub_file_stat{}; for (UInt32 i = 0; i < footer.sub_file_num; i++) { String name; diff --git a/dbms/src/Storages/DeltaMerge/File/DMFile.h b/dbms/src/Storages/DeltaMerge/File/DMFile.h index e74048d0196..48d4071d595 100644 --- a/dbms/src/Storages/DeltaMerge/File/DMFile.h +++ b/dbms/src/Storages/DeltaMerge/File/DMFile.h @@ -141,7 +141,9 @@ class DMFile : private boost::noncopyable struct SubFileStat { - SubFileStat() = default; + SubFileStat() + : SubFileStat(0, 0) + {} SubFileStat(UInt64 offset_, UInt64 size_) : offset{offset_} , size{size_} @@ -195,7 +197,7 @@ class DMFile : private boost::noncopyable static DMFilePtr restore( const FileProviderPtr & file_provider, UInt64 file_id, - UInt64 ref_id, + UInt64 page_id, const String & parent_path, const ReadMetaMode & read_meta_mode); @@ -216,8 +218,10 @@ class DMFile : private boost::noncopyable void enableGC(); void remove(const FileProviderPtr & file_provider); + // The ID for locating DTFile on disk UInt64 fileId() const { return file_id; } - UInt64 refId() const { return ref_id; } + // The PageID for locating this object in the StoragePool.data + UInt64 pageId() const { return page_id; } String path() const; @@ -289,14 +293,14 @@ class DMFile : private boost::noncopyable private: DMFile(UInt64 file_id_, - UInt64 ref_id_, + UInt64 page_id_, String parent_path_, Mode mode_, Status status_, Poco::Logger * log_, DMConfigurationOpt configuration_ = std::nullopt) : file_id(file_id_) - , ref_id(ref_id_) + , page_id(page_id_) , parent_path(std::move(parent_path_)) , mode(mode_) , status(status_) @@ -395,8 +399,10 @@ class DMFile : private boost::noncopyable void initializeIndices(); private: + // The id to construct the file path on disk. UInt64 file_id; - UInt64 ref_id; // It is a reference to file_id, could be the same. + // It is the page_id that represent this file in the PageStorage. It could be the same as file id. + UInt64 page_id; String parent_path; PackStats pack_stats; diff --git a/dbms/src/Storages/DeltaMerge/Segment.cpp b/dbms/src/Storages/DeltaMerge/Segment.cpp index e8cde4ba0c6..9e195b2b25d 100644 --- a/dbms/src/Storages/DeltaMerge/Segment.cpp +++ b/dbms/src/Storages/DeltaMerge/Segment.cpp @@ -919,27 +919,30 @@ std::optional Segment::prepareSplitLogical(DMContext & dm_co auto delegate = dm_context.path_pool.getStableDiskDelegator(); for (const auto & dmfile : segment_snap->stable->getDMFiles()) { - auto ori_ref_id = dmfile->refId(); + auto ori_page_id = dmfile->pageId(); auto file_id = dmfile->fileId(); auto file_parent_path = delegate.getDTFilePath(file_id); - auto my_dmfile_id = storage_pool.newDataPageIdForDTFile(delegate, __PRETTY_FUNCTION__); - auto other_dmfile_id = storage_pool.newDataPageIdForDTFile(delegate, __PRETTY_FUNCTION__); + auto my_dmfile_page_id = storage_pool.newDataPageIdForDTFile(delegate, __PRETTY_FUNCTION__); + auto other_dmfile_page_id = storage_pool.newDataPageIdForDTFile(delegate, __PRETTY_FUNCTION__); - wbs.data.putRefPage(my_dmfile_id, file_id); - wbs.data.putRefPage(other_dmfile_id, file_id); - wbs.removed_data.delPage(ori_ref_id); + // Note that the file id may has already been mark as deleted. We must + // create a reference to the page id itself instead of create a reference + // to the file id. + wbs.data.putRefPage(my_dmfile_page_id, ori_page_id); + wbs.data.putRefPage(other_dmfile_page_id, ori_page_id); + wbs.removed_data.delPage(ori_page_id); auto my_dmfile = DMFile::restore( dm_context.db_context.getFileProvider(), file_id, - /* ref_id= */ my_dmfile_id, + /* page_id= */ my_dmfile_page_id, file_parent_path, DMFile::ReadMetaMode::all()); auto other_dmfile = DMFile::restore( dm_context.db_context.getFileProvider(), file_id, - /* ref_id= */ other_dmfile_id, + /* page_id= */ other_dmfile_page_id, file_parent_path, DMFile::ReadMetaMode::all()); @@ -1059,7 +1062,7 @@ std::optional Segment::prepareSplitPhysical(DMContext & dm_c { // Here we should remove the ref id instead of file_id. // Because a dmfile could be used by several segments, and only after all ref_ids are removed, then the file_id removed. - wbs.removed_data.delPage(file->refId()); + wbs.removed_data.delPage(file->pageId()); } LOG_FMT_INFO(log, "Segment [{}] prepare split physical done", segment_id); diff --git a/dbms/src/Storages/DeltaMerge/StableValueSpace.cpp b/dbms/src/Storages/DeltaMerge/StableValueSpace.cpp index 752a112b565..ed97bd8f421 100644 --- a/dbms/src/Storages/DeltaMerge/StableValueSpace.cpp +++ b/dbms/src/Storages/DeltaMerge/StableValueSpace.cpp @@ -80,7 +80,7 @@ void StableValueSpace::saveMeta(WriteBatch & meta_wb) writeIntBinary(valid_bytes, buf); writeIntBinary(static_cast(files.size()), buf); for (auto & f : files) - writeIntBinary(f->refId(), buf); + writeIntBinary(f->pageId(), buf); auto data_size = buf.count(); // Must be called before tryGetReadBuffer. meta_wb.putPage(id, 0, buf.tryGetReadBuffer(), data_size); @@ -100,15 +100,15 @@ StableValueSpacePtr StableValueSpace::restore(DMContext & context, PageId id) readIntBinary(valid_rows, buf); readIntBinary(valid_bytes, buf); readIntBinary(size, buf); - UInt64 ref_id; + UInt64 page_id; for (size_t i = 0; i < size; ++i) { - readIntBinary(ref_id, buf); + readIntBinary(page_id, buf); - auto file_id = context.storage_pool.dataReader()->getNormalPageId(ref_id); + auto file_id = context.storage_pool.dataReader()->getNormalPageId(page_id); auto file_parent_path = context.path_pool.getStableDiskDelegator().getDTFilePath(file_id); - auto dmfile = DMFile::restore(context.db_context.getFileProvider(), file_id, ref_id, file_parent_path, DMFile::ReadMetaMode::all()); + auto dmfile = DMFile::restore(context.db_context.getFileProvider(), file_id, page_id, file_parent_path, DMFile::ReadMetaMode::all()); stable->files.push_back(dmfile); } @@ -168,7 +168,7 @@ void StableValueSpace::recordRemovePacksPages(WriteBatches & wbs) const { // Here we should remove the ref id instead of file_id. // Because a dmfile could be used by several segments, and only after all ref_ids are removed, then the file_id removed. - wbs.removed_data.delPage(file->refId()); + wbs.removed_data.delPage(file->pageId()); } } diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.cpp b/dbms/src/Storages/DeltaMerge/StoragePool.cpp index b94cc3c1735..fbb631064a7 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.cpp +++ b/dbms/src/Storages/DeltaMerge/StoragePool.cpp @@ -18,6 +18,10 @@ #include #include #include +#include +#include +#include +#include #include @@ -244,6 +248,8 @@ StoragePool::StoragePool(Context & global_ctx, NamespaceId ns_id_, StoragePathPo void StoragePool::forceTransformMetaV2toV3() { + if (unlikely(run_mode != PageStorageRunMode::MIX_MODE)) + throw Exception(fmt::format("Transform meta must run under mix mode [run_mode={}]", static_cast(run_mode))); assert(meta_storage_v2 != nullptr); assert(meta_storage_v3 != nullptr); auto meta_transform_storage_writer = std::make_shared(run_mode, meta_storage_v2, meta_storage_v3); @@ -288,6 +294,80 @@ void StoragePool::forceTransformMetaV2toV3() meta_transform_storage_writer->writeIntoV2(std::move(write_batch_del_v2), nullptr); } +static inline DB::PS::V2::PageEntriesVersionSetWithDelta::Snapshot * +toV2ConcreteSnapshot(const DB::PageStorage::SnapshotPtr & ptr) +{ + return dynamic_cast(ptr.get()); +} + +void StoragePool::forceTransformDataV2toV3() +{ + if (unlikely(run_mode != PageStorageRunMode::MIX_MODE)) + throw Exception(fmt::format("Transform meta must run under mix mode [run_mode={}]", static_cast(run_mode))); + assert(data_storage_v2 != nullptr); + assert(data_storage_v3 != nullptr); + auto data_transform_storage_writer = std::make_shared(run_mode, data_storage_v2, data_storage_v3); + + auto snapshot = data_storage_v2->getSnapshot("transformDataV2toV3"); + auto * v2_snap = toV2ConcreteSnapshot(snapshot); + if (!snapshot || !v2_snap) + { + throw Exception("Can not allocate snapshot from pool.data v2", ErrorCodes::LOGICAL_ERROR); + } + + // Example + // 100 -> 100 + // 102 -> 100 + // 105 -> 100 + // 200 -> 200 + // 305 -> 300 + // Migration steps: + // collect v2 valid page id: 100, 102, 105, 200, 305 + // v3 put external 100, 200, 300; put ref 102, 105, 305 + // mark some id as deleted: v3 del 300 + // v2 delete 100, 102, 105, 200, 305 + + // The page ids that can be accessed by DeltaTree + const auto all_page_ids = v2_snap->view.validPageIds(); + + WriteBatch write_batch_transform{ns_id}; + WriteBatch write_batch_del_v2{ns_id}; + + std::set created_dt_file_id; + for (const auto page_id : all_page_ids) + { + // resolve the page_id into dtfile id + const auto resolved_file_id = v2_snap->view.resolveRefId(page_id); + if (auto ins_result = created_dt_file_id.insert(resolved_file_id); /*created=*/ins_result.second) + { + // first see this file id, migrate to v3 + write_batch_transform.putExternal(resolved_file_id, 0); + } + // migrate the reference for v3 + if (page_id != resolved_file_id) + { + write_batch_transform.putRefPage(page_id, resolved_file_id); + } + // record del for V2 + write_batch_del_v2.delPage(page_id); + } + // If the file id is not existed in `all_page_ids`, it means the file id + // itself has been deleted. + for (const auto dt_file_id : created_dt_file_id) + { + if (all_page_ids.count(dt_file_id) == 0) + { + write_batch_transform.delPage(dt_file_id); + } + } + + // Will rewrite into V3. + data_transform_storage_writer->writeIntoV3(std::move(write_batch_transform), nullptr); + + // DEL must call after rewrite. + data_transform_storage_writer->writeIntoV2(std::move(write_batch_del_v2), nullptr); +} + PageStorageRunMode StoragePool::restore() { switch (run_mode) @@ -324,10 +404,10 @@ PageStorageRunMode StoragePool::restore() // However, the pages on meta V2 can not be deleted. As the pages in meta are small, we perform a forceTransformMetaV2toV3 to convert pages before all. if (const auto & meta_remain_pages = meta_storage_v2->getNumberOfPages(); meta_remain_pages != 0) { - LOG_FMT_INFO(logger, "Current meta transform to V3 begin, [ns_id={}] [pages_before_transform={}]", ns_id, meta_remain_pages); + LOG_FMT_INFO(logger, "Current pool.meta transform to V3 begin [ns_id={}] [pages_before_transform={}]", ns_id, meta_remain_pages); forceTransformMetaV2toV3(); const auto & meta_remain_pages_after_transform = meta_storage_v2->getNumberOfPages(); - LOG_FMT_INFO(logger, "Current meta transform to V3 finished. [ns_id={}] [done={}] [pages_before_transform={}], [pages_after_transform={}]", // + LOG_FMT_INFO(logger, "Current pool.meta transform to V3 finished [ns_id={}] [done={}] [pages_before_transform={}], [pages_after_transform={}]", // ns_id, meta_remain_pages_after_transform == 0, meta_remain_pages, @@ -335,7 +415,23 @@ PageStorageRunMode StoragePool::restore() } else { - LOG_FMT_INFO(logger, "Current meta translate already done before restored.[ns_id={}] ", ns_id); + LOG_FMT_INFO(logger, "Current pool.meta translate already done before restored [ns_id={}] ", ns_id); + } + + if (const auto & data_remain_pages = data_storage_v2->getNumberOfPages(); data_remain_pages != 0) + { + LOG_FMT_INFO(logger, "Current pool.data transform to V3 begin [ns_id={}] [pages_before_transform={}]", ns_id, data_remain_pages); + forceTransformDataV2toV3(); + const auto & data_remain_pages_after_transform = data_storage_v2->getNumberOfPages(); + LOG_FMT_INFO(logger, "Current pool.data transform to V3 finished [ns_id={}] [done={}] [pages_before_transform={}], [pages_after_transform={}]", // + ns_id, + data_remain_pages_after_transform == 0, + data_remain_pages, + data_remain_pages_after_transform); + } + else + { + LOG_FMT_INFO(logger, "Current pool.data translate already done before restored [ns_id={}] ", ns_id); } // Check number of valid pages in v2 diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.h b/dbms/src/Storages/DeltaMerge/StoragePool.h index f106ac725e4..7ba6dd85995 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.h +++ b/dbms/src/Storages/DeltaMerge/StoragePool.h @@ -160,6 +160,7 @@ class StoragePool : private boost::noncopyable bool doV2Gc(const Settings & settings); void forceTransformMetaV2toV3(); + void forceTransformDataV2toV3(); #ifndef DBMS_PUBLIC_GTEST private: diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_file.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_file.cpp index 3ddf318509f..dfd4419fe38 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_file.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_file.cpp @@ -135,10 +135,10 @@ class DMFile_Test DMFilePtr restoreDMFile() { auto file_id = dm_file->fileId(); - auto ref_id = dm_file->refId(); + auto page_id = dm_file->pageId(); auto parent_path = dm_file->parentPath(); auto file_provider = dbContext().getFileProvider(); - return DMFile::restore(file_provider, file_id, ref_id, parent_path, DMFile::ReadMetaMode::all()); + return DMFile::restore(file_provider, file_id, page_id, parent_path, DMFile::ReadMetaMode::all()); } diff --git a/dbms/src/Storages/Page/PageStorage.cpp b/dbms/src/Storages/Page/PageStorage.cpp index 4a8dca05a71..6e1addae093 100644 --- a/dbms/src/Storages/Page/PageStorage.cpp +++ b/dbms/src/Storages/Page/PageStorage.cpp @@ -475,6 +475,8 @@ void PageWriter::writeIntoMixMode(WriteBatch && write_batch, WriteLimiterPtr wri // We need hold mem from V2 pages after write. std::list mem_holders; + std::set page_ids_before_ref; + for (const auto & write : write_batch.getWrites()) { switch (write.type) @@ -483,6 +485,7 @@ void PageWriter::writeIntoMixMode(WriteBatch && write_batch, WriteLimiterPtr wri case WriteBatch::WriteType::PUT: case WriteBatch::WriteType::PUT_EXTERNAL: { + page_ids_before_ref.insert(write.page_id); break; } // Both need del in v2 and v3 @@ -493,57 +496,84 @@ void PageWriter::writeIntoMixMode(WriteBatch && write_batch, WriteLimiterPtr wri } case WriteBatch::WriteType::REF: { + // 1. Try to resolve normal page id PageId resolved_page_id = storage_v3->getNormalPageId(ns_id, write.ori_page_id, /*snapshot*/ nullptr, false); - // If the normal id is not found in v3, read from v2 and create a new put + ref - if (resolved_page_id == INVALID_PAGE_ID) + + // If the origin id is found in V3, then just apply the ref to v3 + if (resolved_page_id != INVALID_PAGE_ID) + { + break; + } + + // 2. Check ori_page_id in current writebatch + if (page_ids_before_ref.count(write.ori_page_id) > 0) { - const auto & entry_for_put = storage_v2->getEntry(ns_id, write.ori_page_id, /*snapshot*/ {}); - if (entry_for_put.isValid()) - { - auto page_for_put = storage_v2->read(ns_id, write.ori_page_id); - - // Keep the mem hold, no need create new one. - mem_holders.emplace_back(page_for_put.mem_holder); - assert(entry_for_put.size == page_for_put.data.size()); - - // Page with fields - if (!entry_for_put.field_offsets.empty()) - { - wb_for_put_v3.putPage(write.ori_page_id, // - entry_for_put.tag, - std::make_shared(page_for_put.data.begin(), page_for_put.data.size()), - page_for_put.data.size(), - Page::fieldOffsetsToSizes(entry_for_put.field_offsets, entry_for_put.size)); - } - else - { // Normal page with fields - wb_for_put_v3.putPage(write.ori_page_id, // - entry_for_put.tag, - std::make_shared(page_for_put.data.begin(), - page_for_put.data.size()), - page_for_put.data.size()); - } - - LOG_FMT_INFO( - Logger::get("PageWriter"), - "Can't find the origin page in v3, migrate a new being ref page into V3 [page_id={}] [origin_id={}] [field_offsets={}]", - write.page_id, - write.ori_page_id, - entry_for_put.field_offsets.size()); - } - else - { - throw Exception(fmt::format("Can't find origin entry in V2 and V3, [ns_id={}, ori_page_id={}]", - ns_id, - write.ori_page_id), - ErrorCodes::LOGICAL_ERROR); - } + break; } - // else V3 found the origin one. - // Then do nothing. + + // Else the normal id is not found in v3, read from v2 and create a new put + ref + + // 3. Check ori_page_id in V2 + const auto & entry_for_put = storage_v2->getEntry(ns_id, write.ori_page_id, /*snapshot*/ {}); + + // If we can't find origin id in V3, must exist in V2. + if (!entry_for_put.isValid()) + { + throw Exception(fmt::format("Can't find origin entry in V2 and V3, [ns_id={}, ori_page_id={}]", + ns_id, + write.ori_page_id), + ErrorCodes::LOGICAL_ERROR); + } + + if (entry_for_put.size == 0) + { + // If the origin page size is 0. + // That means origin page in V2 is a external page id. + // Should not run into here after we introduce `StoragePool::forceTransformDataV2toV3` + throw Exception(fmt::format( + "Can't find the origin page in v3. Origin page in v2 size is 0, meaning it's a external id." + "Migrate a new being ref page into V3 [page_id={}] [origin_id={}]", + write.page_id, + write.ori_page_id, + entry_for_put.field_offsets.size()), + ErrorCodes::LOGICAL_ERROR); + } + + // Else find out origin page is a normal page in V2 + auto page_for_put = storage_v2->read(ns_id, write.ori_page_id); + + // Keep the mem holder for later write + mem_holders.emplace_back(page_for_put.mem_holder); + assert(entry_for_put.size == page_for_put.data.size()); + + // Page with fields + if (!entry_for_put.field_offsets.empty()) + { + wb_for_put_v3.putPage(write.ori_page_id, // + entry_for_put.tag, + std::make_shared(page_for_put.data.begin(), page_for_put.data.size()), + page_for_put.data.size(), + Page::fieldOffsetsToSizes(entry_for_put.field_offsets, entry_for_put.size)); + } + else + { // Normal page without fields + wb_for_put_v3.putPage(write.ori_page_id, // + entry_for_put.tag, + std::make_shared(page_for_put.data.begin(), + page_for_put.data.size()), + page_for_put.data.size()); + } + + LOG_FMT_INFO( + Logger::get("PageWriter"), + "Can't find the origin page in v3, migrate a new being ref page into V3 [page_id={}] [origin_id={}] [field_offsets={}]", + write.page_id, + write.ori_page_id, + entry_for_put.field_offsets.size()); + break; } default: diff --git a/dbms/src/Storages/Page/PageStorage.h b/dbms/src/Storages/Page/PageStorage.h index 06f3be5d1f7..481888bdf33 100644 --- a/dbms/src/Storages/Page/PageStorage.h +++ b/dbms/src/Storages/Page/PageStorage.h @@ -393,9 +393,10 @@ class PageWriter : private boost::noncopyable // Only used for META and KVStore write del. void writeIntoV2(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const; -private: + // Only used for DATA transform data void writeIntoV3(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const; +private: void writeIntoMixMode(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const; // A wrap of getSettings only used for `RegionPersister::gc` diff --git a/dbms/src/Storages/Page/V2/VersionSet/PageEntriesView.h b/dbms/src/Storages/Page/V2/VersionSet/PageEntriesView.h index 6a099626108..fe51c629663 100644 --- a/dbms/src/Storages/Page/V2/VersionSet/PageEntriesView.h +++ b/dbms/src/Storages/Page/V2/VersionSet/PageEntriesView.h @@ -14,6 +14,10 @@ #pragma once +#include +#include +#include + #include namespace DB::PS::V2 @@ -52,9 +56,9 @@ class PageEntriesView size_t numPages() const; size_t numNormalPages() const; -private: PageId resolveRefId(PageId page_id) const; +private: friend class DeltaVersionEditAcceptor; }; diff --git a/dbms/src/Storages/Page/V3/PageDirectory.h b/dbms/src/Storages/Page/V3/PageDirectory.h index 635cf04bfe6..14833245c7a 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.h +++ b/dbms/src/Storages/Page/V3/PageDirectory.h @@ -326,6 +326,7 @@ class PageDirectory { return getNormalPageId(page_id, toConcreteSnapshot(snap), throw_on_not_exist); } + #ifndef NDEBUG // Just for tests, refactor them out later PageIDAndEntryV3 get(PageId page_id, const PageDirectorySnapshotPtr & snap) const diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp index 6966f794de9..8aa9f92675c 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp @@ -254,8 +254,8 @@ void PageStorageImpl::traverseImpl(const std::functiongetAllPageIds(); for (const auto & valid_page : page_ids) { - const auto & page_entries = page_directory->get(valid_page, snapshot); - acceptor(blob_store.read(page_entries)); + const auto & page_id_and_entry = page_directory->get(valid_page, snapshot); + acceptor(blob_store.read(page_id_and_entry)); } } diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp index b0c2625466d..91dfcaac6a8 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp @@ -1301,5 +1301,33 @@ try } CATCH + +TEST_F(PageStorageTest, putExternalAfterRestore) +try +{ + { + WriteBatch batch; + batch.putExternal(1999, 0); + page_storage->write(std::move(batch)); + } + + page_storage = reopenWithConfig(config); + + auto alive_ids = page_storage->getAliveExternalPageIds(TEST_NAMESPACE_ID); + ASSERT_EQ(alive_ids.size(), 1); + ASSERT_EQ(*alive_ids.begin(), 1999); + + { + WriteBatch batch; + batch.putExternal(1999, 0); + page_storage->write(std::move(batch)); + } + + alive_ids = page_storage->getAliveExternalPageIds(TEST_NAMESPACE_ID); + ASSERT_EQ(alive_ids.size(), 1); + ASSERT_EQ(*alive_ids.begin(), 1999); +} +CATCH + } // namespace PS::V3::tests } // namespace DB diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp index d3fdafe57e8..98d84989dd9 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace DB { @@ -490,5 +491,83 @@ try } CATCH + +TEST_F(PageStorageMixedTest, MockDTIngest) +try +{ + { + WriteBatch batch; + batch.putExternal(100, 0); + page_writer_v2->write(std::move(batch), nullptr); + } + + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + { + // create dmf_1999 + // ingest to segment, create ref 2001 -> 1999 + // after ingest done, del 1999 + WriteBatch batch; + batch.putExternal(1999, 0); + batch.putRefPage(2001, 1999); + batch.delPage(1999); + ASSERT_NO_THROW(page_writer_mix->write(std::move(batch), nullptr)); + } + + { + // mock that create ref by dtfile id, should fail + WriteBatch batch; + batch.putRefPage(2012, 1999); + ASSERT_ANY_THROW(page_writer_mix->write(std::move(batch), nullptr)); + } + + { + // mock that create ref by page id of dtfile, should be ok + WriteBatch batch; + batch.putRefPage(2012, 2001); + ASSERT_NO_THROW(page_writer_mix->write(std::move(batch), nullptr)); + } + + // check 2012 -> 2001 => 2021 -> 1999 + ASSERT_EQ(page_reader_mix->getNormalPageId(2012), 1999); + + { + // Revert v3 + WriteBatch batch; + batch.delPage(2012); + batch.delPage(2001); + page_writer_mix->write(std::move(batch), nullptr); + } +} +CATCH + + +TEST_F(PageStorageMixedTest, RefV2External) +try +{ + { + WriteBatch batch; + batch.putExternal(100, 0); + batch.putRefPage(101, 100); + batch.delPage(100); + page_writer_v2->write(std::move(batch), nullptr); + } + + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + { + WriteBatch batch; + batch.putRefPage(102, 101); + // Should not run into this case after we introduce `StoragePool::forceTransformDataV2toV3` + ASSERT_ANY_THROW(page_writer_mix->write(std::move(batch), nullptr);); + } + { + // Revert v3 + WriteBatch batch; + batch.delPage(102); + page_writer_mix->write(std::move(batch), nullptr); + } +} +CATCH + + } // namespace PS::V3::tests } // namespace DB From 71613fd8c059187a89d7858d3d723fea701869fc Mon Sep 17 00:00:00 2001 From: Zhuhe Fang Date: Fri, 20 May 2022 11:22:38 +0800 Subject: [PATCH 047/127] expression: check Overflow int128 (#4762) close pingcap/tiflash#4512 --- .../include/common/arithmeticOverflow.h | 7 +-- libs/libcommon/src/tests/CMakeLists.txt | 1 + .../src/tests/gtest_arithmetic_overflow.cpp | 53 +++++++++++++++++++ .../fullstack-test-dt/expr_push_down.test | 22 ++++++++ 4 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 libs/libcommon/src/tests/gtest_arithmetic_overflow.cpp diff --git a/libs/libcommon/include/common/arithmeticOverflow.h b/libs/libcommon/include/common/arithmeticOverflow.h index 2a64fcef3e6..31218a091ff 100644 --- a/libs/libcommon/include/common/arithmeticOverflow.h +++ b/libs/libcommon/include/common/arithmeticOverflow.h @@ -13,6 +13,7 @@ // limitations under the License. #pragma once +#include "types.h" namespace common { @@ -113,11 +114,11 @@ inline bool mulOverflow(__int128 x, __int128 y, __int128 & res) if (!x || !y) return false; - unsigned __int128 a = (x > 0) ? x : -x; - unsigned __int128 b = (y > 0) ? y : -y; - return (a * b) / b != a; + return res / x != y; /// whether overflow int128 } +/// Int256 doesn't use the complement representation to express negative values, but uses an extra bit to express the sign flag, +/// the actual range of Int256 is from -(2^256 - 1) to 2^256 - 1, so 2^255 ~ 2^256-1 do not overflow Int256. template <> inline bool mulOverflow(Int256 x, Int256 y, Int256 & res) { diff --git a/libs/libcommon/src/tests/CMakeLists.txt b/libs/libcommon/src/tests/CMakeLists.txt index c2b01c8f473..655d118c354 100644 --- a/libs/libcommon/src/tests/CMakeLists.txt +++ b/libs/libcommon/src/tests/CMakeLists.txt @@ -37,6 +37,7 @@ add_executable (gtests_libcommon gtest_mem_utils.cpp gtest_crc64.cpp gtest_logger.cpp + gtest_arithmetic_overflow.cpp ) target_link_libraries (gtests_libcommon gtest_main common) add_check(gtests_libcommon) diff --git a/libs/libcommon/src/tests/gtest_arithmetic_overflow.cpp b/libs/libcommon/src/tests/gtest_arithmetic_overflow.cpp new file mode 100644 index 00000000000..c3bc3b243bc --- /dev/null +++ b/libs/libcommon/src/tests/gtest_arithmetic_overflow.cpp @@ -0,0 +1,53 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include +#include +TEST(OVERFLOW_Suite, SimpleTest) +{ + /// mul int128 + __int128 res128; + bool is_overflow; + /// 2^126 + static constexpr __int128 int_126 = __int128(__int128(1) << 126); + + /// 2^126 << 0 = 2^126 + is_overflow = common::mulOverflow(int_126, __int128(1), res128); + ASSERT_EQ(is_overflow, false); + + /// 2^126 << 1 = 2^127 + is_overflow = common::mulOverflow(int_126, __int128(2), res128); + ASSERT_EQ(is_overflow, true); + + /// 2^126 << 2 = 2^128 + is_overflow = common::mulOverflow(int_126, __int128(4), res128); + ASSERT_EQ(is_overflow, true); + + /// mul int256 + Int256 res256; + /// 2^254 + static constexpr Int256 int_254 = Int256((Int256(0x1) << 254)); + /// 2^254 << 0 = 2^254 + is_overflow = common::mulOverflow(int_254, Int256(1), res256); + ASSERT_EQ(is_overflow, false); + + /// 2^254 << 1 = 2^255 + is_overflow = common::mulOverflow(int_254, Int256(2), res256); + ASSERT_EQ(is_overflow, false); /// because the sign flag is processed by an extra bit, excluding from 256 bits of Int256. + + /// 2^254 << 2 = 2^256 + is_overflow = common::mulOverflow(int_254, Int256(4), res256); + ASSERT_EQ(is_overflow, true); +} diff --git a/tests/tidb-ci/fullstack-test-dt/expr_push_down.test b/tests/tidb-ci/fullstack-test-dt/expr_push_down.test index 341fb899ffd..dc5674456fa 100644 --- a/tests/tidb-ci/fullstack-test-dt/expr_push_down.test +++ b/tests/tidb-ci/fullstack-test-dt/expr_push_down.test @@ -110,3 +110,25 @@ mysql> use test; set @@tidb_isolation_read_engines='tiflash,tidb'; set @@tidb_al | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 2 | | 平凯xingchen公司 | | 平 | | 平凯xingchen公司 | NULL | NULL | 1 | +----------------------+------+------+------+----------------------+------+------------+----------+ + +## test overflow int128, uint128 or not. +mysql> drop table if exists test.t; +mysql> CREATE TABLE test.t (v1 decimal(20,20),v2 decimal(30,0)); +mysql> insert into test.t values (0.00000000000000000000 , 2585910611040796672),(0.00000000000000000000 , -1901644942657191936), (0.00000000000000000000 , -11901644942657191936),(0.00000000000000000000 , 25859106110407966722),(0.00000000000000000000 , 2585912),(0.00000000000000000000 , -190); +mysql> alter table test.t set tiflash replica 1; + +mysql> analyze table test.t; + +func> wait_table test t + +mysql> use test; set @@tidb_isolation_read_engines='tiflash,tidb'; set @@tidb_enforce_mpp=1; select v1,v2,v1>v2,v1>=v2, v1v2 | v1>=v2 | v1 Date: Fri, 20 May 2022 11:48:38 +0800 Subject: [PATCH 048/127] Add a fail point named force_change_all_blobs_to_read_only (#4920) ref pingcap/tiflash#3594 --- dbms/src/Common/FailPoint.cpp | 3 ++- dbms/src/Storages/Page/V3/BlobStore.cpp | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/dbms/src/Common/FailPoint.cpp b/dbms/src/Common/FailPoint.cpp index ae6e6308055..921dd5bf748 100644 --- a/dbms/src/Common/FailPoint.cpp +++ b/dbms/src/Common/FailPoint.cpp @@ -83,7 +83,8 @@ std::unordered_map> FailPointHelper::f M(force_no_local_region_for_mpp_task) \ M(force_remote_read_for_batch_cop) \ M(force_context_path) \ - M(force_slow_page_storage_snapshot_release) + M(force_slow_page_storage_snapshot_release) \ + M(force_change_all_blobs_to_read_only) #define APPLY_FOR_FAILPOINTS_ONCE_WITH_CHANNEL(M) \ M(pause_with_alter_locks_acquired) \ diff --git a/dbms/src/Storages/Page/V3/BlobStore.cpp b/dbms/src/Storages/Page/V3/BlobStore.cpp index 5f50d85e369..dc5ed536f9e 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.cpp +++ b/dbms/src/Storages/Page/V3/BlobStore.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -48,6 +49,11 @@ extern const int LOGICAL_ERROR; extern const int CHECKSUM_DOESNT_MATCH; } // namespace ErrorCodes +namespace FailPoints +{ +extern const char force_change_all_blobs_to_read_only[]; +} // namespace FailPoints + namespace PS::V3 { static constexpr bool BLOBSTORE_CHECKSUM_ON_READ = true; @@ -870,6 +876,19 @@ std::vector BlobStore::getGCStats() std::vector blob_need_gc; BlobStoreGCInfo blobstore_gc_info; + fiu_do_on(FailPoints::force_change_all_blobs_to_read_only, + { + for (const auto & [path, stats] : stats_list) + { + (void)path; + for (const auto & stat : stats) + { + stat->changeToReadOnly(); + } + } + LOG_FMT_WARNING(log, "enabled force_change_all_blobs_to_read_only. All of BlobStat turn to READ-ONLY"); + }); + for (const auto & [path, stats] : stats_list) { (void)path; From 7ebd651fb61cb1681145fe8d0ec00b8f1ccc2a23 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Fri, 20 May 2022 16:42:38 +0800 Subject: [PATCH 049/127] Fix StorageDeltaMerge constructor not match in gtest_manual_compact (#4950) close pingcap/tiflash#4949 --- dbms/src/Flash/Management/tests/gtest_manual_compact.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp b/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp index 922ced2470c..8ec3eb54406 100644 --- a/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp +++ b/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp @@ -38,8 +38,6 @@ extern const char pause_before_server_merge_one_delta[]; namespace tests { - - class BasicManualCompactTest : public DB::base::TiFlashStorageTestBasic , public testing::WithParamInterface @@ -83,7 +81,7 @@ class BasicManualCompactTest storage = StorageDeltaMerge::create("TiFlash", "default" /* db_name */, "test_table" /* table_name */, - std::ref(table_info), + table_info, ColumnsDescription{columns}, astptr, 0, From 2e8d324e76a2c5a96c83fdab8e7e08f3a5424d61 Mon Sep 17 00:00:00 2001 From: ruoxi Date: Fri, 20 May 2022 20:24:38 +0800 Subject: [PATCH 050/127] Add note about how to handle build oom or hang (#4951) close pingcap/tiflash#4953 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3bca0aa6597..8a2217b9a42 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,8 @@ cmake $WORKSPACE/tiflash make tiflash -j ``` +> **NOTE**: Option `-j` (defaults to your system CPU core count, otherwise you can optionally specify a number) is used to control the build parallelism. Higher parallelism consumes more memory. If you encounter compiler OOM or hang, try to lower the parallelism by specifying a reasonable number, e.g., half of your system CPU core count or even smaller, after `-j`, depending on the available memory in your system. + After building, you can get TiFlash binary under `$BUILD/dbms/src/Server/tiflash`. ### Build Options From 94afb714ed22185e9dc7292923ad21fc9952cebe Mon Sep 17 00:00:00 2001 From: lidezhu <47731263+lidezhu@users.noreply.github.com> Date: Mon, 23 May 2022 18:17:53 +0800 Subject: [PATCH 051/127] flush cache before segment merge (#4955) * flush cache before segment merge * keep flush until success * check whether segment is valid if flush failed * Update dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment.cpp Co-authored-by: JaySon * add more fix * check flush result in segment::write Co-authored-by: JaySon --- .../Storages/DeltaMerge/DeltaMergeStore.cpp | 23 +++++++++++ dbms/src/Storages/DeltaMerge/Segment.cpp | 38 ++++++++++++++++++- dbms/src/Storages/DeltaMerge/Segment.h | 3 +- .../DeltaMerge/tests/gtest_dm_segment.cpp | 28 +++++++++----- 4 files changed, 80 insertions(+), 12 deletions(-) diff --git a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp index 997db601d1e..27de092c26a 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp +++ b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp @@ -1971,6 +1971,29 @@ void DeltaMergeStore::segmentMerge(DMContext & dm_context, const SegmentPtr & le right->info(), dm_context.min_version); + /// This segment may contain some rows that not belong to this segment range which is left by previous split operation. + /// And only saved data in this segment will be filtered by the segment range in the merge process, + /// unsaved data will be directly copied to the new segment. + /// So we flush here to make sure that all potential data left by previous split operation is saved. + while (!left->flushCache(dm_context)) + { + // keep flush until success if not abandoned + if (left->hasAbandoned()) + { + LOG_FMT_DEBUG(log, "Give up merge segments left [{}], right [{}]", left->segmentId(), right->segmentId()); + return; + } + } + while (!right->flushCache(dm_context)) + { + // keep flush until success if not abandoned + if (right->hasAbandoned()) + { + LOG_FMT_DEBUG(log, "Give up merge segments left [{}], right [{}]", left->segmentId(), right->segmentId()); + return; + } + } + SegmentSnapshotPtr left_snap; SegmentSnapshotPtr right_snap; ColumnDefinesPtr schema_snap; diff --git a/dbms/src/Storages/DeltaMerge/Segment.cpp b/dbms/src/Storages/DeltaMerge/Segment.cpp index 9e195b2b25d..8398fdcee40 100644 --- a/dbms/src/Storages/DeltaMerge/Segment.cpp +++ b/dbms/src/Storages/DeltaMerge/Segment.cpp @@ -306,7 +306,7 @@ bool Segment::writeToCache(DMContext & dm_context, const Block & block, size_t o return delta->appendToCache(dm_context, block, offset, limit); } -bool Segment::write(DMContext & dm_context, const Block & block) +bool Segment::write(DMContext & dm_context, const Block & block, bool flush_cache) { LOG_FMT_TRACE(log, "Segment [{}] write to disk rows: {}", segment_id, block.rows()); WriteBatches wbs(dm_context.storage_pool, dm_context.getWriteLimiter()); @@ -316,7 +316,14 @@ bool Segment::write(DMContext & dm_context, const Block & block) if (delta->appendColumnFile(dm_context, column_file)) { - flushCache(dm_context); + if (flush_cache) + { + while (!flushCache(dm_context)) + { + if (hasAbandoned()) + return false; + } + } return true; } else @@ -1129,6 +1136,29 @@ SegmentPair Segment::applySplit(DMContext & dm_context, // SegmentPtr Segment::merge(DMContext & dm_context, const ColumnDefinesPtr & schema_snap, const SegmentPtr & left, const SegmentPtr & right) { WriteBatches wbs(dm_context.storage_pool, dm_context.getWriteLimiter()); + /// This segment may contain some rows that not belong to this segment range which is left by previous split operation. + /// And only saved data in this segment will be filtered by the segment range in the merge process, + /// unsaved data will be directly copied to the new segment. + /// So we flush here to make sure that all potential data left by previous split operation is saved. + while (!left->flushCache(dm_context)) + { + // keep flush until success if not abandoned + if (left->hasAbandoned()) + { + LOG_FMT_DEBUG(left->log, "Give up merge segments left [{}], right [{}]", left->segmentId(), right->segmentId()); + return {}; + } + } + while (!right->flushCache(dm_context)) + { + // keep flush until success if not abandoned + if (right->hasAbandoned()) + { + LOG_FMT_DEBUG(right->log, "Give up merge segments left [{}], right [{}]", left->segmentId(), right->segmentId()); + return {}; + } + } + auto left_snap = left->createSnapshot(dm_context, true, CurrentMetrics::DT_SnapshotOfSegmentMerge); auto right_snap = right->createSnapshot(dm_context, true, CurrentMetrics::DT_SnapshotOfSegmentMerge); @@ -1149,6 +1179,10 @@ SegmentPtr Segment::merge(DMContext & dm_context, const ColumnDefinesPtr & schem return merged; } +/// Segments may contain some rows that not belong to its range which is left by previous split operation. +/// And only saved data in the segment will be filtered by the segment range in the merge process, +/// unsaved data will be directly copied to the new segment. +/// So remember to do a flush for the segments before merge. StableValueSpacePtr Segment::prepareMerge(DMContext & dm_context, // const ColumnDefinesPtr & schema_snap, const SegmentPtr & left, diff --git a/dbms/src/Storages/DeltaMerge/Segment.h b/dbms/src/Storages/DeltaMerge/Segment.h index 3ad29ee14a5..cccfc5091b9 100644 --- a/dbms/src/Storages/DeltaMerge/Segment.h +++ b/dbms/src/Storages/DeltaMerge/Segment.h @@ -144,7 +144,8 @@ class Segment : private boost::noncopyable bool writeToCache(DMContext & dm_context, const Block & block, size_t offset, size_t limit); /// For test only. - bool write(DMContext & dm_context, const Block & block); + bool write(DMContext & dm_context, const Block & block, bool flush_cache = true); + bool write(DMContext & dm_context, const RowKeyRange & delete_range); bool ingestColumnFiles(DMContext & dm_context, const RowKeyRange & range, const ColumnFiles & column_files, bool clear_data_in_range); diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment.cpp index 6bf33465366..5726cfa132d 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment.cpp @@ -949,11 +949,17 @@ CATCH TEST_F(Segment_test, Split) try { - const size_t num_rows_write = 100; + const size_t num_rows_write_per_batch = 100; + const size_t num_rows_write = num_rows_write_per_batch * 2; { - // write to segment - Block block = DMTestEnv::prepareSimpleWriteBlock(0, num_rows_write, false); - segment->write(dmContext(), std::move(block)); + // write to segment and flush + Block block = DMTestEnv::prepareSimpleWriteBlock(0, num_rows_write_per_batch, false); + segment->write(dmContext(), std::move(block), true); + } + { + // write to segment and don't flush + Block block = DMTestEnv::prepareSimpleWriteBlock(num_rows_write_per_batch, 2 * num_rows_write_per_batch, false); + segment->write(dmContext(), std::move(block), false); } { @@ -989,7 +995,7 @@ try size_t num_rows_seg2 = 0; { { - auto in = segment->getInputStream(dmContext(), *tableColumns(), {RowKeyRange::newAll(false, 1)}); + auto in = segment->getInputStream(dmContext(), *tableColumns(), {segment->getRowKeyRange()}); in->readPrefix(); while (Block block = in->read()) { @@ -998,7 +1004,7 @@ try in->readSuffix(); } { - auto in = segment->getInputStream(dmContext(), *tableColumns(), {RowKeyRange::newAll(false, 1)}); + auto in = new_segment->getInputStream(dmContext(), *tableColumns(), {new_segment->getRowKeyRange()}); in->readPrefix(); while (Block block = in->read()) { @@ -1009,9 +1015,13 @@ try ASSERT_EQ(num_rows_seg1 + num_rows_seg2, num_rows_write); } + // delete rows in the right segment + { + new_segment->write(dmContext(), /*delete_range*/ new_segment->getRowKeyRange()); + new_segment->flushCache(dmContext()); + } + // merge segments - // TODO: enable merge test! - if (false) { segment = Segment::merge(dmContext(), tableColumns(), segment, new_segment); { @@ -1030,7 +1040,7 @@ try num_rows_read += block.rows(); } in->readSuffix(); - EXPECT_EQ(num_rows_read, num_rows_write); + EXPECT_EQ(num_rows_read, num_rows_seg1); } } } From 1dfe7fc9eba8091348314a52f495a8206ce4a02c Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Mon, 23 May 2022 19:08:46 +0800 Subject: [PATCH 052/127] Add a global max_id to fix reuse page_id problem (#4948) close pingcap/tiflash#4939 --- .../Storages/DeltaMerge/DeltaMergeStore.cpp | 13 +- dbms/src/Storages/DeltaMerge/StoragePool.cpp | 24 ++-- dbms/src/Storages/DeltaMerge/StoragePool.h | 11 +- dbms/src/Storages/Page/PageStorage.h | 12 +- dbms/src/Storages/Page/V2/PageStorage.cpp | 6 +- dbms/src/Storages/Page/V2/PageStorage.h | 2 +- dbms/src/Storages/Page/V3/PageDirectory.cpp | 49 ++------ dbms/src/Storages/Page/V3/PageDirectory.h | 3 +- .../Storages/Page/V3/PageDirectoryFactory.cpp | 4 +- .../Storages/Page/V3/PageDirectoryFactory.h | 1 - dbms/src/Storages/Page/V3/PageStorageImpl.cpp | 4 +- dbms/src/Storages/Page/V3/PageStorageImpl.h | 2 +- dbms/src/Storages/Page/V3/WAL/serialize.cpp | 4 +- .../Page/V3/tests/gtest_page_directory.cpp | 111 ------------------ .../Page/V3/tests/gtest_page_storage.cpp | 51 ++++++++ 15 files changed, 115 insertions(+), 182 deletions(-) diff --git a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp index 27de092c26a..a74404f3dbb 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp +++ b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp @@ -244,10 +244,19 @@ DeltaMergeStore::DeltaMergeStore(Context & db_context, if (const auto first_segment_entry = storage_pool->metaReader()->getPageEntry(DELTA_MERGE_FIRST_SEGMENT_ID); !first_segment_entry.isValid()) { - // Create the first segment. auto segment_id = storage_pool->newMetaPageId(); if (segment_id != DELTA_MERGE_FIRST_SEGMENT_ID) - throw Exception(fmt::format("The first segment id should be {}", DELTA_MERGE_FIRST_SEGMENT_ID), ErrorCodes::LOGICAL_ERROR); + { + if (page_storage_run_mode == PageStorageRunMode::ONLY_V2) + { + throw Exception(fmt::format("The first segment id should be {}", DELTA_MERGE_FIRST_SEGMENT_ID), ErrorCodes::LOGICAL_ERROR); + } + + // In ONLY_V3 or MIX_MODE, If create a new DeltaMergeStore + // Should used fixed DELTA_MERGE_FIRST_SEGMENT_ID to create first segment + segment_id = DELTA_MERGE_FIRST_SEGMENT_ID; + } + auto first_segment = Segment::newSegment(*dm_context, store_columns, RowKeyRange::newAll(is_common_handle, rowkey_column_size), segment_id, 0); segments.emplace(first_segment->getRowKeyRange().getEnd(), first_segment); diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.cpp b/dbms/src/Storages/DeltaMerge/StoragePool.cpp index fbb631064a7..a040c5b6c6a 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.cpp +++ b/dbms/src/Storages/DeltaMerge/StoragePool.cpp @@ -378,18 +378,18 @@ PageStorageRunMode StoragePool::restore() data_storage_v2->restore(); meta_storage_v2->restore(); - max_log_page_id = log_storage_v2->getMaxId(ns_id); - max_data_page_id = data_storage_v2->getMaxId(ns_id); - max_meta_page_id = meta_storage_v2->getMaxId(ns_id); + max_log_page_id = log_storage_v2->getMaxId(); + max_data_page_id = data_storage_v2->getMaxId(); + max_meta_page_id = meta_storage_v2->getMaxId(); storage_pool_metrics = CurrentMetrics::Increment{CurrentMetrics::StoragePoolV2Only}; break; } case PageStorageRunMode::ONLY_V3: { - max_log_page_id = log_storage_v3->getMaxId(ns_id); - max_data_page_id = data_storage_v3->getMaxId(ns_id); - max_meta_page_id = meta_storage_v3->getMaxId(ns_id); + max_log_page_id = log_storage_v3->getMaxId(); + max_data_page_id = data_storage_v3->getMaxId(); + max_meta_page_id = meta_storage_v3->getMaxId(); storage_pool_metrics = CurrentMetrics::Increment{CurrentMetrics::StoragePoolV3Only}; break; @@ -456,18 +456,18 @@ PageStorageRunMode StoragePool::restore() data_storage_writer = std::make_shared(PageStorageRunMode::ONLY_V3, /*storage_v2_*/ nullptr, data_storage_v3); meta_storage_writer = std::make_shared(PageStorageRunMode::ONLY_V3, /*storage_v2_*/ nullptr, meta_storage_v3); - max_log_page_id = log_storage_v3->getMaxId(ns_id); - max_data_page_id = data_storage_v3->getMaxId(ns_id); - max_meta_page_id = meta_storage_v3->getMaxId(ns_id); + max_log_page_id = log_storage_v3->getMaxId(); + max_data_page_id = data_storage_v3->getMaxId(); + max_meta_page_id = meta_storage_v3->getMaxId(); run_mode = PageStorageRunMode::ONLY_V3; storage_pool_metrics = CurrentMetrics::Increment{CurrentMetrics::StoragePoolV3Only}; } else // Still running Mix Mode { - max_log_page_id = std::max(log_storage_v2->getMaxId(ns_id), log_storage_v3->getMaxId(ns_id)); - max_data_page_id = std::max(data_storage_v2->getMaxId(ns_id), data_storage_v3->getMaxId(ns_id)); - max_meta_page_id = std::max(meta_storage_v2->getMaxId(ns_id), meta_storage_v3->getMaxId(ns_id)); + max_log_page_id = std::max(log_storage_v2->getMaxId(), log_storage_v3->getMaxId()); + max_data_page_id = std::max(data_storage_v2->getMaxId(), data_storage_v3->getMaxId()); + max_meta_page_id = std::max(meta_storage_v2->getMaxId(), meta_storage_v3->getMaxId()); storage_pool_metrics = CurrentMetrics::Increment{CurrentMetrics::StoragePoolMixMode}; } break; diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.h b/dbms/src/Storages/DeltaMerge/StoragePool.h index 7ba6dd85995..d05454a5431 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.h +++ b/dbms/src/Storages/DeltaMerge/StoragePool.h @@ -149,11 +149,18 @@ class StoragePool : private boost::noncopyable // Caller must cancel gc tasks before drop void drop(); - PageId newDataPageIdForDTFile(StableDiskDelegator & delegator, const char * who); + // For function `newLogPageId`,`newMetaPageId`,`newDataPageIdForDTFile`: + // For PageStorageRunMode::ONLY_V2, every table have its own three PageStorage (meta/data/log). + // So these functions return the Page id starts from 1 and is continuously incremented. + // For PageStorageRunMode::ONLY_V3/MIX_MODE, PageStorage is global(distinguish by ns_id for different table). + // In order to avoid Page id from being reused (and cause troubles while restoring WAL from disk), + // StoragePool will assign the max_log_page_id/max_meta_page_id/max_data_page_id by the global max id + // regardless of ns_id while being restored. This causes the ids in a table to not be continuously incremented. - PageId maxMetaPageId() { return max_meta_page_id; } + PageId newDataPageIdForDTFile(StableDiskDelegator & delegator, const char * who); PageId newLogPageId() { return ++max_log_page_id; } PageId newMetaPageId() { return ++max_meta_page_id; } + #ifndef DBMS_PUBLIC_GTEST private: #endif diff --git a/dbms/src/Storages/Page/PageStorage.h b/dbms/src/Storages/Page/PageStorage.h index 481888bdf33..479c368a585 100644 --- a/dbms/src/Storages/Page/PageStorage.h +++ b/dbms/src/Storages/Page/PageStorage.h @@ -233,7 +233,17 @@ class PageStorage : private boost::noncopyable virtual void drop() = 0; - virtual PageId getMaxId(NamespaceId ns_id) = 0; + // Get the max id from PageStorage. + // + // For V2, every table have its own three PageStorage (meta/data/log). + // So this function return the Page id starts from 0 and is continuously incremented to + // new pages. + // For V3, PageStorage is global(distinguish by ns_id for different table). + // In order to avoid Page id from being reused (and cause troubles while restoring WAL from disk), + // this function returns the global max id regardless of ns_id. This causes the ids in a table + // to not be continuously incremented. + // Note that Page id 1 in each ns_id is special. + virtual PageId getMaxId() = 0; virtual SnapshotPtr getSnapshot(const String & tracing_id) = 0; diff --git a/dbms/src/Storages/Page/V2/PageStorage.cpp b/dbms/src/Storages/Page/V2/PageStorage.cpp index 3ab62d55242..7a23afb11d4 100644 --- a/dbms/src/Storages/Page/V2/PageStorage.cpp +++ b/dbms/src/Storages/Page/V2/PageStorage.cpp @@ -355,7 +355,7 @@ void PageStorage::restore() LOG_FMT_INFO(log, "{} restore {} pages, write batch sequence: {}, {}", storage_name, num_pages, write_batch_seq, statistics.toString()); } -PageId PageStorage::getMaxId(NamespaceId /*ns_id*/) +PageId PageStorage::getMaxId() { std::lock_guard write_lock(write_mutex); return versioned_page_entries.getSnapshot("")->version()->maxId(); @@ -893,9 +893,9 @@ void PageStorage::drop() struct GcContext { PageFileIdAndLevel min_file_id; - PageFile::Type min_file_type; + PageFile::Type min_file_type = PageFile::Type::Invalid; PageFileIdAndLevel max_file_id; - PageFile::Type max_file_type; + PageFile::Type max_file_type = PageFile::Type::Invalid; size_t num_page_files = 0; size_t num_legacy_files = 0; diff --git a/dbms/src/Storages/Page/V2/PageStorage.h b/dbms/src/Storages/Page/V2/PageStorage.h index cb55a769f37..b9e16fd1775 100644 --- a/dbms/src/Storages/Page/V2/PageStorage.h +++ b/dbms/src/Storages/Page/V2/PageStorage.h @@ -95,7 +95,7 @@ class PageStorage : public DB::PageStorage void drop() override; - PageId getMaxId(NamespaceId ns_id) override; + PageId getMaxId() override; PageId getNormalPageIdImpl(NamespaceId ns_id, PageId page_id, SnapshotPtr snapshot, bool throw_on_not_exist) override; diff --git a/dbms/src/Storages/Page/V3/PageDirectory.cpp b/dbms/src/Storages/Page/V3/PageDirectory.cpp index 06b26156529..64a3fead674 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectory.cpp @@ -685,7 +685,8 @@ void VersionedPageEntries::collapseTo(const UInt64 seq, const PageIdV3Internal p *************************/ PageDirectory::PageDirectory(String storage_name, WALStorePtr && wal_, UInt64 max_persisted_log_files_) - : sequence(0) + : max_page_id(0) + , sequence(0) , wal(std::move(wal_)) , max_persisted_log_files(max_persisted_log_files_) , log(Logger::get("PageDirectory", std::move(storage_name))) @@ -923,49 +924,10 @@ PageIdV3Internal PageDirectory::getNormalPageId(PageIdV3Internal page_id, const } } -PageId PageDirectory::getMaxId(NamespaceId ns_id) const +PageId PageDirectory::getMaxId() const { std::shared_lock read_lock(table_rw_mutex); - PageIdV3Internal upper_bound = buildV3Id(ns_id, UINT64_MAX); - - auto iter = mvcc_table_directory.upper_bound(upper_bound); - if (iter == mvcc_table_directory.begin()) - { - // The smallest page id is greater than the target page id or mvcc_table_directory is empty, - // and it means no page id is less than or equal to the target page id, return 0. - return 0; - } - else - { - // iter is not at the beginning and mvcc_table_directory is not empty, - // so iter-- must be a valid iterator, and it's the largest page id which is smaller than the target page id. - iter--; - - do - { - // Can't find any entries in current ns_id - if (iter->first.high != ns_id) - { - break; - } - - // Check and return whether this id is visible, otherwise continue to check the previous one. - if (iter->second->isVisible(UINT64_MAX - 1)) - { - return iter->first.low; - } - - // Current entry/ref/external is deleted and there are no entries before it. - if (iter == mvcc_table_directory.begin()) - { - break; - } - - iter--; - } while (true); - - return 0; - } + return max_page_id; } std::set PageDirectory::getAllPageIds() @@ -1069,6 +1031,9 @@ void PageDirectory::apply(PageEntriesEdit && edit, const WriteLimiterPtr & write // stage 2, create entry version list for page_id. for (const auto & r : edit.getRecords()) { + // Protected in write_lock + max_page_id = std::max(max_page_id, r.page_id.low); + auto [iter, created] = mvcc_table_directory.insert(std::make_pair(r.page_id, nullptr)); if (created) { diff --git a/dbms/src/Storages/Page/V3/PageDirectory.h b/dbms/src/Storages/Page/V3/PageDirectory.h index 14833245c7a..a3c6b079fee 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.h +++ b/dbms/src/Storages/Page/V3/PageDirectory.h @@ -347,7 +347,7 @@ class PageDirectory } #endif - PageId getMaxId(NamespaceId ns_id) const; + PageId getMaxId() const; std::set getAllPageIds(); @@ -397,6 +397,7 @@ class PageDirectory } private: + PageId max_page_id; std::atomic sequence; mutable std::shared_mutex table_rw_mutex; MVCCMapType mvcc_table_directory; diff --git a/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp b/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp index 40b12b64f06..0592d1ddaa8 100644 --- a/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp @@ -40,6 +40,7 @@ PageDirectoryPtr PageDirectoryFactory::create(String storage_name, FileProviderP // After restoring from the disk, we need cleanup all invalid entries in memory, or it will // try to run GC again on some entries that are already marked as invalid in BlobStore. dir->gcInMemEntries(); + LOG_FMT_INFO(DB::Logger::get("PageDirectoryFactory"), "PageDirectory restored [max_page_id={}] [max_applied_ver={}]", dir->getMaxId(), dir->sequence); if (blob_stats) { @@ -111,7 +112,6 @@ void PageDirectoryFactory::loadEdit(const PageDirectoryPtr & dir, const PageEntr { if (max_applied_ver < r.version) max_applied_ver = r.version; - max_applied_page_id = std::max(r.page_id, max_applied_page_id); // We can not avoid page id from being reused under some corner situation. Try to do gcInMemEntries // and apply again to resolve the error. @@ -135,6 +135,8 @@ bool PageDirectoryFactory::applyRecord( iter->second = std::make_shared(); } + dir->max_page_id = std::max(dir->max_page_id, r.page_id.low); + const auto & version_list = iter->second; const auto & restored_version = r.version; try diff --git a/dbms/src/Storages/Page/V3/PageDirectoryFactory.h b/dbms/src/Storages/Page/V3/PageDirectoryFactory.h index 11337e4a6cc..e4b76bfba0d 100644 --- a/dbms/src/Storages/Page/V3/PageDirectoryFactory.h +++ b/dbms/src/Storages/Page/V3/PageDirectoryFactory.h @@ -38,7 +38,6 @@ class PageDirectoryFactory { public: PageVersion max_applied_ver; - PageIdV3Internal max_applied_page_id; PageDirectoryFactory & setBlobStore(BlobStore & blob_store) { diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp index 8aa9f92675c..58fe4b4dd4c 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp @@ -55,9 +55,9 @@ void PageStorageImpl::restore() .create(storage_name, file_provider, delegator, parseWALConfig(config)); } -PageId PageStorageImpl::getMaxId(NamespaceId ns_id) +PageId PageStorageImpl::getMaxId() { - return page_directory->getMaxId(ns_id); + return page_directory->getMaxId(); } void PageStorageImpl::drop() diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.h b/dbms/src/Storages/Page/V3/PageStorageImpl.h index f3b696d0351..082adb8df34 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.h +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.h @@ -64,7 +64,7 @@ class PageStorageImpl : public DB::PageStorage void drop() override; - PageId getMaxId(NamespaceId ns_id) override; + PageId getMaxId() override; PageId getNormalPageIdImpl(NamespaceId ns_id, PageId page_id, SnapshotPtr snapshot, bool throw_on_not_exist) override; diff --git a/dbms/src/Storages/Page/V3/WAL/serialize.cpp b/dbms/src/Storages/Page/V3/WAL/serialize.cpp index f8e26617499..6b7bc9b8a21 100644 --- a/dbms/src/Storages/Page/V3/WAL/serialize.cpp +++ b/dbms/src/Storages/Page/V3/WAL/serialize.cpp @@ -218,7 +218,7 @@ void deserializeFrom(ReadBuffer & buf, PageEntriesEdit & edit) break; } default: - throw Exception(fmt::format("Unknown record type: {}", record_type)); + throw Exception(fmt::format("Unknown record type: {}", record_type), ErrorCodes::LOGICAL_ERROR); } } } @@ -261,7 +261,7 @@ PageEntriesEdit deserializeFrom(std::string_view record) UInt32 version = 0; readIntBinary(version, buf); if (version != 1) - throw Exception(""); + throw Exception(fmt::format("Unknown version for PageEntriesEdit deser [version={}]", version), ErrorCodes::LOGICAL_ERROR); deserializeFrom(buf, edit); return edit; diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp index 4511cc8ddd7..dfa33824473 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp @@ -2399,117 +2399,6 @@ try } CATCH -TEST_F(PageDirectoryTest, GetMaxId) -try -{ - NamespaceId small = 20; - NamespaceId medium = 50; - NamespaceId large = 100; - ASSERT_EQ(dir->getMaxId(small), 0); - ASSERT_EQ(dir->getMaxId(medium), 0); - ASSERT_EQ(dir->getMaxId(large), 0); - - PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry2{.file_id = 2, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - { - PageEntriesEdit edit; - edit.put(buildV3Id(small, 1), entry1); - edit.put(buildV3Id(large, 2), entry2); - dir->apply(std::move(edit)); - ASSERT_EQ(dir->getMaxId(small), 1); - ASSERT_EQ(dir->getMaxId(medium), 0); - ASSERT_EQ(dir->getMaxId(large), 2); - } - - PageEntryV3 entry3{.file_id = 3, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry4{.file_id = 4, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - { - PageEntriesEdit edit; - edit.put(buildV3Id(medium, 300), entry1); - edit.put(buildV3Id(medium, 320), entry2); - dir->apply(std::move(edit)); - ASSERT_EQ(dir->getMaxId(small), 1); - ASSERT_EQ(dir->getMaxId(medium), 320); - ASSERT_EQ(dir->getMaxId(large), 2); - } - - { - PageEntriesEdit edit; - edit.del(buildV3Id(medium, 320)); - dir->apply(std::move(edit)); - ASSERT_EQ(dir->getMaxId(medium), 300); - } - - { - PageEntriesEdit edit; - edit.del(buildV3Id(medium, 300)); - dir->apply(std::move(edit)); - ASSERT_EQ(dir->getMaxId(medium), 0); - } -} -CATCH - -TEST_F(PageDirectoryTest, GetMaxIdAfterDelete) -try -{ - /// test for deleting put - PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry2{.file_id = 2, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - { - PageEntriesEdit edit; - edit.put(1, entry1); - edit.put(2, entry2); - dir->apply(std::move(edit)); - } - - ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 2); - - { - PageEntriesEdit edit; - edit.del(2); - dir->apply(std::move(edit)); - } - ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 1); - - { - PageEntriesEdit edit; - edit.del(1); - dir->apply(std::move(edit)); - } - ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 0); - - dir->gcInMemEntries(); - ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 0); - - /// test for deleting put_ext/ref - - { - PageEntriesEdit edit; - edit.putExternal(1); - edit.ref(2, 1); - dir->apply(std::move(edit)); - } - - { - PageEntriesEdit edit; - edit.del(1); - dir->apply(std::move(edit)); - } - ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 2); - dir->gcInMemEntries(); - ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 2); - - { - PageEntriesEdit edit; - edit.del(2); - dir->apply(std::move(edit)); - } - ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 0); - dir->gcInMemEntries(); - ASSERT_EQ(dir->getMaxId(TEST_NAMESPACE_ID), 0); -} -CATCH - #undef INSERT_ENTRY_TO #undef INSERT_ENTRY #undef INSERT_ENTRY_ACQ_SNAP diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp index 91dfcaac6a8..498fd4124e5 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp @@ -1329,5 +1329,56 @@ try } CATCH +TEST_F(PageStorageTest, GetMaxId) +try +{ + NamespaceId small = 20; + NamespaceId medium = 50; + NamespaceId large = 100; + + { + WriteBatch batch{small}; + batch.putExternal(1, 0); + batch.putExternal(1999, 0); + batch.putExternal(2000, 0); + page_storage->write(std::move(batch)); + ASSERT_EQ(page_storage->getMaxId(), 2000); + } + + { + page_storage = reopenWithConfig(config); + ASSERT_EQ(page_storage->getMaxId(), 2000); + } + + { + WriteBatch batch{medium}; + batch.putExternal(1, 0); + batch.putExternal(100, 0); + batch.putExternal(200, 0); + page_storage->write(std::move(batch)); + ASSERT_EQ(page_storage->getMaxId(), 2000); + } + + { + page_storage = reopenWithConfig(config); + ASSERT_EQ(page_storage->getMaxId(), 2000); + } + + { + WriteBatch batch{large}; + batch.putExternal(1, 0); + batch.putExternal(20000, 0); + batch.putExternal(20001, 0); + page_storage->write(std::move(batch)); + ASSERT_EQ(page_storage->getMaxId(), 20001); + } + + { + page_storage = reopenWithConfig(config); + ASSERT_EQ(page_storage->getMaxId(), 20001); + } +} +CATCH + } // namespace PS::V3::tests } // namespace DB From 350323d36167e30c85dac7f399fd920334fa13d3 Mon Sep 17 00:00:00 2001 From: Zhuhe Fang Date: Mon, 23 May 2022 19:40:46 +0800 Subject: [PATCH 053/127] Schedule: track the waiting tasks with task ID, and deleted the scheduled task with exceeded state from the waiting tasks queue (#4958) close pingcap/tiflash#4954 --- dbms/src/Flash/Mpp/MPPTaskManager.h | 2 +- dbms/src/Flash/Mpp/MinTSOScheduler.cpp | 22 ++++++++++++++++------ dbms/src/Flash/Mpp/MinTSOScheduler.h | 2 +- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/dbms/src/Flash/Mpp/MPPTaskManager.h b/dbms/src/Flash/Mpp/MPPTaskManager.h index 024dd4f3a59..d7047804aca 100644 --- a/dbms/src/Flash/Mpp/MPPTaskManager.h +++ b/dbms/src/Flash/Mpp/MPPTaskManager.h @@ -35,7 +35,7 @@ struct MPPQueryTaskSet bool to_be_cancelled = false; MPPTaskMap task_map; /// only used in scheduler - std::queue waiting_tasks; + std::queue waiting_tasks; }; using MPPQueryTaskSetPtr = std::shared_ptr; diff --git a/dbms/src/Flash/Mpp/MinTSOScheduler.cpp b/dbms/src/Flash/Mpp/MinTSOScheduler.cpp index 9fbe4b7b7cb..af525bd1a55 100644 --- a/dbms/src/Flash/Mpp/MinTSOScheduler.cpp +++ b/dbms/src/Flash/Mpp/MinTSOScheduler.cpp @@ -72,7 +72,8 @@ bool MinTSOScheduler::tryToSchedule(const MPPTaskPtr & task, MPPTaskManager & ta LOG_FMT_WARNING(log, "{} is scheduled with miss or cancellation.", id.toString()); return true; } - return scheduleImp(id.start_ts, query_task_set, task, false); + bool has_error = false; + return scheduleImp(id.start_ts, query_task_set, task, false, has_error); } /// after finishing the query, there would be no threads released soon, so the updated min-tso query with waiting tasks should be scheduled. @@ -97,7 +98,9 @@ void MinTSOScheduler::deleteQuery(const UInt64 tso, MPPTaskManager & task_manage { while (!query_task_set->waiting_tasks.empty()) { - query_task_set->waiting_tasks.front()->scheduleThisTask(MPPTask::ScheduleState::FAILED); + auto task_it = query_task_set->task_map.find(query_task_set->waiting_tasks.front()); + if (task_it != query_task_set->task_map.end() && task_it->second != nullptr) + task_it->second->scheduleThisTask(MPPTask::ScheduleState::FAILED); query_task_set->waiting_tasks.pop(); GET_METRIC(tiflash_task_scheduler, type_waiting_tasks_count).Decrement(); } @@ -153,9 +156,14 @@ void MinTSOScheduler::scheduleWaitingQueries(MPPTaskManager & task_manager) /// schedule tasks one by one while (!query_task_set->waiting_tasks.empty()) { - auto task = query_task_set->waiting_tasks.front(); - if (!scheduleImp(current_query_id, query_task_set, task, true)) + auto task_it = query_task_set->task_map.find(query_task_set->waiting_tasks.front()); + bool has_error = false; + if (task_it != query_task_set->task_map.end() && task_it->second != nullptr && !scheduleImp(current_query_id, query_task_set, task_it->second, true, has_error)) + { + if (has_error) + query_task_set->waiting_tasks.pop(); /// it should be pop from the waiting queue, because the task is scheduled with errors. return; + } query_task_set->waiting_tasks.pop(); GET_METRIC(tiflash_task_scheduler, type_waiting_tasks_count).Decrement(); } @@ -166,7 +174,7 @@ void MinTSOScheduler::scheduleWaitingQueries(MPPTaskManager & task_manager) } /// [directly schedule, from waiting set] * [is min_tso query, not] * [can schedule, can't] totally 8 cases. -bool MinTSOScheduler::scheduleImp(const UInt64 tso, const MPPQueryTaskSetPtr & query_task_set, const MPPTaskPtr & task, const bool isWaiting) +bool MinTSOScheduler::scheduleImp(const UInt64 tso, const MPPQueryTaskSetPtr & query_task_set, const MPPTaskPtr & task, const bool isWaiting, bool & has_error) { auto needed_threads = task->getNeededThreads(); auto check_for_new_min_tso = tso <= min_tso && estimated_thread_usage + needed_threads <= thread_hard_limit; @@ -187,6 +195,7 @@ bool MinTSOScheduler::scheduleImp(const UInt64 tso, const MPPQueryTaskSetPtr & q { if (tso <= min_tso) /// the min_tso query should fully run, otherwise throw errors here. { + has_error = true; auto msg = fmt::format("threads are unavailable for the query {} ({} min_tso {}) {}, need {}, but used {} of the thread hard limit {}, {} active and {} waiting queries.", tso, tso == min_tso ? "is" : "is newer than", min_tso, isWaiting ? "from the waiting set" : "when directly schedule it", needed_threads, estimated_thread_usage, thread_hard_limit, active_set.size(), waiting_set.size()); LOG_FMT_ERROR(log, "{}", msg); GET_METRIC(tiflash_task_scheduler, type_hard_limit_exceeded_count).Increment(); @@ -200,11 +209,12 @@ bool MinTSOScheduler::scheduleImp(const UInt64 tso, const MPPQueryTaskSetPtr & q { throw Exception(msg); } + return false; } if (!isWaiting) { waiting_set.insert(tso); - query_task_set->waiting_tasks.push(task); + query_task_set->waiting_tasks.push(task->getId()); GET_METRIC(tiflash_task_scheduler, type_waiting_queries_count).Set(waiting_set.size()); GET_METRIC(tiflash_task_scheduler, type_waiting_tasks_count).Increment(); } diff --git a/dbms/src/Flash/Mpp/MinTSOScheduler.h b/dbms/src/Flash/Mpp/MinTSOScheduler.h index 501aa772a33..17ab1f4dfa3 100644 --- a/dbms/src/Flash/Mpp/MinTSOScheduler.h +++ b/dbms/src/Flash/Mpp/MinTSOScheduler.h @@ -42,7 +42,7 @@ class MinTSOScheduler : private boost::noncopyable void releaseThreadsThenSchedule(const int needed_threads, MPPTaskManager & task_manager); private: - bool scheduleImp(const UInt64 tso, const MPPQueryTaskSetPtr & query_task_set, const MPPTaskPtr & task, const bool isWaiting); + bool scheduleImp(const UInt64 tso, const MPPQueryTaskSetPtr & query_task_set, const MPPTaskPtr & task, const bool isWaiting, bool & has_error); bool updateMinTSO(const UInt64 tso, const bool retired, const String msg); void scheduleWaitingQueries(MPPTaskManager & task_manager); bool isDisabled() From d1975b8bcbc8531b4f28033924ed05e9e2793d3d Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Mon, 23 May 2022 20:08:46 +0800 Subject: [PATCH 054/127] Enable LTO in release build process (#4924) ref pingcap/tiflash#4909 --- dbms/src/Common/TiFlashBuildInfo.cpp | 5 +++ .../include/common/config_common.h.in | 1 + .../scripts/build-tiflash-release.sh | 38 +++++++++++-------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/dbms/src/Common/TiFlashBuildInfo.cpp b/dbms/src/Common/TiFlashBuildInfo.cpp index b18fb63d93e..ff46e04384d 100644 --- a/dbms/src/Common/TiFlashBuildInfo.cpp +++ b/dbms/src/Common/TiFlashBuildInfo.cpp @@ -95,6 +95,11 @@ std::string getEnabledFeatures() #else "unwind", #endif +#endif + +// THINLTO +#if ENABLE_THINLTO + "thinlto", #endif }; return fmt::format("{}", fmt::join(features.begin(), features.end(), " ")); diff --git a/libs/libcommon/include/common/config_common.h.in b/libs/libcommon/include/common/config_common.h.in index e11ec418e7c..46f167ea683 100644 --- a/libs/libcommon/include/common/config_common.h.in +++ b/libs/libcommon/include/common/config_common.h.in @@ -13,3 +13,4 @@ #cmakedefine01 ENABLE_FAILPOINTS #cmakedefine01 USE_UNWIND #cmakedefine01 USE_LLVM_LIBUNWIND +#cmakedefine01 ENABLE_THINLTO diff --git a/release-centos7-llvm/scripts/build-tiflash-release.sh b/release-centos7-llvm/scripts/build-tiflash-release.sh index d3eda1dff4e..bb62e4743f6 100755 --- a/release-centos7-llvm/scripts/build-tiflash-release.sh +++ b/release-centos7-llvm/scripts/build-tiflash-release.sh @@ -13,12 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. - CMAKE_BUILD_TYPE=$1 CMAKE_PREFIX_PATH=$2 if [[ -z ${CMAKE_BUILD_TYPE} ]]; then - CMAKE_BUILD_TYPE="RELWITHDEBINFO" + CMAKE_BUILD_TYPE="RELWITHDEBINFO" fi DEFAULT_CMAKE_PREFIX_PATH="/usr/local/" @@ -27,15 +26,22 @@ DEFINE_CMAKE_PREFIX_PATH="-DCMAKE_PREFIX_PATH=${DEFAULT_CMAKE_PREFIX_PATH}" # https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html # CMAKE_PREFIX_PATH should be semicolon-separated list if [[ ${CMAKE_PREFIX_PATH} ]]; then - DEFINE_CMAKE_PREFIX_PATH="-DCMAKE_PREFIX_PATH=\"${DEFAULT_CMAKE_PREFIX_PATH};${CMAKE_PREFIX_PATH}\"" - echo "CMAKE_PREFIX_PATH is \"${DEFAULT_CMAKE_PREFIX_PATH};${CMAKE_PREFIX_PATH}\"" + DEFINE_CMAKE_PREFIX_PATH="-DCMAKE_PREFIX_PATH=\"${DEFAULT_CMAKE_PREFIX_PATH};${CMAKE_PREFIX_PATH}\"" + echo "CMAKE_PREFIX_PATH is \"${DEFAULT_CMAKE_PREFIX_PATH};${CMAKE_PREFIX_PATH}\"" fi set -ueox pipefail -SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" -SRCPATH=$(cd ${SCRIPTPATH}/../..; pwd -P) +SCRIPTPATH="$( + cd "$(dirname "$0")" + pwd -P +)" +SRCPATH=$( + cd ${SCRIPTPATH}/../.. + pwd -P +) NPROC=${NPROC:-$(nproc || grep -c ^processor /proc/cpuinfo)} +ENABLE_THINLTO=${ENABLE_THINLTO:-ON} INSTALL_DIR="${SRCPATH}/release-centos7-llvm/tiflash" rm -rf ${INSTALL_DIR} && mkdir -p ${INSTALL_DIR} @@ -43,14 +49,17 @@ rm -rf ${INSTALL_DIR} && mkdir -p ${INSTALL_DIR} BUILD_DIR="${SRCPATH}/release-centos7-llvm/build-release" rm -rf ${BUILD_DIR} && mkdir -p ${BUILD_DIR} && cd ${BUILD_DIR} -cmake "${SRCPATH}" ${DEFINE_CMAKE_PREFIX_PATH} \ - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ - -DENABLE_TESTING=OFF \ - -DENABLE_TESTS=OFF \ - -Wno-dev \ - -DUSE_CCACHE=OFF \ - -DRUN_HAVE_STD_REGEX=0 \ - -GNinja +cmake -S "${SRCPATH}" \ + ${DEFINE_CMAKE_PREFIX_PATH} \ + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ + -DENABLE_TESTING=OFF \ + -DENABLE_TESTS=OFF \ + -Wno-dev \ + -DUSE_CCACHE=OFF \ + -DRUN_HAVE_STD_REGEX=0 \ + -DENABLE_THINLTO=${ENABLE_THINLTO} \ + -DTHINLTO_JOBS=${NPROC} \ + -GNinja cmake --build . --target tiflash --parallel ${NPROC} cmake --install . --component=tiflash-release --prefix="${INSTALL_DIR}" @@ -59,4 +68,3 @@ cmake --install . --component=tiflash-release --prefix="${INSTALL_DIR}" unset LD_LIBRARY_PATH readelf -d "${INSTALL_DIR}/tiflash" ldd "${INSTALL_DIR}/tiflash" - From 44f099934f2ab0bff83b614eb3332dc29feafd9d Mon Sep 17 00:00:00 2001 From: hehechen Date: Tue, 24 May 2022 12:20:46 +0800 Subject: [PATCH 055/127] Revert the retry applyRecord when loadEdit (#4938) close pingcap/tiflash#4937 --- .../Storages/Page/V3/PageDirectoryFactory.cpp | 22 +-- .../Storages/Page/V3/PageDirectoryFactory.h | 5 +- .../Page/V3/tests/gtest_page_directory.cpp | 185 ------------------ 3 files changed, 6 insertions(+), 206 deletions(-) diff --git a/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp b/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp index 0592d1ddaa8..9d20e0a64ab 100644 --- a/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp @@ -113,21 +113,13 @@ void PageDirectoryFactory::loadEdit(const PageDirectoryPtr & dir, const PageEntr if (max_applied_ver < r.version) max_applied_ver = r.version; - // We can not avoid page id from being reused under some corner situation. Try to do gcInMemEntries - // and apply again to resolve the error. - if (bool ok = applyRecord(dir, r, /*throw_on_error*/ false); unlikely(!ok)) - { - dir->gcInMemEntries(); - applyRecord(dir, r, /*throw_on_error*/ true); - LOG_FMT_INFO(DB::Logger::get("PageDirectoryFactory"), "resolve from error status done, continue to restore"); - } + applyRecord(dir, r); } } -bool PageDirectoryFactory::applyRecord( +void PageDirectoryFactory::applyRecord( const PageDirectoryPtr & dir, - const PageEntriesEdit::EditRecord & r, - bool throw_on_error) + const PageEntriesEdit::EditRecord & r) { auto [iter, created] = dir->mvcc_table_directory.insert(std::make_pair(r.page_id, nullptr)); if (created) @@ -189,14 +181,8 @@ bool PageDirectoryFactory::applyRecord( catch (DB::Exception & e) { e.addMessage(fmt::format(" [type={}] [page_id={}] [ver={}]", r.type, r.page_id, restored_version)); - if (throw_on_error || e.code() != ErrorCodes::PS_DIR_APPLY_INVALID_STATUS) - { - throw e; - } - LOG_FMT_WARNING(DB::Logger::get("PageDirectoryFactory"), "try to resolve error during restore: {}", e.message()); - return false; + throw e; } - return true; } void PageDirectoryFactory::loadFromDisk(const PageDirectoryPtr & dir, WALStoreReaderPtr && reader) diff --git a/dbms/src/Storages/Page/V3/PageDirectoryFactory.h b/dbms/src/Storages/Page/V3/PageDirectoryFactory.h index e4b76bfba0d..185e8fd19a5 100644 --- a/dbms/src/Storages/Page/V3/PageDirectoryFactory.h +++ b/dbms/src/Storages/Page/V3/PageDirectoryFactory.h @@ -60,10 +60,9 @@ class PageDirectoryFactory private: void loadFromDisk(const PageDirectoryPtr & dir, WALStoreReaderPtr && reader); void loadEdit(const PageDirectoryPtr & dir, const PageEntriesEdit & edit); - static bool applyRecord( + static void applyRecord( const PageDirectoryPtr & dir, - const PageEntriesEdit::EditRecord & r, - bool throw_on_error); + const PageEntriesEdit::EditRecord & r); BlobStore::BlobStats * blob_stats = nullptr; }; diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp index dfa33824473..151b3b50657 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp @@ -2214,191 +2214,6 @@ try } CATCH -TEST_F(PageDirectoryGCTest, RestoreWithDuplicateID) -try -{ - auto restore_from_edit = [](const PageEntriesEdit & edit) { - auto ctx = ::DB::tests::TiFlashTestEnv::getContext(); - auto provider = ctx.getFileProvider(); - auto path = getTemporaryPath(); - PSDiskDelegatorPtr delegator = std::make_shared(path); - PageDirectoryFactory factory; - auto d = factory.createFromEdit(getCurrentTestName(), provider, delegator, edit); - return d; - }; - - const PageId target_id = 100; - // ========= 1 =======// - // Reuse same id: PUT_EXT/DEL/REF - { - PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntriesEdit edit; - edit.put(50, entry_50); - edit.putExternal(target_id); - edit.del(target_id); - // restart and reuse id=100 as ref to replace put_ext - edit.ref(target_id, 50); - - auto restored_dir = restore_from_edit(edit); - auto snap = restored_dir->createSnapshot(); - ASSERT_EQ(restored_dir->getNormalPageId(target_id, snap).low, 50); - } - // Reuse same id: PUT_EXT/DEL/PUT - { - PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_100{.file_id = 100, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntriesEdit edit; - edit.put(50, entry_50); - edit.putExternal(target_id); - edit.del(target_id); - // restart and reuse id=100 as put to replace put_ext - edit.put(target_id, entry_100); - - auto restored_dir = restore_from_edit(edit); - auto snap = restored_dir->createSnapshot(); - ASSERT_SAME_ENTRY(restored_dir->get(target_id, snap).second, entry_100); - } - - // ========= 1-invalid =======// - // Reuse same id: PUT_EXT/BEING REF/DEL/REF - { - PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntriesEdit edit; - edit.put(50, entry_50); - edit.putExternal(target_id); - edit.ref(101, target_id); - edit.del(target_id); - // restart and reuse id=100 as ref. Should not happen because 101 still ref to 100 - edit.ref(target_id, 50); - - ASSERT_THROW(restore_from_edit(edit);, DB::Exception); - } - // Reuse same id: PUT_EXT/BEING REF/DEL/PUT - { - PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_100{.file_id = 100, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntriesEdit edit; - edit.put(50, entry_50); - edit.putExternal(target_id); - edit.ref(101, target_id); - edit.del(target_id); - // restart and reuse id=100 as put. Should not happen because 101 still ref to 100 - edit.put(target_id, entry_100); - - ASSERT_THROW(restore_from_edit(edit);, DB::Exception); - } - - // ========= 2 =======// - // Reuse same id: PUT/DEL/REF - { - PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntriesEdit edit; - edit.put(50, entry_50); - edit.put(target_id, entry_50); - edit.del(target_id); - // restart and reuse id=100 as ref to replace put - edit.ref(target_id, 50); - - auto restored_dir = restore_from_edit(edit); - auto snap = restored_dir->createSnapshot(); - ASSERT_EQ(restored_dir->getNormalPageId(target_id, snap).low, 50); - } - // Reuse same id: PUT/DEL/PUT_EXT - { - PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntriesEdit edit; - edit.put(50, entry_50); - edit.put(target_id, entry_50); - edit.del(target_id); - // restart and reuse id=100 as external to replace put - edit.putExternal(target_id); - - auto restored_dir = restore_from_edit(edit); - auto snap = restored_dir->createSnapshot(); - auto ext_ids = restored_dir->getAliveExternalIds(TEST_NAMESPACE_ID); - ASSERT_EQ(ext_ids.size(), 1); - ASSERT_EQ(*ext_ids.begin(), target_id); - } - - // ========= 2-invalid =======// - // Reuse same id: PUT/BEING REF/DEL/REF - { - PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntriesEdit edit; - edit.put(50, entry_50); - edit.put(target_id, entry_50); - edit.ref(101, target_id); - edit.del(target_id); - // restart and reuse id=100 as ref to replace put - edit.ref(target_id, 50); - - ASSERT_THROW(restore_from_edit(edit);, DB::Exception); - } - // Reuse same id: PUT/BEING REF/DEL/PUT_EXT - { - PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntriesEdit edit; - edit.put(50, entry_50); - edit.put(target_id, entry_50); - edit.ref(101, target_id); - edit.del(target_id); - // restart and reuse id=100 as external to replace put - edit.putExternal(target_id); - - ASSERT_THROW(restore_from_edit(edit);, DB::Exception); - } - - // ========= 3 =======// - // Reuse same id: REF/DEL/PUT - { - PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_100{.file_id = 100, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntriesEdit edit; - edit.put(50, entry_50); - edit.ref(target_id, 50); - edit.del(target_id); - // restart and reuse id=100 as put to replace ref - edit.put(target_id, entry_100); - - auto restored_dir = restore_from_edit(edit); - auto snap = restored_dir->createSnapshot(); - ASSERT_SAME_ENTRY(restored_dir->get(target_id, snap).second, entry_100); - } - // Reuse same id: REF/DEL/PUT_EXT - { - PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntriesEdit edit; - edit.put(50, entry_50); - edit.ref(target_id, 50); - edit.del(target_id); - // restart and reuse id=100 as external to replace ref - edit.putExternal(target_id); - - auto restored_dir = restore_from_edit(edit); - auto snap = restored_dir->createSnapshot(); - auto ext_ids = restored_dir->getAliveExternalIds(TEST_NAMESPACE_ID); - ASSERT_EQ(ext_ids.size(), 1); - ASSERT_EQ(*ext_ids.begin(), target_id); - } - // Reuse same id: REF/DEL/REF another id - { - PageEntryV3 entry_50{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_51{.file_id = 2, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntriesEdit edit; - edit.put(50, entry_50); - edit.put(51, entry_51); - edit.ref(target_id, 50); - edit.del(target_id); - // restart and reuse id=target_id as external to replace put - edit.ref(target_id, 51); - - auto restored_dir = restore_from_edit(edit); - auto snap = restored_dir->createSnapshot(); - ASSERT_EQ(restored_dir->getNormalPageId(target_id, snap).low, 51); - } -} -CATCH - #undef INSERT_ENTRY_TO #undef INSERT_ENTRY #undef INSERT_ENTRY_ACQ_SNAP From 4448bacb2f4b4af159eaf91a09aa87f7f5d66ce6 Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Tue, 24 May 2022 13:56:46 +0800 Subject: [PATCH 056/127] Remove useless template instantiations (#4978) ref pingcap/tiflash#4909 --- .../src/Functions/FunctionsTiDBConversion.cpp | 15 ++ dbms/src/Functions/FunctionsTiDBConversion.h | 20 +-- .../Functions/tests/gtest_tidb_conversion.cpp | 146 +++++++++--------- 3 files changed, 94 insertions(+), 87 deletions(-) diff --git a/dbms/src/Functions/FunctionsTiDBConversion.cpp b/dbms/src/Functions/FunctionsTiDBConversion.cpp index 75c015c4bad..74daca2b7fe 100644 --- a/dbms/src/Functions/FunctionsTiDBConversion.cpp +++ b/dbms/src/Functions/FunctionsTiDBConversion.cpp @@ -46,4 +46,19 @@ void registerFunctionsTiDBConversion(FunctionFactory & factory) factory.registerFunction(); } +FunctionBasePtr FunctionBuilderTiDBCast::buildImpl( + const ColumnsWithTypeAndName & arguments, + const DataTypePtr & return_type, + const TiDB::TiDBCollatorPtr &) const +{ + DataTypes data_types(arguments.size()); + + for (size_t i = 0; i < arguments.size(); ++i) + data_types[i] = arguments[i].type; + + auto monotonicity = getMonotonicityInformation(arguments.front().type, return_type.get()); + return std::make_shared>(context, name, std::move(monotonicity), data_types, return_type, in_union, tidb_tp); +} + + } // namespace DB diff --git a/dbms/src/Functions/FunctionsTiDBConversion.h b/dbms/src/Functions/FunctionsTiDBConversion.h index 30251aac36d..bcd7856ee71 100644 --- a/dbms/src/Functions/FunctionsTiDBConversion.h +++ b/dbms/src/Functions/FunctionsTiDBConversion.h @@ -1743,6 +1743,7 @@ inline bool numberToDateTime(Int64 number, MyDateTime & result, DAGContext * ctx return getDatetime(number, result, ctx); } +template class ExecutableFunctionTiDBCast : public IExecutableFunction { public: @@ -1782,13 +1783,15 @@ class ExecutableFunctionTiDBCast : public IExecutableFunction const Context & context; }; +using MonotonicityForRange = std::function; + /// FunctionTiDBCast implements SQL cast function in TiDB /// The basic idea is to dispatch according to combinations of parameter types +template class FunctionTiDBCast final : public IFunctionBase { public: using WrapperType = std::function; - using MonotonicityForRange = std::function; FunctionTiDBCast(const Context & context, const char * name, MonotonicityForRange && monotonicity_for_range, const DataTypes & argument_types, const DataTypePtr & return_type, bool in_union_, const tipb::FieldType & tidb_tp_) : context(context) @@ -1805,7 +1808,7 @@ class FunctionTiDBCast final : public IFunctionBase ExecutableFunctionPtr prepare(const Block & /*sample_block*/) const override { - return std::make_shared( + return std::make_shared>( prepare(getArgumentTypes()[0], getReturnType()), name, in_union, @@ -2341,8 +2344,6 @@ class FunctionTiDBCast final : public IFunctionBase class FunctionBuilderTiDBCast : public IFunctionBuilder { public: - using MonotonicityForRange = FunctionTiDBCast::MonotonicityForRange; - static constexpr auto name = "tidb_cast"; static FunctionBuilderPtr create(const Context & context) { @@ -2369,16 +2370,7 @@ class FunctionBuilderTiDBCast : public IFunctionBuilder FunctionBasePtr buildImpl( const ColumnsWithTypeAndName & arguments, const DataTypePtr & return_type, - const TiDB::TiDBCollatorPtr &) const override - { - DataTypes data_types(arguments.size()); - - for (size_t i = 0; i < arguments.size(); ++i) - data_types[i] = arguments[i].type; - - auto monotonicity = getMonotonicityInformation(arguments.front().type, return_type.get()); - return std::make_shared(context, name, std::move(monotonicity), data_types, return_type, in_union, tidb_tp); - } + const TiDB::TiDBCollatorPtr &) const override; // use the last const string column's value as the return type name, in string representation like "Float64" DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override diff --git a/dbms/src/Functions/tests/gtest_tidb_conversion.cpp b/dbms/src/Functions/tests/gtest_tidb_conversion.cpp index d67ef49e108..5f885c2716f 100644 --- a/dbms/src/Functions/tests/gtest_tidb_conversion.cpp +++ b/dbms/src/Functions/tests/gtest_tidb_conversion.cpp @@ -1474,76 +1474,76 @@ TEST_F(TestTidbConversion, skipCheckOverflowIntToDeciaml) const ScaleType scale = 0; // int8(max_prec: 3) -> decimal32(max_prec: 9) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int8_ptr, prec_decimal32, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int8_ptr, prec_decimal32, scale)); // int16(max_prec: 5) -> decimal32(max_prec: 9) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int16_ptr, prec_decimal32, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int16_ptr, prec_decimal32, scale)); // int32(max_prec: 10) -> decimal32(max_prec: 9) - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int32_ptr, prec_decimal32, scale)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int32_ptr, prec_decimal32, scale)); // int64(max_prec: 20) -> decimal32(max_prec: 9) - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int64_ptr, prec_decimal32, scale)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int64_ptr, prec_decimal32, scale)); // uint8(max_prec: 3) -> decimal32(max_prec: 9) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint8_ptr, prec_decimal32, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint8_ptr, prec_decimal32, scale)); // uint16(max_prec: 5) -> decimal32(max_prec: 9) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint16_ptr, prec_decimal32, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint16_ptr, prec_decimal32, scale)); // uint32(max_prec: 10) -> decimal32(max_prec: 9) - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint32_ptr, prec_decimal32, scale)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint32_ptr, prec_decimal32, scale)); // uint64(max_prec: 20) -> decimal32(max_prec: 9) - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint64_ptr, prec_decimal32, scale)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint64_ptr, prec_decimal32, scale)); // int8(max_prec: 3) -> decimal64(max_prec: 18) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int8_ptr, prec_decimal64, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int8_ptr, prec_decimal64, scale)); // int16(max_prec: 5) -> decimal64(max_prec: 18) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int16_ptr, prec_decimal64, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int16_ptr, prec_decimal64, scale)); // int32(max_prec: 10) -> decimal64(max_prec: 18) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int32_ptr, prec_decimal64, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int32_ptr, prec_decimal64, scale)); // int64(max_prec: 20) -> decimal64(max_prec: 18) - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int64_ptr, prec_decimal64, scale)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int64_ptr, prec_decimal64, scale)); // uint8(max_prec: 3) -> decimal64(max_prec: 18) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint8_ptr, prec_decimal64, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint8_ptr, prec_decimal64, scale)); // uint16(max_prec: 5) -> decimal64(max_prec: 18) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint16_ptr, prec_decimal64, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint16_ptr, prec_decimal64, scale)); // uint32(max_prec: 10) -> decimal64(max_prec: 18) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint32_ptr, prec_decimal64, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint32_ptr, prec_decimal64, scale)); // uint64(max_prec: 20) -> decimal64(max_prec: 18) - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint64_ptr, prec_decimal64, scale)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint64_ptr, prec_decimal64, scale)); // int8(max_prec: 3) -> decimal128(max_prec: 38) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int8_ptr, prec_decimal128, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int8_ptr, prec_decimal128, scale)); // int16(max_prec: 5) -> decimal128(max_prec: 38) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int16_ptr, prec_decimal128, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int16_ptr, prec_decimal128, scale)); // int32(max_prec: 10) -> decimal128(max_prec: 38) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int32_ptr, prec_decimal128, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int32_ptr, prec_decimal128, scale)); // int64(max_prec: 20) -> decimal128(max_prec: 38) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int64_ptr, prec_decimal128, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int64_ptr, prec_decimal128, scale)); // uint8(max_prec: 3) -> decimal128(max_prec: 38) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint8_ptr, prec_decimal128, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint8_ptr, prec_decimal128, scale)); // uint16(max_prec: 5) -> decimal128(max_prec: 38) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint16_ptr, prec_decimal128, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint16_ptr, prec_decimal128, scale)); // uint32(max_prec: 10) -> decimal128(max_prec: 38) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint32_ptr, prec_decimal128, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint32_ptr, prec_decimal128, scale)); // uint64(max_prec: 20) -> decimal128(max_prec: 38) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint64_ptr, prec_decimal128, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint64_ptr, prec_decimal128, scale)); // int8(max_prec: 3) -> decimal256(max_prec: 65) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int8_ptr, prec_decimal256, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int8_ptr, prec_decimal256, scale)); // int16(max_prec: 5) -> decimal256(max_prec: 65) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int16_ptr, prec_decimal256, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int16_ptr, prec_decimal256, scale)); // int32(max_prec: 10) -> decimal256(max_prec: 65) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int32_ptr, prec_decimal256, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int32_ptr, prec_decimal256, scale)); // int64(max_prec: 20) -> decimal256(max_prec: 65) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int64_ptr, prec_decimal256, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int64_ptr, prec_decimal256, scale)); // uint8(max_prec: 3) -> decimal256(max_prec: 65) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint8_ptr, prec_decimal256, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint8_ptr, prec_decimal256, scale)); // uint16(max_prec: 5) -> decimal256(max_prec: 65) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint16_ptr, prec_decimal256, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint16_ptr, prec_decimal256, scale)); // uint32(max_prec: 10) -> decimal256(max_prec: 65) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint32_ptr, prec_decimal256, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint32_ptr, prec_decimal256, scale)); // uint64(max_prec: 20) -> decimal256(max_prec: 65) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(uint64_ptr, prec_decimal256, scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(uint64_ptr, prec_decimal256, scale)); } TEST_F(TestTidbConversion, skipCheckOverflowDecimalToDeciaml) @@ -1551,24 +1551,24 @@ TEST_F(TestTidbConversion, skipCheckOverflowDecimalToDeciaml) DataTypePtr decimal32_ptr_8_3 = createDecimal(8, 3); DataTypePtr decimal32_ptr_8_2 = createDecimal(8, 2); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(decimal32_ptr_8_2, 8, 3)); - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(decimal32_ptr_8_3, 8, 2)); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(decimal32_ptr_8_2, 7, 5)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(decimal32_ptr_8_2, 8, 3)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(decimal32_ptr_8_3, 8, 2)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(decimal32_ptr_8_2, 7, 5)); - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(decimal32_ptr_8_2, 9, 3)); - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(decimal32_ptr_8_2, 9, 1)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(decimal32_ptr_8_2, 9, 3)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(decimal32_ptr_8_2, 9, 1)); DataTypePtr decimal32_ptr_6_4 = createDecimal(6, 4); // decimal(6, 4) -> decimal(5, 3) // because select cast(99.9999 as decimal(5, 3)); -> 100.000 is greater than 99.999. - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(decimal32_ptr_6_4, 5, 3)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(decimal32_ptr_6_4, 5, 3)); // decimal(6, 4) -> decimal(7, 5) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(decimal32_ptr_6_4, 7, 5)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(decimal32_ptr_6_4, 7, 5)); // decimal(6, 4) -> decimal(6, 5) - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(decimal32_ptr_6_4, 6, 5)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(decimal32_ptr_6_4, 6, 5)); // decimal(6, 4) -> decimal(8, 5) - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(decimal32_ptr_6_4, 8, 5)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(decimal32_ptr_6_4, 8, 5)); } TEST_F(TestTidbConversion, skipCheckOverflowEnumToDecimal) @@ -1583,15 +1583,15 @@ TEST_F(TestTidbConversion, skipCheckOverflowEnumToDecimal) enum16_values.push_back({"b1", 2000}); DataTypePtr enum16_ptr = std::make_shared(enum16_values); - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(enum8_ptr, 3, 0)); - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(enum8_ptr, 4, 1)); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(enum8_ptr, 2, 0)); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(enum8_ptr, 4, 2)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(enum8_ptr, 3, 0)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(enum8_ptr, 4, 1)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(enum8_ptr, 2, 0)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(enum8_ptr, 4, 2)); - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(enum16_ptr, 5, 0)); - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(enum16_ptr, 6, 1)); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(enum16_ptr, 4, 0)); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(enum16_ptr, 6, 2)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(enum16_ptr, 5, 0)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(enum16_ptr, 6, 1)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(enum16_ptr, 4, 0)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(enum16_ptr, 6, 2)); } TEST_F(TestTidbConversion, skipCheckOverflowMyDateTimeToDeciaml) @@ -1600,18 +1600,18 @@ TEST_F(TestTidbConversion, skipCheckOverflowMyDateTimeToDeciaml) DataTypePtr datetime_ptr_fsp_5 = std::make_shared(5); // rule for no fsp: 14 + to_scale <= to_prec. - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(datetime_ptr_no_fsp, 5, 3)); - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(datetime_ptr_no_fsp, 18, 3)); - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(datetime_ptr_no_fsp, 17, 3)); - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(datetime_ptr_no_fsp, 18, 4)); - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(datetime_ptr_no_fsp, 14, 0)); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(datetime_ptr_no_fsp, 14, 1)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(datetime_ptr_no_fsp, 5, 3)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(datetime_ptr_no_fsp, 18, 3)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(datetime_ptr_no_fsp, 17, 3)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(datetime_ptr_no_fsp, 18, 4)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(datetime_ptr_no_fsp, 14, 0)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(datetime_ptr_no_fsp, 14, 1)); // rule for fsp: 20 + scale_diff <= to_prec. // 20 + (3 - 6 + 1) = 18 - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(datetime_ptr_fsp_5, 19, 3)); - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(datetime_ptr_fsp_5, 18, 3)); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(datetime_ptr_fsp_5, 17, 3)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(datetime_ptr_fsp_5, 19, 3)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(datetime_ptr_fsp_5, 18, 3)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(datetime_ptr_fsp_5, 17, 3)); } TEST_F(TestTidbConversion, skipCheckOverflowMyDateToDeciaml) @@ -1619,30 +1619,30 @@ TEST_F(TestTidbConversion, skipCheckOverflowMyDateToDeciaml) DataTypePtr date_ptr = std::make_shared(); // rule: 8 + to_scale <= to_prec. - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(date_ptr, 11, 3)); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(date_ptr, 11, 4)); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(date_ptr, 10, 3)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(date_ptr, 11, 3)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(date_ptr, 11, 4)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(date_ptr, 10, 3)); } TEST_F(TestTidbConversion, skipCheckOverflowOtherToDecimal) { // float and string not support skip overflow check. DataTypePtr string_ptr = std::make_shared(); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(string_ptr, 1, 0)); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(string_ptr, 60, 1)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(string_ptr, 1, 0)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(string_ptr, 60, 1)); DataTypePtr float32_ptr = std::make_shared(); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(float32_ptr, 1, 0)); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(float32_ptr, 60, 1)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(float32_ptr, 1, 0)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(float32_ptr, 60, 1)); DataTypePtr float64_ptr = std::make_shared(); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(float64_ptr, 1, 0)); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(float64_ptr, 60, 1)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(float64_ptr, 1, 0)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(float64_ptr, 60, 1)); // cast duration to decimal is not supported to push down to tiflash for now. DataTypePtr duration_ptr = std::make_shared(); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(duration_ptr, 1, 0)); - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(duration_ptr, 60, 1)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(duration_ptr, 1, 0)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(duration_ptr, 60, 1)); } // check if template argument of CastInternalType is correct or not. @@ -1654,7 +1654,7 @@ try ScaleType to_scale = 3; DataTypePtr int8_ptr = std::make_shared(); // from_prec(3) + to_scale(3) <= Decimal32::prec(9), so we **CAN** skip check overflow. - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int8_ptr, to_prec, to_scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int8_ptr, to_prec, to_scale)); // from_prec(3) + to_scale(3) <= Int32::real_prec(10) - 1, so CastInternalType should be **Int32**. ASSERT_COLUMN_EQ( @@ -1669,7 +1669,7 @@ try to_prec = 9; to_scale = 7; // from_prec(3) + to_scale(7) > Decimal32::prec(9), so we **CANNOT** skip check overflow. - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int8_ptr, to_prec, to_scale)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int8_ptr, to_prec, to_scale)); // from_prec(3) + to_scale(7) > Int32::real_prec(10) - 1, so CastInternalType should be **Int64**. DAGContext * dag_context = context.getDAGContext(); @@ -1690,7 +1690,7 @@ try to_prec = 40; to_scale = 20; DataTypePtr int64_ptr = std::make_shared(); - ASSERT_TRUE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int64_ptr, to_prec, to_scale)); + ASSERT_TRUE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int64_ptr, to_prec, to_scale)); // from_prec(19) + to_scale(20) > Int128::real_prec(39) - 1, so CastInternalType should be **Int256**. ASSERT_COLUMN_EQ( @@ -1705,7 +1705,7 @@ try // from_prec(19) + to_scale(20) > Decimal256::prec(38), so we **CANNOT** skip check overflow. to_prec = 38; to_scale = 20; - ASSERT_FALSE(FunctionTiDBCast::canSkipCheckOverflowForDecimal(int64_ptr, to_prec, to_scale)); + ASSERT_FALSE(FunctionTiDBCast<>::canSkipCheckOverflowForDecimal(int64_ptr, to_prec, to_scale)); // from_prec(19) + to_scale(20) > Int128::real_prec(39) - 1, so CastInternalType should be **Int256**. ASSERT_COLUMN_EQ( From 1e64c5d95c617676b99dcc0055253fdc56139e7a Mon Sep 17 00:00:00 2001 From: SeaRise Date: Tue, 24 May 2022 14:34:46 +0800 Subject: [PATCH 057/127] fix clang tidy err for `ExpressionAnalyzer`, `Join`, `StorageJoin`, `DAGQueryBlockInterpreter` (#4961) ref pingcap/tiflash#4605 --- .../Coprocessor/DAGQueryBlockInterpreter.cpp | 3 +- dbms/src/Interpreters/ExpressionAnalyzer.cpp | 120 +++++++++--------- dbms/src/Interpreters/Join.cpp | 10 +- dbms/src/Storages/StorageJoin.cpp | 6 +- 4 files changed, 67 insertions(+), 72 deletions(-) diff --git a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp index 8d91b8b23e9..7dfe0ebd871 100644 --- a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp +++ b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp @@ -51,7 +51,6 @@ #include #include #include -#include #include @@ -91,7 +90,7 @@ struct AnalysisResult Names aggregation_keys; TiDB::TiDBCollators aggregation_collators; AggregateDescriptions aggregate_descriptions; - bool is_final_agg; + bool is_final_agg = false; }; AnalysisResult analyzeExpressions( diff --git a/dbms/src/Interpreters/ExpressionAnalyzer.cpp b/dbms/src/Interpreters/ExpressionAnalyzer.cpp index eb07e2d541e..cd947d08953 100644 --- a/dbms/src/Interpreters/ExpressionAnalyzer.cpp +++ b/dbms/src/Interpreters/ExpressionAnalyzer.cpp @@ -247,16 +247,16 @@ void ExpressionAnalyzer::translateQualifiedNames() if (!select_query || !select_query->tables || select_query->tables->children.empty()) return; - ASTTablesInSelectQueryElement & element = static_cast(*select_query->tables->children[0]); + auto & element = static_cast(*select_query->tables->children[0]); if (!element.table_expression) /// This is ARRAY JOIN without a table at the left side. return; - ASTTableExpression & table_expression = static_cast(*element.table_expression); + auto & table_expression = static_cast(*element.table_expression); if (table_expression.database_and_table_name) { - const ASTIdentifier & identifier = static_cast(*table_expression.database_and_table_name); + const auto & identifier = static_cast(*table_expression.database_and_table_name); alias = identifier.tryGetAlias(); @@ -291,7 +291,7 @@ void ExpressionAnalyzer::translateQualifiedNames() void ExpressionAnalyzer::translateQualifiedNamesImpl(ASTPtr & ast, const String & database_name, const String & table_name, const String & alias) { - if (ASTIdentifier * ident = typeid_cast(ast.get())) + if (auto * ident = typeid_cast(ast.get())) { if (ident->kind == ASTIdentifier::Column) { @@ -352,7 +352,7 @@ void ExpressionAnalyzer::translateQualifiedNamesImpl(ASTPtr & ast, const String if (ast->children.size() != 1) throw Exception("Logical error: qualified asterisk must have exactly one child", ErrorCodes::LOGICAL_ERROR); - ASTIdentifier * ident = typeid_cast(ast->children[0].get()); + auto * ident = typeid_cast(ast->children[0].get()); if (!ident) throw Exception("Logical error: qualified asterisk must have identifier as its child", ErrorCodes::LOGICAL_ERROR); @@ -396,7 +396,7 @@ void ExpressionAnalyzer::optimizeIfWithConstantCondition() bool ExpressionAnalyzer::tryExtractConstValueFromCondition(const ASTPtr & condition, bool & value) const { /// numeric constant in condition - if (const ASTLiteral * literal = typeid_cast(condition.get())) + if (const auto * literal = typeid_cast(condition.get())) { if (literal->value.getType() == Field::Types::Int64 || literal->value.getType() == Field::Types::UInt64) { @@ -406,14 +406,14 @@ bool ExpressionAnalyzer::tryExtractConstValueFromCondition(const ASTPtr & condit } /// cast of numeric constant in condition to UInt8 - if (const ASTFunction * function = typeid_cast(condition.get())) + if (const auto * function = typeid_cast(condition.get())) { if (function->name == "CAST") { - if (ASTExpressionList * expr_list = typeid_cast(function->arguments.get())) + if (auto * expr_list = typeid_cast(function->arguments.get())) { const ASTPtr & type_ast = expr_list->children.at(1); - if (const ASTLiteral * type_literal = typeid_cast(type_ast.get())) + if (const auto * type_literal = typeid_cast(type_ast.get())) { if (type_literal->value.getType() == Field::Types::String && type_literal->value.get() == "UInt8") return tryExtractConstValueFromCondition(expr_list->children.at(0), value); @@ -432,7 +432,7 @@ void ExpressionAnalyzer::optimizeIfWithConstantConditionImpl(ASTPtr & current_as for (ASTPtr & child : current_ast->children) { - ASTFunction * function_node = typeid_cast(child.get()); + auto * function_node = typeid_cast(child.get()); if (!function_node || function_node->name != "if") { optimizeIfWithConstantConditionImpl(child, aliases); @@ -440,7 +440,7 @@ void ExpressionAnalyzer::optimizeIfWithConstantConditionImpl(ASTPtr & current_as } optimizeIfWithConstantConditionImpl(function_node->arguments, aliases); - ASTExpressionList * args = typeid_cast(function_node->arguments.get()); + auto * args = typeid_cast(function_node->arguments.get()); ASTPtr condition_expr = args->children.at(0); ASTPtr then_expr = args->children.at(1); @@ -603,13 +603,13 @@ void ExpressionAnalyzer::initGlobalSubqueries(ASTPtr & ast) /// Bottom-up actions. - if (ASTFunction * node = typeid_cast(ast.get())) + if (auto * node = typeid_cast(ast.get())) { /// For GLOBAL IN. if (do_global && (node->name == "globalIn" || node->name == "globalNotIn")) addExternalStorage(node->arguments->children.at(1)); } - else if (ASTTablesInSelectQueryElement * node = typeid_cast(ast.get())) + else if (auto * node = typeid_cast(ast.get())) { /// For GLOBAL JOIN. if (do_global && node->table_join @@ -628,7 +628,7 @@ void ExpressionAnalyzer::findExternalTables(ASTPtr & ast) /// If table type identifier StoragePtr external_storage; - if (ASTIdentifier * node = typeid_cast(ast.get())) + if (auto * node = typeid_cast(ast.get())) if (node->kind == ASTIdentifier::Table) if ((external_storage = context.tryGetExternalTable(node->name))) external_tables[node->name] = external_storage; @@ -658,8 +658,8 @@ static std::shared_ptr interpretSubquery( const Names & required_source_columns) { /// Subquery or table name. The name of the table is similar to the subquery `SELECT * FROM t`. - const ASTSubquery * subquery = typeid_cast(subquery_or_table_name.get()); - const ASTIdentifier * table = typeid_cast(subquery_or_table_name.get()); + const auto * subquery = typeid_cast(subquery_or_table_name.get()); + const auto * table = typeid_cast(subquery_or_table_name.get()); if (!subquery && !table) throw Exception("IN/JOIN supports only SELECT subqueries.", ErrorCodes::BAD_ARGUMENTS); @@ -721,9 +721,9 @@ static std::shared_ptr interpretSubquery( std::set all_column_names; std::set assigned_column_names; - if (ASTSelectWithUnionQuery * select_with_union = typeid_cast(query.get())) + if (auto * select_with_union = typeid_cast(query.get())) { - if (ASTSelectQuery * select = typeid_cast(select_with_union->list_of_selects->children.at(0).get())) + if (auto * select = typeid_cast(select_with_union->list_of_selects->children.at(0).get())) { for (auto & expr : select->select_expression_list->children) all_column_names.insert(expr->getAliasOrColumnName()); @@ -973,7 +973,7 @@ void ExpressionAnalyzer::normalizeTreeImpl( { /// `IN t` can be specified, where t is a table, which is equivalent to `IN (SELECT * FROM t)`. if (functionIsInOrGlobalInOperator(func_node->name)) - if (ASTIdentifier * right = typeid_cast(func_node->arguments->children.at(1).get())) + if (auto * right = typeid_cast(func_node->arguments->children.at(1).get())) if (!aliases.count(right->name)) right->kind = ASTIdentifier::Table; @@ -1030,7 +1030,7 @@ void ExpressionAnalyzer::normalizeTreeImpl( } } } - else if (ASTExpressionList * node = typeid_cast(ast.get())) + else if (auto * node = typeid_cast(ast.get())) { // Get hidden column names of mutable storage OrderedNameSet filtered_names; @@ -1068,14 +1068,14 @@ void ExpressionAnalyzer::normalizeTreeImpl( } } } - else if (ASTTablesInSelectQueryElement * node = typeid_cast(ast.get())) + else if (auto * node = typeid_cast(ast.get())) { if (node->table_expression) { auto & database_and_table_name = static_cast(*node->table_expression).database_and_table_name; if (database_and_table_name) { - if (ASTIdentifier * right = typeid_cast(database_and_table_name.get())) + if (auto * right = typeid_cast(database_and_table_name.get())) { right->kind = ASTIdentifier::Table; } @@ -1127,7 +1127,7 @@ void ExpressionAnalyzer::normalizeTreeImpl( } /// If the WHERE clause or HAVING consists of a single alias, the reference must be replaced not only in children, but also in where_expression and having_expression. - if (ASTSelectQuery * select = typeid_cast(ast.get())) + if (auto * select = typeid_cast(ast.get())) { if (select->prewhere_expression) normalizeTreeImpl(select->prewhere_expression, finished_asts, current_asts, current_alias, level + 1); @@ -1211,7 +1211,7 @@ void ExpressionAnalyzer::executeScalarSubqueriesImpl(ASTPtr & ast) * The request is sent to remote servers with already substituted constants. */ - if (ASTSubquery * subquery = typeid_cast(ast.get())) + if (auto * subquery = typeid_cast(ast.get())) { Context subquery_context = context; Settings subquery_settings = context.getSettings(); @@ -1283,7 +1283,7 @@ void ExpressionAnalyzer::executeScalarSubqueriesImpl(ASTPtr & ast) /** Don't descend into subqueries in arguments of IN operator. * But if an argument is not subquery, than deeper may be scalar subqueries and we need to descend in them. */ - ASTFunction * func = typeid_cast(ast.get()); + auto * func = typeid_cast(ast.get()); if (func && functionIsInOrGlobalInOperator(func->name)) { @@ -1424,7 +1424,7 @@ void ExpressionAnalyzer::optimizeOrderBy() for (const auto & elem : elems) { String name = elem->children.front()->getColumnName(); - const ASTOrderByElement & order_by_elem = typeid_cast(*elem); + const auto & order_by_elem = typeid_cast(*elem); if (elems_set.emplace(name, order_by_elem.collation ? order_by_elem.collation->getColumnName() : "").second) unique_elems.emplace_back(elem); @@ -1496,14 +1496,14 @@ void ExpressionAnalyzer::makeSetsForIndexImpl(const ASTPtr & node, const Block & continue; /// Don't dive into lambda functions - const ASTFunction * func = typeid_cast(child.get()); + const auto * func = typeid_cast(child.get()); if (func && func->name == "lambda") continue; makeSetsForIndexImpl(child, sample_block); } - const ASTFunction * func = typeid_cast(node.get()); + const auto * func = typeid_cast(node.get()); if (func && functionIsInOperator(func->name)) { const IAST & args = *func->arguments; @@ -1551,7 +1551,7 @@ void ExpressionAnalyzer::makeSet(const ASTFunction * node, const Block & sample_ return; /// If the subquery or table name for SELECT. - const ASTIdentifier * identifier = typeid_cast(arg.get()); + const auto * identifier = typeid_cast(arg.get()); if (typeid_cast(arg.get()) || identifier) { /// We get the stream of blocks for the subquery. Create Set and put it in place of the subquery. @@ -1566,7 +1566,7 @@ void ExpressionAnalyzer::makeSet(const ASTFunction * node, const Block & sample_ if (table) { - StorageSet * storage_set = dynamic_cast(table.get()); + auto * storage_set = dynamic_cast(table.get()); if (storage_set) { @@ -1650,7 +1650,7 @@ void ExpressionAnalyzer::makeExplicitSet(const ASTFunction * node, const Block & DataTypes set_element_types; const ASTPtr & left_arg = args.children.at(0); - const ASTFunction * left_arg_tuple = typeid_cast(left_arg.get()); + const auto * left_arg_tuple = typeid_cast(left_arg.get()); /** NOTE If tuple in left hand side specified non-explicitly * Example: identity((a, b)) IN ((1, 2), (3, 4)) @@ -1672,7 +1672,7 @@ void ExpressionAnalyzer::makeExplicitSet(const ASTFunction * node, const Block & bool single_value = false; ASTPtr elements_ast = arg; - if (ASTFunction * set_func = typeid_cast(arg.get())) + if (auto * set_func = typeid_cast(arg.get())) { if (set_func->name == "tuple") { @@ -1684,7 +1684,7 @@ void ExpressionAnalyzer::makeExplicitSet(const ASTFunction * node, const Block & else { /// Distinguish the case `(x, y) in ((1, 2), (3, 4))` from the case `(x, y) in (1, 2)`. - ASTFunction * any_element = typeid_cast(set_func->arguments->children.at(0).get()); + auto * any_element = typeid_cast(set_func->arguments->children.at(0).get()); if (set_element_types.size() >= 2 && (!any_element || any_element->name != "tuple")) single_value = true; else @@ -1902,7 +1902,7 @@ void ExpressionAnalyzer::getArrayJoinedColumnsImpl(const ASTPtr & ast) if (typeid_cast(ast.get())) return; - if (ASTIdentifier * node = typeid_cast(ast.get())) + if (auto * node = typeid_cast(ast.get())) { if (node->kind == ASTIdentifier::Column) { @@ -1955,7 +1955,7 @@ void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries, && actions_stack.getSampleBlock().has(ast->getColumnName())) return; - if (ASTIdentifier * node = typeid_cast(ast.get())) + if (auto * node = typeid_cast(ast.get())) { std::string name = node->getColumnName(); if (!only_consts && !actions_stack.getSampleBlock().has(name)) @@ -1973,7 +1973,7 @@ void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries, ErrorCodes::NOT_AN_AGGREGATE); } } - else if (ASTFunction * node = typeid_cast(ast.get())) + else if (auto * node = typeid_cast(ast.get())) { if (node->name == "lambda") throw Exception("Unexpected lambda expression", ErrorCodes::UNEXPECTED_EXPRESSION); @@ -2049,14 +2049,14 @@ void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries, for (auto & child : node->arguments->children) { - ASTFunction * lambda = typeid_cast(child.get()); + auto * lambda = typeid_cast(child.get()); if (lambda && lambda->name == "lambda") { /// If the argument is a lambda expression, just remember its approximate type. if (lambda->arguments->children.size() != 2) throw Exception("lambda requires two arguments", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - ASTFunction * lambda_args_tuple = typeid_cast(lambda->arguments->children.at(0).get()); + auto * lambda_args_tuple = typeid_cast(lambda->arguments->children.at(0).get()); if (!lambda_args_tuple || lambda_args_tuple->name != "tuple") throw Exception("First argument of lambda must be a tuple", ErrorCodes::TYPE_MISMATCH); @@ -2126,17 +2126,17 @@ void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries, { ASTPtr child = node->arguments->children[i]; - ASTFunction * lambda = typeid_cast(child.get()); + auto * lambda = typeid_cast(child.get()); if (lambda && lambda->name == "lambda") { - const DataTypeFunction * lambda_type = typeid_cast(argument_types[i].get()); - ASTFunction * lambda_args_tuple = typeid_cast(lambda->arguments->children.at(0).get()); + const auto * lambda_type = typeid_cast(argument_types[i].get()); + auto * lambda_args_tuple = typeid_cast(lambda->arguments->children.at(0).get()); ASTs lambda_arg_asts = lambda_args_tuple->arguments->children; NamesAndTypesList lambda_arguments; for (size_t j = 0; j < lambda_arg_asts.size(); ++j) { - ASTIdentifier * identifier = typeid_cast(lambda_arg_asts[j].get()); + auto * identifier = typeid_cast(lambda_arg_asts[j].get()); if (!identifier) throw Exception("lambda argument declarations must be identifiers", ErrorCodes::TYPE_MISMATCH); @@ -2192,7 +2192,7 @@ void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries, if (arguments_present) actions_stack.addAction(ExpressionAction::applyFunction(function_builder, argument_names, node->getColumnName())); } - else if (ASTLiteral * node = typeid_cast(ast.get())) + else if (auto * node = typeid_cast(ast.get())) { DataTypePtr type = applyVisitor(FieldToDataType(), node->value); @@ -2232,7 +2232,7 @@ void ExpressionAnalyzer::getAggregates(const ASTPtr & ast, ExpressionActionsPtr return; } - const ASTFunction * node = typeid_cast(ast.get()); + const auto * node = typeid_cast(ast.get()); if (node && AggregateFunctionFactory::instance().isAggregateFunctionName(node->name)) { has_aggregation = true; @@ -2276,7 +2276,7 @@ void ExpressionAnalyzer::getAggregates(const ASTPtr & ast, ExpressionActionsPtr void ExpressionAnalyzer::assertNoAggregates(const ASTPtr & ast, const char * description) { - const ASTFunction * node = typeid_cast(ast.get()); + const auto * node = typeid_cast(ast.get()); if (node && AggregateFunctionFactory::instance().isAggregateFunctionName(node->name)) throw Exception("Aggregate function " + node->getColumnName() @@ -2365,9 +2365,9 @@ bool ExpressionAnalyzer::appendJoin(ExpressionActionsChain & chain, bool only_ty initChain(chain, source_columns); ExpressionActionsChain::Step & step = chain.steps.back(); - const ASTTablesInSelectQueryElement & join_element = static_cast(*select_query->join()); - const ASTTableJoin & join_params = static_cast(*join_element.table_join); - const ASTTableExpression & table_to_join = static_cast(*join_element.table_expression); + const auto & join_element = static_cast(*select_query->join()); + const auto & join_params = static_cast(*join_element.table_join); + const auto & table_to_join = static_cast(*join_element.table_expression); if (join_params.using_expression_list) getRootActions(join_params.using_expression_list, only_types, false, step.actions); @@ -2386,7 +2386,7 @@ bool ExpressionAnalyzer::appendJoin(ExpressionActionsChain & chain, bool only_ty if (table) { - StorageJoin * storage_join = dynamic_cast(table.get()); + auto * storage_join = dynamic_cast(table.get()); if (storage_join) { @@ -2544,7 +2544,7 @@ bool ExpressionAnalyzer::appendOrderBy(ExpressionActionsChain & chain, bool only ASTs asts = select_query->order_expression_list->children; for (const auto & i : asts) { - ASTOrderByElement * ast = typeid_cast(i.get()); + auto * ast = typeid_cast(i.get()); if (!ast || ast->children.empty()) throw Exception("Bad order expression AST", ErrorCodes::UNKNOWN_TYPE_OF_AST_NODE); ASTPtr order_expression = ast->children.at(0); @@ -2598,7 +2598,7 @@ void ExpressionAnalyzer::appendProjectResult(ExpressionActionsChain & chain) con void ExpressionAnalyzer::getActionsBeforeAggregation(const ASTPtr & ast, ExpressionActionsPtr & actions, bool no_subqueries) { - ASTFunction * node = typeid_cast(ast.get()); + auto * node = typeid_cast(ast.get()); if (node && AggregateFunctionFactory::instance().isAggregateFunctionName(node->name)) for (auto & argument : node->arguments->children) @@ -2714,7 +2714,7 @@ void ExpressionAnalyzer::collectUsedColumns() NameSet required_joined_columns; getRequiredSourceColumnsImpl(ast, available_columns, required, ignored, available_joined_columns, required_joined_columns); - for (NamesAndTypesList::iterator it = columns_added_by_join.begin(); it != columns_added_by_join.end();) + for (auto it = columns_added_by_join.begin(); it != columns_added_by_join.end();) { if (required_joined_columns.count(it->name)) ++it; @@ -2737,7 +2737,7 @@ void ExpressionAnalyzer::collectUsedColumns() NameSet unknown_required_source_columns = required; - for (NamesAndTypesList::iterator it = source_columns.begin(); it != source_columns.end();) + for (auto it = source_columns.begin(); it != source_columns.end();) { unknown_required_source_columns.erase(it->name); @@ -2777,8 +2777,8 @@ void ExpressionAnalyzer::collectJoinedColumns(NameSet & joined_columns, NamesAnd if (!node) return; - const ASTTableJoin & table_join = static_cast(*node->table_join); - const ASTTableExpression & table_expression = static_cast(*node->table_expression); + const auto & table_join = static_cast(*node->table_join); + const auto & table_expression = static_cast(*node->table_expression); Block nested_result_sample; if (table_expression.database_and_table_name) @@ -2847,7 +2847,7 @@ void ExpressionAnalyzer::getRequiredSourceColumnsImpl(const ASTPtr & ast, * - we put identifiers available from JOIN in required_joined_columns. */ - if (ASTIdentifier * node = typeid_cast(ast.get())) + if (auto * node = typeid_cast(ast.get())) { if (node->kind == ASTIdentifier::Column && !ignored_names.count(node->name) @@ -2863,14 +2863,14 @@ void ExpressionAnalyzer::getRequiredSourceColumnsImpl(const ASTPtr & ast, return; } - if (ASTFunction * node = typeid_cast(ast.get())) + if (auto * node = typeid_cast(ast.get())) { if (node->name == "lambda") { if (node->arguments->children.size() != 2) throw Exception("lambda requires two arguments", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - ASTFunction * lambda_args_tuple = typeid_cast(node->arguments->children.at(0).get()); + auto * lambda_args_tuple = typeid_cast(node->arguments->children.at(0).get()); if (!lambda_args_tuple || lambda_args_tuple->name != "tuple") throw Exception("First argument of lambda must be a tuple", ErrorCodes::TYPE_MISMATCH); @@ -2879,7 +2879,7 @@ void ExpressionAnalyzer::getRequiredSourceColumnsImpl(const ASTPtr & ast, Names added_ignored; for (auto & child : lambda_args_tuple->arguments->children) { - ASTIdentifier * identifier = typeid_cast(child.get()); + auto * identifier = typeid_cast(child.get()); if (!identifier) throw Exception("lambda argument declarations must be identifiers", ErrorCodes::TYPE_MISMATCH); @@ -2926,7 +2926,7 @@ void ExpressionAnalyzer::getRequiredSourceColumnsImpl(const ASTPtr & ast, static bool hasArrayJoin(const ASTPtr & ast) { - if (const ASTFunction * function = typeid_cast(&*ast)) + if (const auto * function = typeid_cast(&*ast)) if (function->name == "arrayJoin") return true; diff --git a/dbms/src/Interpreters/Join.cpp b/dbms/src/Interpreters/Join.cpp index f1275d8e88e..ab37a1cb29b 100644 --- a/dbms/src/Interpreters/Join.cpp +++ b/dbms/src/Interpreters/Join.cpp @@ -26,10 +26,6 @@ #include #include #include -#include - -#include "executeQuery.h" - namespace DB { @@ -104,6 +100,7 @@ Join::Join( , key_names_right(key_names_right_) , use_nulls(use_nulls_) , build_concurrency(std::max(1, build_concurrency_)) + , build_set_exceeded(false) , collators(collators_) , left_filter_column(left_filter_column_) , right_filter_column(right_filter_column_) @@ -116,7 +113,6 @@ Join::Join( , log(Logger::get("Join", req_id)) , limits(limits) { - build_set_exceeded.store(false); for (size_t i = 0; i < build_concurrency; i++) pools.emplace_back(std::make_shared()); if (other_condition_ptr != nullptr) @@ -725,7 +721,7 @@ void recordFilteredRows(const Block & block, const String & filter_column, Colum column = column->convertToFullColumnIfConst(); if (column->isColumnNullable()) { - const ColumnNullable & column_nullable = static_cast(*column); + const auto & column_nullable = static_cast(*column); if (!null_map_holder) { null_map_holder = column_nullable.getNullMapColumnPtr(); @@ -2048,7 +2044,7 @@ class NonJoinedBlockInputStream : public IProfilingBlockInputStream MutableColumns columns_right; std::unique_ptr> position; /// type erasure - size_t current_segment; + size_t current_segment = 0; Join::RowRefList * current_not_mapped_row = nullptr; void setNextCurrentNotMappedRow() diff --git a/dbms/src/Storages/StorageJoin.cpp b/dbms/src/Storages/StorageJoin.cpp index 47907b3e94e..4e3c01c6574 100644 --- a/dbms/src/Storages/StorageJoin.cpp +++ b/dbms/src/Storages/StorageJoin.cpp @@ -87,7 +87,7 @@ void registerStorageJoin(StorageFactory & factory) "Storage Join requires at least 3 parameters: Join(ANY|ALL, LEFT|INNER, keys...).", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - const ASTIdentifier * strictness_id = typeid_cast(engine_args[0].get()); + const auto * strictness_id = typeid_cast(engine_args[0].get()); if (!strictness_id) throw Exception("First parameter of storage Join must be ANY or ALL (without quotes).", ErrorCodes::BAD_ARGUMENTS); @@ -100,7 +100,7 @@ void registerStorageJoin(StorageFactory & factory) else throw Exception("First parameter of storage Join must be ANY or ALL (without quotes).", ErrorCodes::BAD_ARGUMENTS); - const ASTIdentifier * kind_id = typeid_cast(engine_args[1].get()); + const auto * kind_id = typeid_cast(engine_args[1].get()); if (!kind_id) throw Exception("Second parameter of storage Join must be LEFT or INNER (without quotes).", ErrorCodes::BAD_ARGUMENTS); @@ -121,7 +121,7 @@ void registerStorageJoin(StorageFactory & factory) key_names.reserve(engine_args.size() - 2); for (size_t i = 2, size = engine_args.size(); i < size; ++i) { - const ASTIdentifier * key = typeid_cast(engine_args[i].get()); + const auto * key = typeid_cast(engine_args[i].get()); if (!key) throw Exception("Parameter №" + toString(i + 1) + " of storage Join don't look like column name.", ErrorCodes::BAD_ARGUMENTS); From 64a747a179d736475a66e02d926efbab5a133c32 Mon Sep 17 00:00:00 2001 From: SeaRise Date: Tue, 24 May 2022 15:20:46 +0800 Subject: [PATCH 058/127] refine `handleJoin` (#4722) ref pingcap/tiflash#4118 --- .../Coprocessor/DAGQueryBlockInterpreter.cpp | 329 +++------------- .../Coprocessor/DAGQueryBlockInterpreter.h | 14 - .../Coprocessor/JoinInterpreterHelper.cpp | 356 ++++++++++++++++++ .../Flash/Coprocessor/JoinInterpreterHelper.h | 133 +++++++ dbms/src/Flash/tests/exchange_perftest.cpp | 4 +- dbms/src/Interpreters/ExpressionAnalyzer.cpp | 2 +- dbms/src/Interpreters/Join.cpp | 138 ++++--- dbms/src/Interpreters/Join.h | 61 ++- dbms/src/Storages/StorageJoin.cpp | 2 +- tests/fullstack-test/mpp/misc_join.test | 41 ++ 10 files changed, 719 insertions(+), 361 deletions(-) create mode 100644 dbms/src/Flash/Coprocessor/JoinInterpreterHelper.cpp create mode 100644 dbms/src/Flash/Coprocessor/JoinInterpreterHelper.h create mode 100644 tests/fullstack-test/mpp/misc_join.test diff --git a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp index 7dfe0ebd871..4d8faffde6c 100644 --- a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp +++ b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp @@ -33,9 +33,7 @@ #include #include #include -#include #include -#include #include #include #include @@ -43,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -50,9 +49,6 @@ #include #include #include -#include -#include - namespace DB { @@ -183,322 +179,117 @@ void DAGQueryBlockInterpreter::handleTableScan(const TiDBTableScan & table_scan, analyzer = std::move(storage_interpreter.analyzer); } -void DAGQueryBlockInterpreter::prepareJoin( - const google::protobuf::RepeatedPtrField & keys, - const DataTypes & key_types, - DAGPipeline & pipeline, - Names & key_names, - bool left, - bool is_right_out_join, - const google::protobuf::RepeatedPtrField & filters, - String & filter_column_name) -{ - NamesAndTypes source_columns; - for (auto const & p : pipeline.firstStream()->getHeader().getNamesAndTypesList()) - source_columns.emplace_back(p.name, p.type); - DAGExpressionAnalyzer dag_analyzer(std::move(source_columns), context); - ExpressionActionsChain chain; - if (dag_analyzer.appendJoinKeyAndJoinFilters(chain, keys, key_types, key_names, left, is_right_out_join, filters, filter_column_name)) - { - pipeline.transform([&](auto & stream) { stream = std::make_shared(stream, chain.getLastActions(), log->identifier()); }); - } -} - -ExpressionActionsPtr DAGQueryBlockInterpreter::genJoinOtherConditionAction( - const tipb::Join & join, - NamesAndTypes & source_columns, - String & filter_column_for_other_condition, - String & filter_column_for_other_eq_condition) -{ - if (join.other_conditions_size() == 0 && join.other_eq_conditions_from_in_size() == 0) - return nullptr; - DAGExpressionAnalyzer dag_analyzer(source_columns, context); - ExpressionActionsChain chain; - std::vector condition_vector; - if (join.other_conditions_size() > 0) - { - for (const auto & c : join.other_conditions()) - { - condition_vector.push_back(&c); - } - filter_column_for_other_condition = dag_analyzer.appendWhere(chain, condition_vector); - } - if (join.other_eq_conditions_from_in_size() > 0) - { - condition_vector.clear(); - for (const auto & c : join.other_eq_conditions_from_in()) - { - condition_vector.push_back(&c); - } - filter_column_for_other_eq_condition = dag_analyzer.appendWhere(chain, condition_vector); - } - return chain.getLastActions(); -} - -/// ClickHouse require join key to be exactly the same type -/// TiDB only require the join key to be the same category -/// for example decimal(10,2) join decimal(20,0) is allowed in -/// TiDB and will throw exception in ClickHouse -void getJoinKeyTypes(const tipb::Join & join, DataTypes & key_types) -{ - for (int i = 0; i < join.left_join_keys().size(); i++) - { - if (!exprHasValidFieldType(join.left_join_keys(i)) || !exprHasValidFieldType(join.right_join_keys(i))) - throw TiFlashException("Join key without field type", Errors::Coprocessor::BadRequest); - DataTypes types; - types.emplace_back(getDataTypeByFieldTypeForComputingLayer(join.left_join_keys(i).field_type())); - types.emplace_back(getDataTypeByFieldTypeForComputingLayer(join.right_join_keys(i).field_type())); - DataTypePtr common_type = getLeastSupertype(types); - key_types.emplace_back(common_type); - } -} - void DAGQueryBlockInterpreter::handleJoin(const tipb::Join & join, DAGPipeline & pipeline, SubqueryForSet & right_query) { - // build - static const std::unordered_map equal_join_type_map{ - {tipb::JoinType::TypeInnerJoin, ASTTableJoin::Kind::Inner}, - {tipb::JoinType::TypeLeftOuterJoin, ASTTableJoin::Kind::Left}, - {tipb::JoinType::TypeRightOuterJoin, ASTTableJoin::Kind::Right}, - {tipb::JoinType::TypeSemiJoin, ASTTableJoin::Kind::Inner}, - {tipb::JoinType::TypeAntiSemiJoin, ASTTableJoin::Kind::Anti}, - {tipb::JoinType::TypeLeftOuterSemiJoin, ASTTableJoin::Kind::LeftSemi}, - {tipb::JoinType::TypeAntiLeftOuterSemiJoin, ASTTableJoin::Kind::LeftAnti}}; - static const std::unordered_map cartesian_join_type_map{ - {tipb::JoinType::TypeInnerJoin, ASTTableJoin::Kind::Cross}, - {tipb::JoinType::TypeLeftOuterJoin, ASTTableJoin::Kind::Cross_Left}, - {tipb::JoinType::TypeRightOuterJoin, ASTTableJoin::Kind::Cross_Right}, - {tipb::JoinType::TypeSemiJoin, ASTTableJoin::Kind::Cross}, - {tipb::JoinType::TypeAntiSemiJoin, ASTTableJoin::Kind::Cross_Anti}, - {tipb::JoinType::TypeLeftOuterSemiJoin, ASTTableJoin::Kind::Cross_LeftSemi}, - {tipb::JoinType::TypeAntiLeftOuterSemiJoin, ASTTableJoin::Kind::Cross_LeftAnti}}; - - if (input_streams_vec.size() != 2) + if (unlikely(input_streams_vec.size() != 2)) { throw TiFlashException("Join query block must have 2 input streams", Errors::BroadcastJoin::Internal); } - const auto & join_type_map = join.left_join_keys_size() == 0 ? cartesian_join_type_map : equal_join_type_map; - auto join_type_it = join_type_map.find(join.join_type()); - if (join_type_it == join_type_map.end()) - throw TiFlashException("Unknown join type in dag request", Errors::Coprocessor::BadRequest); - - /// (cartesian) (anti) left semi join. - const bool is_left_semi_family = join.join_type() == tipb::JoinType::TypeLeftOuterSemiJoin || join.join_type() == tipb::JoinType::TypeAntiLeftOuterSemiJoin; - - ASTTableJoin::Kind kind = join_type_it->second; - const bool is_semi_join = join.join_type() == tipb::JoinType::TypeSemiJoin || join.join_type() == tipb::JoinType::TypeAntiSemiJoin || is_left_semi_family; - ASTTableJoin::Strictness strictness = ASTTableJoin::Strictness::All; - if (is_semi_join) - strictness = ASTTableJoin::Strictness::Any; - - /// in DAG request, inner part is the build side, however for TiFlash implementation, - /// the build side must be the right side, so need to swap the join side if needed - /// 1. for (cross) inner join, there is no problem in this swap. - /// 2. for (cross) semi/anti-semi join, the build side is always right, needn't swap. - /// 3. for non-cross left/right join, there is no problem in this swap. - /// 4. for cross left join, the build side is always right, needn't and can't swap. - /// 5. for cross right join, the build side is always left, so it will always swap and change to cross left join. - /// note that whatever the build side is, we can't support cross-right join now. - - bool swap_join_side; - if (kind == ASTTableJoin::Kind::Cross_Right) - swap_join_side = true; - else if (kind == ASTTableJoin::Kind::Cross_Left) - swap_join_side = false; - else - swap_join_side = join.inner_idx() == 0; + JoinInterpreterHelper::TiFlashJoin tiflash_join{join}; - DAGPipeline left_pipeline; - DAGPipeline right_pipeline; + DAGPipeline probe_pipeline; + DAGPipeline build_pipeline; + probe_pipeline.streams = input_streams_vec[1 - tiflash_join.build_side_index]; + build_pipeline.streams = input_streams_vec[tiflash_join.build_side_index]; - if (swap_join_side) - { - if (kind == ASTTableJoin::Kind::Left) - kind = ASTTableJoin::Kind::Right; - else if (kind == ASTTableJoin::Kind::Right) - kind = ASTTableJoin::Kind::Left; - else if (kind == ASTTableJoin::Kind::Cross_Right) - kind = ASTTableJoin::Kind::Cross_Left; - left_pipeline.streams = input_streams_vec[1]; - right_pipeline.streams = input_streams_vec[0]; - } - else - { - left_pipeline.streams = input_streams_vec[0]; - right_pipeline.streams = input_streams_vec[1]; - } - - NamesAndTypes join_output_columns; - /// columns_for_other_join_filter is a vector of columns used - /// as the input columns when compiling other join filter. - /// Note the order in the column vector is very important: - /// first the columns in input_streams_vec[0], then followed - /// by the columns in input_streams_vec[1], if there are other - /// columns generated before compile other join filter, then - /// append the extra columns afterwards. In order to figure out - /// whether a given column is already in the column vector or - /// not quickly, we use another set to store the column names - NamesAndTypes columns_for_other_join_filter; - std::unordered_set column_set_for_other_join_filter; - bool make_nullable = join.join_type() == tipb::JoinType::TypeRightOuterJoin; - for (auto const & p : input_streams_vec[0][0]->getHeader().getNamesAndTypesList()) - { - join_output_columns.emplace_back(p.name, make_nullable ? makeNullable(p.type) : p.type); - columns_for_other_join_filter.emplace_back(p.name, make_nullable ? makeNullable(p.type) : p.type); - column_set_for_other_join_filter.emplace(p.name); - } - make_nullable = join.join_type() == tipb::JoinType::TypeLeftOuterJoin; - for (auto const & p : input_streams_vec[1][0]->getHeader().getNamesAndTypesList()) - { - if (!is_semi_join) - /// for semi join, the columns from right table will be ignored - join_output_columns.emplace_back(p.name, make_nullable ? makeNullable(p.type) : p.type); - /// however, when compiling join's other condition, we still need the columns from right table - columns_for_other_join_filter.emplace_back(p.name, make_nullable ? makeNullable(p.type) : p.type); - column_set_for_other_join_filter.emplace(p.name); - } + RUNTIME_ASSERT(!input_streams_vec[0].empty(), log, "left input streams cannot be empty"); + const Block & left_input_header = input_streams_vec[0].back()->getHeader(); - bool is_tiflash_left_join = kind == ASTTableJoin::Kind::Left || kind == ASTTableJoin::Kind::Cross_Left; - /// Cross_Right join will be converted to Cross_Left join, so no need to check Cross_Right - bool is_tiflash_right_join = kind == ASTTableJoin::Kind::Right; - /// all the columns from right table should be added after join, even for the join key - NamesAndTypesList columns_added_by_join; - make_nullable = is_tiflash_left_join; - for (auto const & p : right_pipeline.streams[0]->getHeader().getNamesAndTypesList()) - { - columns_added_by_join.emplace_back(p.name, make_nullable ? makeNullable(p.type) : p.type); - } - - String match_helper_name; - if (is_left_semi_family) - { - const auto & left_block = input_streams_vec[0][0]->getHeader(); - const auto & right_block = input_streams_vec[1][0]->getHeader(); + RUNTIME_ASSERT(!input_streams_vec[1].empty(), log, "right input streams cannot be empty"); + const Block & right_input_header = input_streams_vec[1].back()->getHeader(); - match_helper_name = Join::match_helper_prefix; - for (int i = 1; left_block.has(match_helper_name) || right_block.has(match_helper_name); ++i) - { - match_helper_name = Join::match_helper_prefix + std::to_string(i); - } - - columns_added_by_join.emplace_back(match_helper_name, Join::match_helper_type); - join_output_columns.emplace_back(match_helper_name, Join::match_helper_type); - } - - DataTypes join_key_types; - getJoinKeyTypes(join, join_key_types); - TiDB::TiDBCollators collators; - size_t join_key_size = join_key_types.size(); - if (join.probe_types_size() == static_cast(join_key_size) && join.build_types_size() == join.probe_types_size()) - for (size_t i = 0; i < join_key_size; i++) - { - if (removeNullable(join_key_types[i])->isString()) - { - if (join.probe_types(i).collate() != join.build_types(i).collate()) - throw TiFlashException("Join with different collators on the join key", Errors::Coprocessor::BadRequest); - collators.push_back(getCollatorFromFieldType(join.probe_types(i))); - } - else - collators.push_back(nullptr); - } - - Names left_key_names, right_key_names; - String left_filter_column_name, right_filter_column_name; + String match_helper_name = tiflash_join.genMatchHelperName(left_input_header, right_input_header); + NamesAndTypesList columns_added_by_join = tiflash_join.genColumnsAddedByJoin(build_pipeline.firstStream()->getHeader(), match_helper_name); + NamesAndTypes join_output_columns = tiflash_join.genJoinOutputColumns(left_input_header, right_input_header, match_helper_name); /// add necessary transformation if the join key is an expression - prepareJoin( - swap_join_side ? join.right_join_keys() : join.left_join_keys(), - join_key_types, - left_pipeline, - left_key_names, + bool is_tiflash_right_join = tiflash_join.isTiFlashRightJoin(); + + // prepare probe side + auto [probe_side_prepare_actions, probe_key_names, probe_filter_column_name] = JoinInterpreterHelper::prepareJoin( + context, + probe_pipeline.firstStream()->getHeader(), + tiflash_join.getProbeJoinKeys(), + tiflash_join.join_key_types, true, is_tiflash_right_join, - swap_join_side ? join.right_conditions() : join.left_conditions(), - left_filter_column_name); - - prepareJoin( - swap_join_side ? join.left_join_keys() : join.right_join_keys(), - join_key_types, - right_pipeline, - right_key_names, + tiflash_join.getProbeConditions()); + RUNTIME_ASSERT(probe_side_prepare_actions, log, "probe_side_prepare_actions cannot be nullptr"); + + // prepare build side + auto [build_side_prepare_actions, build_key_names, build_filter_column_name] = JoinInterpreterHelper::prepareJoin( + context, + build_pipeline.firstStream()->getHeader(), + tiflash_join.getBuildJoinKeys(), + tiflash_join.join_key_types, false, is_tiflash_right_join, - swap_join_side ? join.left_conditions() : join.right_conditions(), - right_filter_column_name); + tiflash_join.getBuildConditions()); + RUNTIME_ASSERT(build_side_prepare_actions, log, "build_side_prepare_actions cannot be nullptr"); - String other_filter_column_name, other_eq_filter_from_in_column_name; - for (auto const & p : left_pipeline.streams[0]->getHeader().getNamesAndTypesList()) - { - if (column_set_for_other_join_filter.find(p.name) == column_set_for_other_join_filter.end()) - columns_for_other_join_filter.emplace_back(p.name, p.type); - } - for (auto const & p : right_pipeline.streams[0]->getHeader().getNamesAndTypesList()) - { - if (column_set_for_other_join_filter.find(p.name) == column_set_for_other_join_filter.end()) - columns_for_other_join_filter.emplace_back(p.name, p.type); - } - - ExpressionActionsPtr other_condition_expr - = genJoinOtherConditionAction(join, columns_for_other_join_filter, other_filter_column_name, other_eq_filter_from_in_column_name); + auto [other_condition_expr, other_filter_column_name, other_eq_filter_from_in_column_name] + = tiflash_join.genJoinOtherConditionAction(context, left_input_header, right_input_header, probe_side_prepare_actions); const Settings & settings = context.getSettingsRef(); - size_t join_build_concurrency = settings.join_concurrent_build ? std::min(max_streams, right_pipeline.streams.size()) : 1; size_t max_block_size_for_cross_join = settings.max_block_size; fiu_do_on(FailPoints::minimum_block_size_for_cross_join, { max_block_size_for_cross_join = 1; }); JoinPtr join_ptr = std::make_shared( - left_key_names, - right_key_names, + probe_key_names, + build_key_names, true, SizeLimits(settings.max_rows_in_join, settings.max_bytes_in_join, settings.join_overflow_mode), - kind, - strictness, + tiflash_join.kind, + tiflash_join.strictness, log->identifier(), - join_build_concurrency, - collators, - left_filter_column_name, - right_filter_column_name, + tiflash_join.join_key_collators, + probe_filter_column_name, + build_filter_column_name, other_filter_column_name, other_eq_filter_from_in_column_name, other_condition_expr, max_block_size_for_cross_join, match_helper_name); - recordJoinExecuteInfo(swap_join_side ? 0 : 1, join_ptr); + recordJoinExecuteInfo(tiflash_join.build_side_index, join_ptr); + + size_t join_build_concurrency = settings.join_concurrent_build ? std::min(max_streams, build_pipeline.streams.size()) : 1; + /// build side streams + executeExpression(build_pipeline, build_side_prepare_actions); // add a HashJoinBuildBlockInputStream to build a shared hash table - size_t concurrency_build_index = 0; - auto get_concurrency_build_index = [&concurrency_build_index, &join_build_concurrency]() { - return (concurrency_build_index++) % join_build_concurrency; - }; - right_pipeline.transform([&](auto & stream) { + auto get_concurrency_build_index = JoinInterpreterHelper::concurrencyBuildIndexGenerator(join_build_concurrency); + build_pipeline.transform([&](auto & stream) { stream = std::make_shared(stream, join_ptr, get_concurrency_build_index(), log->identifier()); }); - executeUnion(right_pipeline, max_streams, log, /*ignore_block=*/true); + executeUnion(build_pipeline, max_streams, log, /*ignore_block=*/true); - right_query.source = right_pipeline.firstStream(); + right_query.source = build_pipeline.firstStream(); right_query.join = join_ptr; - right_query.join->setSampleBlock(right_query.source->getHeader()); + join_ptr->init(right_query.source->getHeader(), join_build_concurrency); + /// probe side streams + executeExpression(probe_pipeline, probe_side_prepare_actions); NamesAndTypes source_columns; - for (const auto & p : left_pipeline.streams[0]->getHeader().getNamesAndTypesList()) + for (const auto & p : probe_pipeline.firstStream()->getHeader()) source_columns.emplace_back(p.name, p.type); DAGExpressionAnalyzer dag_analyzer(std::move(source_columns), context); ExpressionActionsChain chain; dag_analyzer.appendJoin(chain, right_query, columns_added_by_join); - pipeline.streams = left_pipeline.streams; + pipeline.streams = probe_pipeline.streams; /// add join input stream if (is_tiflash_right_join) { auto & join_execute_info = dagContext().getJoinExecuteInfoMap()[query_block.source_name]; - for (size_t i = 0; i < join_build_concurrency; i++) + size_t not_joined_concurrency = join_ptr->getNotJoinedStreamConcurrency(); + for (size_t i = 0; i < not_joined_concurrency; ++i) { - auto non_joined_stream = chain.getLastActions()->createStreamWithNonJoinedDataIfFullOrRightJoin( + auto non_joined_stream = join_ptr->createStreamWithNonJoinedRows( pipeline.firstStream()->getHeader(), i, - join_build_concurrency, + not_joined_concurrency, settings.max_block_size); pipeline.streams_with_non_joined_data.push_back(non_joined_stream); join_execute_info.non_joined_streams.push_back(non_joined_stream); diff --git a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h index 69bac9c3ba9..9b95a5c3e93 100644 --- a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h +++ b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h @@ -61,25 +61,11 @@ class DAGQueryBlockInterpreter void handleMockTableScan(const TiDBTableScan & table_scan, DAGPipeline & pipeline); void handleTableScan(const TiDBTableScan & table_scan, DAGPipeline & pipeline); void handleJoin(const tipb::Join & join, DAGPipeline & pipeline, SubqueryForSet & right_query); - void prepareJoin( - const google::protobuf::RepeatedPtrField & keys, - const DataTypes & key_types, - DAGPipeline & pipeline, - Names & key_names, - bool left, - bool is_right_out_join, - const google::protobuf::RepeatedPtrField & filters, - String & filter_column_name); void handleExchangeReceiver(DAGPipeline & pipeline); void handleMockExchangeReceiver(DAGPipeline & pipeline); void handleProjection(DAGPipeline & pipeline, const tipb::Projection & projection); void handleWindow(DAGPipeline & pipeline, const tipb::Window & window); void handleWindowOrder(DAGPipeline & pipeline, const tipb::Sort & window_sort); - ExpressionActionsPtr genJoinOtherConditionAction( - const tipb::Join & join, - NamesAndTypes & source_columns, - String & filter_column_for_other_condition, - String & filter_column_for_other_eq_condition); void executeWhere(DAGPipeline & pipeline, const ExpressionActionsPtr & expressionActionsPtr, String & filter_column); void executeExpression(DAGPipeline & pipeline, const ExpressionActionsPtr & expressionActionsPtr); void executeWindowOrder(DAGPipeline & pipeline, SortDescription sort_desc); diff --git a/dbms/src/Flash/Coprocessor/JoinInterpreterHelper.cpp b/dbms/src/Flash/Coprocessor/JoinInterpreterHelper.cpp new file mode 100644 index 00000000000..2582a84ac46 --- /dev/null +++ b/dbms/src/Flash/Coprocessor/JoinInterpreterHelper.cpp @@ -0,0 +1,356 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace DB::JoinInterpreterHelper +{ +namespace +{ +std::pair getJoinKindAndBuildSideIndex(const tipb::Join & join) +{ + static const std::unordered_map equal_join_type_map{ + {tipb::JoinType::TypeInnerJoin, ASTTableJoin::Kind::Inner}, + {tipb::JoinType::TypeLeftOuterJoin, ASTTableJoin::Kind::Left}, + {tipb::JoinType::TypeRightOuterJoin, ASTTableJoin::Kind::Right}, + {tipb::JoinType::TypeSemiJoin, ASTTableJoin::Kind::Inner}, + {tipb::JoinType::TypeAntiSemiJoin, ASTTableJoin::Kind::Anti}, + {tipb::JoinType::TypeLeftOuterSemiJoin, ASTTableJoin::Kind::LeftSemi}, + {tipb::JoinType::TypeAntiLeftOuterSemiJoin, ASTTableJoin::Kind::LeftAnti}}; + static const std::unordered_map cartesian_join_type_map{ + {tipb::JoinType::TypeInnerJoin, ASTTableJoin::Kind::Cross}, + {tipb::JoinType::TypeLeftOuterJoin, ASTTableJoin::Kind::Cross_Left}, + {tipb::JoinType::TypeRightOuterJoin, ASTTableJoin::Kind::Cross_Right}, + {tipb::JoinType::TypeSemiJoin, ASTTableJoin::Kind::Cross}, + {tipb::JoinType::TypeAntiSemiJoin, ASTTableJoin::Kind::Cross_Anti}, + {tipb::JoinType::TypeLeftOuterSemiJoin, ASTTableJoin::Kind::Cross_LeftSemi}, + {tipb::JoinType::TypeAntiLeftOuterSemiJoin, ASTTableJoin::Kind::Cross_LeftAnti}}; + + const auto & join_type_map = join.left_join_keys_size() == 0 ? cartesian_join_type_map : equal_join_type_map; + auto join_type_it = join_type_map.find(join.join_type()); + if (unlikely(join_type_it == join_type_map.end())) + throw TiFlashException("Unknown join type in dag request", Errors::Coprocessor::BadRequest); + + ASTTableJoin::Kind kind = join_type_it->second; + + /// in DAG request, inner part is the build side, however for TiFlash implementation, + /// the build side must be the right side, so need to swap the join side if needed + /// 1. for (cross) inner join, there is no problem in this swap. + /// 2. for (cross) semi/anti-semi join, the build side is always right, needn't swap. + /// 3. for non-cross left/right join, there is no problem in this swap. + /// 4. for cross left join, the build side is always right, needn't and can't swap. + /// 5. for cross right join, the build side is always left, so it will always swap and change to cross left join. + /// note that whatever the build side is, we can't support cross-right join now. + + size_t build_side_index = 0; + switch (kind) + { + case ASTTableJoin::Kind::Cross_Right: + build_side_index = 0; + break; + case ASTTableJoin::Kind::Cross_Left: + build_side_index = 1; + break; + default: + build_side_index = join.inner_idx(); + } + assert(build_side_index == 0 || build_side_index == 1); + + // should swap join side. + if (build_side_index != 1) + { + switch (kind) + { + case ASTTableJoin::Kind::Left: + kind = ASTTableJoin::Kind::Right; + break; + case ASTTableJoin::Kind::Right: + kind = ASTTableJoin::Kind::Left; + break; + case ASTTableJoin::Kind::Cross_Right: + kind = ASTTableJoin::Kind::Cross_Left; + default:; // just `default`, for other kinds, don't need to change kind. + } + } + + return {kind, build_side_index}; +} + +DataTypes getJoinKeyTypes(const tipb::Join & join) +{ + if (unlikely(join.left_join_keys_size() != join.right_join_keys_size())) + throw TiFlashException("size of join.left_join_keys != size of join.right_join_keys", Errors::Coprocessor::BadRequest); + DataTypes key_types; + for (int i = 0; i < join.left_join_keys_size(); ++i) + { + if (unlikely(!exprHasValidFieldType(join.left_join_keys(i)) || !exprHasValidFieldType(join.right_join_keys(i)))) + throw TiFlashException("Join key without field type", Errors::Coprocessor::BadRequest); + DataTypes types; + types.emplace_back(getDataTypeByFieldTypeForComputingLayer(join.left_join_keys(i).field_type())); + types.emplace_back(getDataTypeByFieldTypeForComputingLayer(join.right_join_keys(i).field_type())); + DataTypePtr common_type = getLeastSupertype(types); + key_types.emplace_back(common_type); + } + return key_types; +} + +TiDB::TiDBCollators getJoinKeyCollators(const tipb::Join & join, const DataTypes & join_key_types) +{ + TiDB::TiDBCollators collators; + size_t join_key_size = join_key_types.size(); + if (join.probe_types_size() == static_cast(join_key_size) && join.build_types_size() == join.probe_types_size()) + for (size_t i = 0; i < join_key_size; ++i) + { + if (removeNullable(join_key_types[i])->isString()) + { + if (unlikely(join.probe_types(i).collate() != join.build_types(i).collate())) + throw TiFlashException("Join with different collators on the join key", Errors::Coprocessor::BadRequest); + collators.push_back(getCollatorFromFieldType(join.probe_types(i))); + } + else + collators.push_back(nullptr); + } + return collators; +} + +std::tuple doGenJoinOtherConditionAction( + const Context & context, + const tipb::Join & join, + const NamesAndTypes & source_columns) +{ + if (join.other_conditions_size() == 0 && join.other_eq_conditions_from_in_size() == 0) + return {nullptr, "", ""}; + + DAGExpressionAnalyzer dag_analyzer(source_columns, context); + ExpressionActionsChain chain; + + String filter_column_for_other_condition; + if (join.other_conditions_size() > 0) + { + std::vector condition_vector; + for (const auto & c : join.other_conditions()) + { + condition_vector.push_back(&c); + } + filter_column_for_other_condition = dag_analyzer.appendWhere(chain, condition_vector); + } + + String filter_column_for_other_eq_condition; + if (join.other_eq_conditions_from_in_size() > 0) + { + std::vector condition_vector; + for (const auto & c : join.other_eq_conditions_from_in()) + { + condition_vector.push_back(&c); + } + filter_column_for_other_eq_condition = dag_analyzer.appendWhere(chain, condition_vector); + } + + return {chain.getLastActions(), std::move(filter_column_for_other_condition), std::move(filter_column_for_other_eq_condition)}; +} +} // namespace + +TiFlashJoin::TiFlashJoin(const tipb::Join & join_) // NOLINT(cppcoreguidelines-pro-type-member-init) + : join(join_) + , join_key_types(getJoinKeyTypes(join_)) + , join_key_collators(getJoinKeyCollators(join_, join_key_types)) +{ + std::tie(kind, build_side_index) = getJoinKindAndBuildSideIndex(join); + strictness = isSemiJoin() ? ASTTableJoin::Strictness::Any : ASTTableJoin::Strictness::All; +} + +String TiFlashJoin::genMatchHelperName(const Block & header1, const Block & header2) const +{ + if (!isLeftSemiFamily()) + { + return ""; + } + + size_t i = 0; + String match_helper_name = fmt::format("{}{}", Join::match_helper_prefix, i); + while (header1.has(match_helper_name) || header2.has(match_helper_name)) + { + match_helper_name = fmt::format("{}{}", Join::match_helper_prefix, ++i); + } + return match_helper_name; +} + +NamesAndTypes TiFlashJoin::genColumnsForOtherJoinFilter( + const Block & left_input_header, + const Block & right_input_header, + const ExpressionActionsPtr & probe_prepare_join_actions) const +{ +#ifndef NDEBUG + auto is_prepare_actions_valid = [](const Block & origin_block, const ExpressionActionsPtr & prepare_actions) { + const Block & prepare_sample_block = prepare_actions->getSampleBlock(); + for (const auto & p : origin_block) + { + if (!prepare_sample_block.has(p.name)) + return false; + } + return true; + }; + if (unlikely(!is_prepare_actions_valid(build_side_index == 1 ? left_input_header : right_input_header, probe_prepare_join_actions))) + { + throw TiFlashException("probe_prepare_join_actions isn't valid", Errors::Coprocessor::Internal); + } +#endif + + /// columns_for_other_join_filter is a vector of columns used + /// as the input columns when compiling other join filter. + /// Note the order in the column vector is very important: + /// first the columns in left_input_header, then followed + /// by the columns in right_input_header, if there are other + /// columns generated before compile other join filter, then + /// append the extra columns afterwards. In order to figure out + /// whether a given column is already in the column vector or + /// not quickly, we use another set to store the column names. + + /// The order of columns must be {left_input, right_input, extra columns}, + /// because tidb requires the input schema of join to be {left_input, right_input}. + /// Extra columns are appended to prevent extra columns from being repeatedly generated. + + NamesAndTypes columns_for_other_join_filter; + std::unordered_set column_set_for_origin_columns; + + auto append_origin_columns = [&columns_for_other_join_filter, &column_set_for_origin_columns](const Block & header, bool make_nullable) { + for (const auto & p : header) + { + columns_for_other_join_filter.emplace_back(p.name, make_nullable ? makeNullable(p.type) : p.type); + column_set_for_origin_columns.emplace(p.name); + } + }; + append_origin_columns(left_input_header, join.join_type() == tipb::JoinType::TypeRightOuterJoin); + append_origin_columns(right_input_header, join.join_type() == tipb::JoinType::TypeLeftOuterJoin); + + /// append the columns generated by probe side prepare join actions. + /// the new columns are + /// - filter_column and related temporary columns + /// - join keys and related temporary columns + auto append_new_columns = [&columns_for_other_join_filter, &column_set_for_origin_columns](const Block & header, bool make_nullable) { + for (const auto & p : header) + { + if (column_set_for_origin_columns.find(p.name) == column_set_for_origin_columns.end()) + columns_for_other_join_filter.emplace_back(p.name, make_nullable ? makeNullable(p.type) : p.type); + } + }; + bool make_nullable = build_side_index == 1 + ? join.join_type() == tipb::JoinType::TypeRightOuterJoin + : join.join_type() == tipb::JoinType::TypeLeftOuterJoin; + append_new_columns(probe_prepare_join_actions->getSampleBlock(), make_nullable); + + return columns_for_other_join_filter; +} + +/// all the columns from build side streams should be added after join, even for the join key. +NamesAndTypesList TiFlashJoin::genColumnsAddedByJoin( + const Block & build_side_header, + const String & match_helper_name) const +{ + NamesAndTypesList columns_added_by_join; + bool make_nullable = isTiFlashLeftJoin(); + for (auto const & p : build_side_header) + { + columns_added_by_join.emplace_back(p.name, make_nullable ? makeNullable(p.type) : p.type); + } + if (!match_helper_name.empty()) + { + columns_added_by_join.emplace_back(match_helper_name, Join::match_helper_type); + } + return columns_added_by_join; +} + +NamesAndTypes TiFlashJoin::genJoinOutputColumns( + const Block & left_input_header, + const Block & right_input_header, + const String & match_helper_name) const +{ + NamesAndTypes join_output_columns; + auto append_output_columns = [&join_output_columns](const Block & header, bool make_nullable) { + for (auto const & p : header) + { + join_output_columns.emplace_back(p.name, make_nullable ? makeNullable(p.type) : p.type); + } + }; + + append_output_columns(left_input_header, join.join_type() == tipb::JoinType::TypeRightOuterJoin); + if (!isSemiJoin()) + { + /// for semi join, the columns from right table will be ignored + append_output_columns(right_input_header, join.join_type() == tipb::JoinType::TypeLeftOuterJoin); + } + + if (!match_helper_name.empty()) + { + join_output_columns.emplace_back(match_helper_name, Join::match_helper_type); + } + + return join_output_columns; +} + +std::tuple TiFlashJoin::genJoinOtherConditionAction( + const Context & context, + const Block & left_input_header, + const Block & right_input_header, + const ExpressionActionsPtr & probe_side_prepare_join) const +{ + auto columns_for_other_join_filter + = genColumnsForOtherJoinFilter( + left_input_header, + right_input_header, + probe_side_prepare_join); + + return doGenJoinOtherConditionAction(context, join, columns_for_other_join_filter); +} + +std::tuple prepareJoin( + const Context & context, + const Block & input_header, + const google::protobuf::RepeatedPtrField & keys, + const DataTypes & key_types, + bool left, + bool is_right_out_join, + const google::protobuf::RepeatedPtrField & filters) +{ + NamesAndTypes source_columns; + for (auto const & p : input_header) + source_columns.emplace_back(p.name, p.type); + DAGExpressionAnalyzer dag_analyzer(std::move(source_columns), context); + ExpressionActionsChain chain; + Names key_names; + String filter_column_name; + dag_analyzer.appendJoinKeyAndJoinFilters(chain, keys, key_types, key_names, left, is_right_out_join, filters, filter_column_name); + return {chain.getLastActions(), std::move(key_names), std::move(filter_column_name)}; +} + +std::function concurrencyBuildIndexGenerator(size_t join_build_concurrency) +{ + size_t init_value = 0; + return [init_value, join_build_concurrency]() mutable { + return (init_value++) % join_build_concurrency; + }; +} +} // namespace DB::JoinInterpreterHelper \ No newline at end of file diff --git a/dbms/src/Flash/Coprocessor/JoinInterpreterHelper.h b/dbms/src/Flash/Coprocessor/JoinInterpreterHelper.h new file mode 100644 index 00000000000..d84c03d572d --- /dev/null +++ b/dbms/src/Flash/Coprocessor/JoinInterpreterHelper.h @@ -0,0 +1,133 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace DB +{ +class Context; + +namespace JoinInterpreterHelper +{ +struct TiFlashJoin +{ + explicit TiFlashJoin(const tipb::Join & join_); + + const tipb::Join & join; + + ASTTableJoin::Kind kind; + size_t build_side_index = 0; + + DataTypes join_key_types; + TiDB::TiDBCollators join_key_collators; + + ASTTableJoin::Strictness strictness; + + /// (cartesian) (anti) left semi join. + bool isLeftSemiFamily() const { return join.join_type() == tipb::JoinType::TypeLeftOuterSemiJoin || join.join_type() == tipb::JoinType::TypeAntiLeftOuterSemiJoin; } + + bool isSemiJoin() const { return join.join_type() == tipb::JoinType::TypeSemiJoin || join.join_type() == tipb::JoinType::TypeAntiSemiJoin || isLeftSemiFamily(); } + + const google::protobuf::RepeatedPtrField & getBuildJoinKeys() const + { + return build_side_index == 1 ? join.right_join_keys() : join.left_join_keys(); + } + + const google::protobuf::RepeatedPtrField & getProbeJoinKeys() const + { + return build_side_index == 0 ? join.right_join_keys() : join.left_join_keys(); + } + + const google::protobuf::RepeatedPtrField & getBuildConditions() const + { + return build_side_index == 1 ? join.right_conditions() : join.left_conditions(); + } + + const google::protobuf::RepeatedPtrField & getProbeConditions() const + { + return build_side_index == 0 ? join.right_conditions() : join.left_conditions(); + } + + bool isTiFlashLeftJoin() const { return kind == ASTTableJoin::Kind::Left || kind == ASTTableJoin::Kind::Cross_Left; } + + /// Cross_Right join will be converted to Cross_Left join, so no need to check Cross_Right + bool isTiFlashRightJoin() const { return kind == ASTTableJoin::Kind::Right; } + + /// return a name that is unique in header1 and header2 for left semi family join, + /// return "" for everything else. + String genMatchHelperName(const Block & header1, const Block & header2) const; + + /// columns_added_by_join + /// = join_output_columns - probe_side_columns + /// = build_side_columns + match_helper_name + NamesAndTypesList genColumnsAddedByJoin( + const Block & build_side_header, + const String & match_helper_name) const; + + /// The columns output by join will be: + /// {columns of left_input, columns of right_input, match_helper_name} + NamesAndTypes genJoinOutputColumns( + const Block & left_input_header, + const Block & right_input_header, + const String & match_helper_name) const; + + /// @other_condition_expr: generates other_filter_column and other_eq_filter_from_in_column + /// @other_filter_column_name: column name of `and(other_cond1, other_cond2, ...)` + /// @other_eq_filter_from_in_column_name: column name of `and(other_eq_cond1_from_in, other_eq_cond2_from_in, ...)` + /// such as + /// `select * from t where col1 in (select col2 from t2 where t1.col2 = t2.col3)` + /// - other_filter is `t1.col2 = t2.col3` + /// - other_eq_filter_from_in_column is `t1.col1 = t2.col2` + /// + /// new columns from build side prepare join actions cannot be appended. + /// because the input that other filter accepts is + /// {left_input_columns, right_input_columns, new_columns_from_probe_side_prepare, match_helper_name}. + std::tuple genJoinOtherConditionAction( + const Context & context, + const Block & left_input_header, + const Block & right_input_header, + const ExpressionActionsPtr & probe_side_prepare_join) const; + + NamesAndTypes genColumnsForOtherJoinFilter( + const Block & left_input_header, + const Block & right_input_header, + const ExpressionActionsPtr & probe_prepare_join_actions) const; +}; + +/// @join_prepare_expr_actions: generates join key columns and join filter column +/// @key_names: column names of keys. +/// @filter_column_name: column name of `and(filters)` +std::tuple prepareJoin( + const Context & context, + const Block & input_header, + const google::protobuf::RepeatedPtrField & keys, + const DataTypes & key_types, + bool left, + bool is_right_out_join, + const google::protobuf::RepeatedPtrField & filters); + +std::function concurrencyBuildIndexGenerator(size_t join_build_concurrency); +} // namespace JoinInterpreterHelper +} // namespace DB diff --git a/dbms/src/Flash/tests/exchange_perftest.cpp b/dbms/src/Flash/tests/exchange_perftest.cpp index 45dbac4a7f6..c2e047bec62 100644 --- a/dbms/src/Flash/tests/exchange_perftest.cpp +++ b/dbms/src/Flash/tests/exchange_perftest.cpp @@ -462,7 +462,7 @@ struct ReceiverHelper SizeLimits(0, 0, OverflowMode::THROW), ASTTableJoin::Kind::Inner, ASTTableJoin::Strictness::All, - concurrency, + /*req_id=*/"", TiDB::TiDBCollators{nullptr}, "", "", @@ -471,7 +471,7 @@ struct ReceiverHelper nullptr, 65536); - join_ptr->setSampleBlock(receiver_header); + join_ptr->init(receiver_header, concurrency); for (int i = 0; i < concurrency; ++i) streams[i] = std::make_shared(streams[i], join_ptr, i, /*req_id=*/""); diff --git a/dbms/src/Interpreters/ExpressionAnalyzer.cpp b/dbms/src/Interpreters/ExpressionAnalyzer.cpp index cd947d08953..a532ed8a8e0 100644 --- a/dbms/src/Interpreters/ExpressionAnalyzer.cpp +++ b/dbms/src/Interpreters/ExpressionAnalyzer.cpp @@ -2435,7 +2435,7 @@ bool ExpressionAnalyzer::appendJoin(ExpressionActionsChain & chain, bool only_ty /// TODO You do not need to set this up when JOIN is only needed on remote servers. subquery_for_set.join = join; - subquery_for_set.join->setSampleBlock(subquery_for_set.source->getHeader()); + subquery_for_set.join->init(subquery_for_set.source->getHeader()); } addJoinAction(step.actions, false); diff --git a/dbms/src/Interpreters/Join.cpp b/dbms/src/Interpreters/Join.cpp index ab37a1cb29b..820618a6e8b 100644 --- a/dbms/src/Interpreters/Join.cpp +++ b/dbms/src/Interpreters/Join.cpp @@ -38,40 +38,67 @@ extern const int TYPE_MISMATCH; extern const int ILLEGAL_COLUMN; } // namespace ErrorCodes +namespace +{ /// Do I need to use the hash table maps_*_full, in which we remember whether the row was joined. -static bool getFullness(ASTTableJoin::Kind kind) +bool getFullness(ASTTableJoin::Kind kind) { return kind == ASTTableJoin::Kind::Right || kind == ASTTableJoin::Kind::Cross_Right || kind == ASTTableJoin::Kind::Full; } -static bool isLeftJoin(ASTTableJoin::Kind kind) +bool isLeftJoin(ASTTableJoin::Kind kind) { return kind == ASTTableJoin::Kind::Left || kind == ASTTableJoin::Kind::Cross_Left; } -static bool isRightJoin(ASTTableJoin::Kind kind) +bool isRightJoin(ASTTableJoin::Kind kind) { return kind == ASTTableJoin::Kind::Right || kind == ASTTableJoin::Kind::Cross_Right; } -static bool isInnerJoin(ASTTableJoin::Kind kind) +bool isInnerJoin(ASTTableJoin::Kind kind) { return kind == ASTTableJoin::Kind::Inner || kind == ASTTableJoin::Kind::Cross; } -static bool isAntiJoin(ASTTableJoin::Kind kind) +bool isAntiJoin(ASTTableJoin::Kind kind) { return kind == ASTTableJoin::Kind::Anti || kind == ASTTableJoin::Kind::Cross_Anti; } -static bool isCrossJoin(ASTTableJoin::Kind kind) +bool isCrossJoin(ASTTableJoin::Kind kind) { return kind == ASTTableJoin::Kind::Cross || kind == ASTTableJoin::Kind::Cross_Left || kind == ASTTableJoin::Kind::Cross_Right || kind == ASTTableJoin::Kind::Cross_Anti || kind == ASTTableJoin::Kind::Cross_LeftSemi || kind == ASTTableJoin::Kind::Cross_LeftAnti; } /// (cartesian) (anti) left semi join. -static bool isLeftSemiFamily(ASTTableJoin::Kind kind) +bool isLeftSemiFamily(ASTTableJoin::Kind kind) { return kind == ASTTableJoin::Kind::LeftSemi || kind == ASTTableJoin::Kind::LeftAnti || kind == ASTTableJoin::Kind::Cross_LeftSemi || kind == ASTTableJoin::Kind::Cross_LeftAnti; } +void convertColumnToNullable(ColumnWithTypeAndName & column) +{ + column.type = makeNullable(column.type); + if (column.column) + column.column = makeNullable(column.column); +} + +ColumnRawPtrs getKeyColumns(const Names & key_names, const Block & block) +{ + size_t keys_size = key_names.size(); + ColumnRawPtrs key_columns(keys_size); + + for (size_t i = 0; i < keys_size; ++i) + { + key_columns[i] = block.getByName(key_names[i]).column.get(); + + /// We will join only keys, where all components are not NULL. + if (key_columns[i]->isColumnNullable()) + key_columns[i] = &static_cast(*key_columns[i]).getNestedColumn(); + } + + return key_columns; +} +} // namespace + const std::string Join::match_helper_prefix = "__left-semi-join-match-helper"; const DataTypePtr Join::match_helper_type = makeNullable(std::make_shared()); @@ -84,7 +111,6 @@ Join::Join( ASTTableJoin::Kind kind_, ASTTableJoin::Strictness strictness_, const String & req_id, - size_t build_concurrency_, const TiDB::TiDBCollators & collators_, const String & left_filter_column_, const String & right_filter_column_, @@ -99,7 +125,7 @@ Join::Join( , key_names_left(key_names_left_) , key_names_right(key_names_right_) , use_nulls(use_nulls_) - , build_concurrency(std::max(1, build_concurrency_)) + , build_concurrency(0) , build_set_exceeded(false) , collators(collators_) , left_filter_column(left_filter_column_) @@ -113,8 +139,6 @@ Join::Join( , log(Logger::get("Join", req_id)) , limits(limits) { - for (size_t i = 0; i < build_concurrency; i++) - pools.emplace_back(std::make_shared()); if (other_condition_ptr != nullptr) { /// if there is other_condition, then should keep all the valid rows during probe stage @@ -123,14 +147,9 @@ Join::Join( strictness = ASTTableJoin::Strictness::All; } } - if (getFullness(kind)) - { - for (size_t i = 0; i < build_concurrency; i++) - rows_not_inserted_to_map.push_back(std::make_unique()); - } - if (!left_filter_column.empty() && !isLeftJoin(kind)) + if (unlikely(!left_filter_column.empty() && !isLeftJoin(kind))) throw Exception("Not supported: non left join with left conditions"); - if (!right_filter_column.empty() && !isRightJoin(kind)) + if (unlikely(!right_filter_column.empty() && !isRightJoin(kind))) throw Exception("Not supported: non right join with right conditions"); } @@ -324,7 +343,7 @@ struct KeyGetterForType using Type = typename KeyGetterForTypeImpl::Type; }; -void Join::init(Type type_) +void Join::initMapImpl(Type type_) { type = type_; @@ -334,16 +353,16 @@ void Join::init(Type type_) if (!getFullness(kind)) { if (strictness == ASTTableJoin::Strictness::Any) - initImpl(maps_any, type, build_concurrency); + initImpl(maps_any, type, getBuildConcurrencyInternal()); else - initImpl(maps_all, type, build_concurrency); + initImpl(maps_all, type, getBuildConcurrencyInternal()); } else { if (strictness == ASTTableJoin::Strictness::Any) - initImpl(maps_any_full, type, build_concurrency); + initImpl(maps_any_full, type, getBuildConcurrencyInternal()); else - initImpl(maps_all_full, type, build_concurrency); + initImpl(maps_all_full, type, getBuildConcurrencyInternal()); } } @@ -392,37 +411,24 @@ size_t Join::getTotalByteCount() const return res; } - -static void convertColumnToNullable(ColumnWithTypeAndName & column) +void Join::setBuildConcurrencyAndInitPool(size_t build_concurrency_) { - column.type = makeNullable(column.type); - if (column.column) - column.column = makeNullable(column.column); -} - + if (unlikely(build_concurrency > 0)) + throw Exception("Logical error: `setBuildConcurrencyAndInitPool` shouldn't be called more than once", ErrorCodes::LOGICAL_ERROR); + build_concurrency = std::max(1, build_concurrency_); -void Join::setSampleBlock(const Block & block) -{ - std::unique_lock lock(rwlock); - - if (!empty()) - return; - - size_t keys_size = key_names_right.size(); - ColumnRawPtrs key_columns(keys_size); - - for (size_t i = 0; i < keys_size; ++i) + for (size_t i = 0; i < getBuildConcurrencyInternal(); ++i) + pools.emplace_back(std::make_shared()); + // init for non-joined-streams. + if (getFullness(kind)) { - key_columns[i] = block.getByName(key_names_right[i]).column.get(); - - /// We will join only keys, where all components are not NULL. - if (key_columns[i]->isColumnNullable()) - key_columns[i] = &static_cast(*key_columns[i]).getNestedColumn(); + for (size_t i = 0; i < getNotJoinedStreamConcurrencyInternal(); ++i) + rows_not_inserted_to_map.push_back(std::make_unique()); } +} - /// Choose data structure to use for JOIN. - init(chooseMethod(key_columns, key_sizes)); - +void Join::setSampleBlock(const Block & block) +{ sample_block_with_columns_to_add = materializeBlock(block); /// Move from `sample_block_with_columns_to_add` key columns to `sample_block_with_keys`, keeping the order. @@ -457,6 +463,18 @@ void Join::setSampleBlock(const Block & block) sample_block_with_columns_to_add.insert(ColumnWithTypeAndName(Join::match_helper_type, match_helper_name)); } +void Join::init(const Block & sample_block, size_t build_concurrency_) +{ + std::unique_lock lock(rwlock); + if (unlikely(initialized)) + throw Exception("Logical error: Join has been initialized", ErrorCodes::LOGICAL_ERROR); + initialized = true; + setBuildConcurrencyAndInitPool(build_concurrency_); + /// Choose data structure to use for JOIN. + initMapImpl(chooseMethod(getKeyColumns(key_names_right, sample_block), key_sizes)); + setSampleBlock(sample_block); +} + namespace { @@ -757,9 +775,9 @@ void recordFilteredRows(const Block & block, const String & filter_column, Colum bool Join::insertFromBlock(const Block & block) { - if (empty()) - throw Exception("Logical error: Join was not initialized", ErrorCodes::LOGICAL_ERROR); std::unique_lock lock(rwlock); + if (unlikely(!initialized)) + throw Exception("Logical error: Join was not initialized", ErrorCodes::LOGICAL_ERROR); blocks.push_back(block); Block * stored_block = &blocks.back(); return insertFromBlockInternal(stored_block, 0); @@ -768,11 +786,12 @@ bool Join::insertFromBlock(const Block & block) /// the block should be valid. void Join::insertFromBlock(const Block & block, size_t stream_index) { - assert(stream_index < build_concurrency); + std::shared_lock lock(rwlock); + assert(stream_index < getBuildConcurrencyInternal()); + assert(stream_index < getNotJoinedStreamConcurrencyInternal()); - if (empty()) + if (unlikely(!initialized)) throw Exception("Logical error: Join was not initialized", ErrorCodes::LOGICAL_ERROR); - std::shared_lock lock(rwlock); Block * stored_block = nullptr; { std::lock_guard lk(blocks_lock); @@ -868,16 +887,16 @@ bool Join::insertFromBlockInternal(Block * stored_block, size_t stream_index) if (!getFullness(kind)) { if (strictness == ASTTableJoin::Strictness::Any) - insertFromBlockImpl(type, maps_any, rows, key_columns, key_sizes, collators, stored_block, null_map, nullptr, stream_index, build_concurrency, *pools[stream_index]); + insertFromBlockImpl(type, maps_any, rows, key_columns, key_sizes, collators, stored_block, null_map, nullptr, stream_index, getBuildConcurrencyInternal(), *pools[stream_index]); else - insertFromBlockImpl(type, maps_all, rows, key_columns, key_sizes, collators, stored_block, null_map, nullptr, stream_index, build_concurrency, *pools[stream_index]); + insertFromBlockImpl(type, maps_all, rows, key_columns, key_sizes, collators, stored_block, null_map, nullptr, stream_index, getBuildConcurrencyInternal(), *pools[stream_index]); } else { if (strictness == ASTTableJoin::Strictness::Any) - insertFromBlockImpl(type, maps_any_full, rows, key_columns, key_sizes, collators, stored_block, null_map, rows_not_inserted_to_map[stream_index].get(), stream_index, build_concurrency, *pools[stream_index]); + insertFromBlockImpl(type, maps_any_full, rows, key_columns, key_sizes, collators, stored_block, null_map, rows_not_inserted_to_map[stream_index].get(), stream_index, getBuildConcurrencyInternal(), *pools[stream_index]); else - insertFromBlockImpl(type, maps_all_full, rows, key_columns, key_sizes, collators, stored_block, null_map, rows_not_inserted_to_map[stream_index].get(), stream_index, build_concurrency, *pools[stream_index]); + insertFromBlockImpl(type, maps_all_full, rows, key_columns, key_sizes, collators, stored_block, null_map, rows_not_inserted_to_map[stream_index].get(), stream_index, getBuildConcurrencyInternal(), *pools[stream_index]); } } @@ -1954,7 +1973,8 @@ class NonJoinedBlockInputStream : public IProfilingBlockInputStream , max_block_size(max_block_size_) , add_not_mapped_rows(true) { - if (step > parent.build_concurrency || index >= parent.build_concurrency) + size_t build_concurrency = parent.getBuildConcurrency(); + if (unlikely(step > build_concurrency || index >= build_concurrency)) throw Exception("The concurrency of NonJoinedBlockInputStream should not be larger than join build concurrency"); /** left_sample_block contains keys and "left" columns. diff --git a/dbms/src/Interpreters/Join.h b/dbms/src/Interpreters/Join.h index 89dad0d1ca6..01916aa1dcc 100644 --- a/dbms/src/Interpreters/Join.h +++ b/dbms/src/Interpreters/Join.h @@ -99,7 +99,6 @@ class Join ASTTableJoin::Kind kind_, ASTTableJoin::Strictness strictness_, const String & req_id, - size_t build_concurrency = 1, const TiDB::TiDBCollators & collators_ = TiDB::dummy_collators, const String & left_filter_column = "", const String & right_filter_column = "", @@ -109,17 +108,10 @@ class Join size_t max_block_size = 0, const String & match_helper_name = ""); - bool empty() { return type == Type::EMPTY; } - - /** Set information about structure of right hand of JOIN (joined data). + /** Call `setBuildConcurrencyAndInitPool`, `initMapImpl` and `setSampleBlock`. * You must call this method before subsequent calls to insertFromBlock. */ - void setSampleBlock(const Block & block); - - /** Add block of data from right hand of JOIN to the map. - * Returns false, if some limit was exceeded and you should not insert more data. - */ - bool insertFromBlockInternal(Block * stored_block, size_t stream_index); + void init(const Block & sample_block, size_t build_concurrency_ = 1); bool insertFromBlock(const Block & block); @@ -153,9 +145,19 @@ class Join bool useNulls() const { return use_nulls; } const Names & getLeftJoinKeys() const { return key_names_left; } - size_t getBuildConcurrency() const { return build_concurrency; } + + size_t getBuildConcurrency() const + { + std::shared_lock lock(rwlock); + return getBuildConcurrencyInternal(); + } + size_t getNotJoinedStreamConcurrency() const + { + std::shared_lock lock(rwlock); + return getNotJoinedStreamConcurrencyInternal(); + } + bool isBuildSetExceeded() const { return build_set_exceeded.load(); } - size_t getNotJoinedStreamConcurrency() const { return build_concurrency; }; enum BuildTableState { @@ -171,7 +173,7 @@ class Join const Block * block; size_t row_num; - RowRef() {} + RowRef() = default; RowRef(const Block * block_, size_t row_num_) : block(block_) , row_num(row_num_) @@ -183,7 +185,7 @@ class Join { RowRefList * next = nullptr; - RowRefList() {} + RowRefList() = default; RowRefList(const Block * block_, size_t row_num_) : RowRef(block_, row_num_) {} @@ -342,11 +344,40 @@ class Join */ mutable std::shared_mutex rwlock; - void init(Type type_); + bool initialized = false; + + size_t getBuildConcurrencyInternal() const + { + if (unlikely(build_concurrency == 0)) + throw Exception("Logical error: `setBuildConcurrencyAndInitPool` has not been called", ErrorCodes::LOGICAL_ERROR); + return build_concurrency; + } + size_t getNotJoinedStreamConcurrencyInternal() const + { + return getBuildConcurrencyInternal(); + } + + /// Initialize map implementations for various join types. + void initMapImpl(Type type_); + + /** Set information about structure of right hand of JOIN (joined data). + * You must call this method before subsequent calls to insertFromBlock. + */ + void setSampleBlock(const Block & block); + + /** Set Join build concurrency and init hash map. + * You must call this method before subsequent calls to insertFromBlock. + */ + void setBuildConcurrencyAndInitPool(size_t build_concurrency_); /// Throw an exception if blocks have different types of key columns. void checkTypesOfKeys(const Block & block_left, const Block & block_right) const; + /** Add block of data from right hand of JOIN to the map. + * Returns false, if some limit was exceeded and you should not insert more data. + */ + bool insertFromBlockInternal(Block * stored_block, size_t stream_index); + template void joinBlockImpl(Block & block, const Maps & maps) const; diff --git a/dbms/src/Storages/StorageJoin.cpp b/dbms/src/Storages/StorageJoin.cpp index 4e3c01c6574..4ca3e79a7ab 100644 --- a/dbms/src/Storages/StorageJoin.cpp +++ b/dbms/src/Storages/StorageJoin.cpp @@ -52,7 +52,7 @@ StorageJoin::StorageJoin( /// NOTE StorageJoin doesn't use join_use_nulls setting. join = std::make_shared(key_names, key_names, false /* use_nulls */, SizeLimits(), kind, strictness, /*req_id=*/""); - join->setSampleBlock(getSampleBlock().sortColumns()); + join->init(getSampleBlock().sortColumns()); restore(); } diff --git a/tests/fullstack-test/mpp/misc_join.test b/tests/fullstack-test/mpp/misc_join.test new file mode 100644 index 00000000000..61a1de49925 --- /dev/null +++ b/tests/fullstack-test/mpp/misc_join.test @@ -0,0 +1,41 @@ +# Copyright 2022 PingCAP, Ltd. +# +# 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. + +# Preparation. +mysql> drop table if exists test.t1; +mysql> create table test.t1 (id decimal(5,2), value bigint(20)); +mysql> insert into test.t1 values(1, 1),(2, 2); +mysql> drop table if exists test.t2; +mysql> create table test.t2 (id decimal(5,2), value bigint(20)); +mysql> insert into test.t2 values(1, 1),(2, 2),(3, 3),(4, 4); + +mysql> alter table test.t1 set tiflash replica 1 +mysql> alter table test.t2 set tiflash replica 1 +mysql> analyze table test.t1 +mysql> analyze table test.t2 + +func> wait_table test t1 +func> wait_table test t2 + +mysql> use test; set tidb_allow_mpp=1; set tidb_enforce_mpp=1; set tidb_isolation_read_engines='tiflash'; select * from t1 left join t2 on cast(t1.id as decimal(7,2)) = cast(t2.id as decimal(7,2)) and t1.id + cast(t2.id as decimal(7,2)) + t1.id > 10; ++------+-------+------+-------+ +| id | value | id | value | ++------+-------+------+-------+ +| 1.00 | 1 | NULL | NULL | +| 2.00 | 2 | NULL | NULL | ++------+-------+------+-------+ + +# Clean up. +mysql> drop table if exists test.t1 +mysql> drop table if exists test.t2 From dc976d32084741443efbae6244e4f3f139bd8543 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Tue, 24 May 2022 18:38:47 +0800 Subject: [PATCH 059/127] Change BlobFileId to uint64. (#4984) ref pingcap/tiflash#3594 --- dbms/src/Storages/Page/PageDefines.h | 2 +- dbms/src/Storages/Page/V3/LogFile/LogFormat.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dbms/src/Storages/Page/PageDefines.h b/dbms/src/Storages/Page/PageDefines.h index 46789419fbd..7feea494eb4 100644 --- a/dbms/src/Storages/Page/PageDefines.h +++ b/dbms/src/Storages/Page/PageDefines.h @@ -74,7 +74,7 @@ using PageFileIdAndLevels = std::vector; using PageSize = UInt64; -using BlobFileId = UInt32; +using BlobFileId = UInt64; using BlobFileOffset = UInt64; static constexpr BlobFileId INVALID_BLOBFILE_ID = 0; static constexpr BlobFileOffset INVALID_BLOBFILE_OFFSET = std::numeric_limits::max(); diff --git a/dbms/src/Storages/Page/V3/LogFile/LogFormat.h b/dbms/src/Storages/Page/V3/LogFile/LogFormat.h index c1151d1181f..aa273167109 100644 --- a/dbms/src/Storages/Page/V3/LogFile/LogFormat.h +++ b/dbms/src/Storages/Page/V3/LogFile/LogFormat.h @@ -56,7 +56,7 @@ static constexpr UInt32 PAYLOAD_FIELD_SIZE = sizeof(UInt16); // The checksum count begin at the `type` field in Header/RecyclableHeader static constexpr size_t CHECKSUM_START_OFFSET = Format::CHECKSUM_FIELD_SIZE + Format::PAYLOAD_FIELD_SIZE; -using LogNumberType = UInt32; +using LogNumberType = UInt64; // Header is // - checksum (`CHECKSUM_FIELD_SIZE` bytes From 3674439a1f1aeb09c70ad67f70cf5c2e961bd01a Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Tue, 24 May 2022 19:18:47 +0800 Subject: [PATCH 060/127] =?UTF-8?q?*:=20Rename=20the=20function=20with=20?= =?UTF-8?q?=E2=80=9CwithCollation=E2=80=9D=20postfix=20(#3790)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close pingcap/tiflash#4985 --- dbms/src/Columns/ColumnNullable.cpp | 37 ++++++++++--------- dbms/src/Columns/ColumnNullable.h | 4 +- dbms/src/Columns/ColumnString.cpp | 7 ++-- dbms/src/Columns/ColumnString.h | 4 +- dbms/src/Columns/IColumn.h | 8 ++-- dbms/src/Core/SortCursor.h | 4 +- .../DataStreams/WindowBlockInputStream.cpp | 12 +++--- dbms/src/Interpreters/sortBlock.cpp | 18 ++++----- 8 files changed, 49 insertions(+), 45 deletions(-) diff --git a/dbms/src/Columns/ColumnNullable.cpp b/dbms/src/Columns/ColumnNullable.cpp index 6348f3bdfd0..a3ef93f1ad3 100644 --- a/dbms/src/Columns/ColumnNullable.cpp +++ b/dbms/src/Columns/ColumnNullable.cpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace DB @@ -41,10 +42,10 @@ ColumnNullable::ColumnNullable(MutableColumnPtr && nested_column_, MutableColumn nested_column = nested_column_materialized; if (!getNestedColumn().canBeInsideNullable()) - throw Exception{getNestedColumn().getName() + " cannot be inside Nullable column", ErrorCodes::ILLEGAL_COLUMN}; + throw Exception(fmt::format("{} cannot be inside Nullable column", getNestedColumn().getName()), ErrorCodes::ILLEGAL_COLUMN); if (null_map->isColumnConst()) - throw Exception{"ColumnNullable cannot have constant null map", ErrorCodes::ILLEGAL_COLUMN}; + throw Exception("ColumnNullable cannot have constant null map", ErrorCodes::ILLEGAL_COLUMN); } @@ -106,7 +107,7 @@ void ColumnNullable::updateWeakHash32(WeakHash32 & hash, const TiDB::TiDBCollato auto s = size(); if (hash.getData().size() != s) - throw Exception("Size of WeakHash32 does not match size of column: column size is " + std::to_string(s) + ", hash size is " + std::to_string(hash.getData().size()), ErrorCodes::LOGICAL_ERROR); + throw Exception(fmt::format("Size of WeakHash32 does not match size of column: column size is {}, hash size is {}", s, hash.getData().size()), ErrorCodes::LOGICAL_ERROR); WeakHash32 old_hash = hash; nested_column->updateWeakHash32(hash, collator, sort_key_container); @@ -158,12 +159,12 @@ void ColumnNullable::get(size_t n, Field & res) const StringRef ColumnNullable::getDataAt(size_t /*n*/) const { - throw Exception{"Method getDataAt is not supported for " + getName(), ErrorCodes::NOT_IMPLEMENTED}; + throw Exception(fmt::format("Method getDataAt is not supported for {}", getName()), ErrorCodes::NOT_IMPLEMENTED); } void ColumnNullable::insertData(const char * /*pos*/, size_t /*length*/) { - throw Exception{"Method insertData is not supported for " + getName(), ErrorCodes::NOT_IMPLEMENTED}; + throw Exception(fmt::format("Method insertData is not supported for {}", getName()), ErrorCodes::NOT_IMPLEMENTED); } bool ColumnNullable::decodeTiDBRowV2Datum(size_t cursor, const String & raw_value, size_t length, bool force_decode) @@ -212,7 +213,7 @@ const char * ColumnNullable::deserializeAndInsertFromArena(const char * pos, con void ColumnNullable::insertRangeFrom(const IColumn & src, size_t start, size_t length) { - const ColumnNullable & nullable_col = static_cast(src); + const auto & nullable_col = static_cast(src); getNullMapColumn().insertRangeFrom(*nullable_col.null_map, start, length); getNestedColumn().insertRangeFrom(*nullable_col.nested_column, start, length); } @@ -233,7 +234,7 @@ void ColumnNullable::insert(const Field & x) void ColumnNullable::insertFrom(const IColumn & src, size_t n) { - const ColumnNullable & src_concrete = static_cast(src); + const auto & src_concrete = static_cast(src); getNestedColumn().insertFrom(src_concrete.getNestedColumn(), n); getNullMapData().push_back(src_concrete.getNullMapData()[n]); } @@ -285,24 +286,24 @@ std::tuple ColumnNullable::compareAtCheckNull(size_t n, size_t m, con return std::make_tuple(has_null, res); } -int ColumnNullable::compareAtWithCollation( +int ColumnNullable::compareAt( size_t n, size_t m, const IColumn & rhs_, int null_direction_hint, const ICollator & collator) const { - const ColumnNullable & nullable_rhs = static_cast(rhs_); + const auto & nullable_rhs = static_cast(rhs_); auto [has_null, res] = compareAtCheckNull(n, m, nullable_rhs, null_direction_hint); if (has_null) return res; const IColumn & nested_rhs = nullable_rhs.getNestedColumn(); - return getNestedColumn().compareAtWithCollation(n, m, nested_rhs, null_direction_hint, collator); + return getNestedColumn().compareAt(n, m, nested_rhs, null_direction_hint, collator); } int ColumnNullable::compareAt(size_t n, size_t m, const IColumn & rhs_, int null_direction_hint) const { - const ColumnNullable & nullable_rhs = static_cast(rhs_); + const auto & nullable_rhs = static_cast(rhs_); auto [has_null, res] = compareAtCheckNull(n, m, nullable_rhs, null_direction_hint); if (has_null) return res; @@ -310,7 +311,7 @@ int ColumnNullable::compareAt(size_t n, size_t m, const IColumn & rhs_, int null return getNestedColumn().compareAt(n, m, nested_rhs, null_direction_hint); } -void ColumnNullable::getPermutationWithCollation( +void ColumnNullable::getPermutation( const ICollator & collator, bool reverse, size_t limit, @@ -318,7 +319,7 @@ void ColumnNullable::getPermutationWithCollation( DB::IColumn::Permutation & res) const { /// Cannot pass limit because of unknown amount of NULLs. - getNestedColumn().getPermutationWithCollation(collator, reverse, 0, null_direction_hint, res); + getNestedColumn().getPermutation(collator, reverse, 0, null_direction_hint, res); adjustPermutationWithNullDirection(reverse, limit, null_direction_hint, res); } @@ -538,7 +539,7 @@ void ColumnNullable::applyNullMapImpl(const ColumnUInt8 & map) const NullMap & arr2 = map.getData(); if (arr1.size() != arr2.size()) - throw Exception{"Inconsistent sizes of ColumnNullable objects", ErrorCodes::LOGICAL_ERROR}; + throw Exception("Inconsistent sizes of ColumnNullable objects", ErrorCodes::LOGICAL_ERROR); for (size_t i = 0, size = arr1.size(); i < size; ++i) arr1[i] |= negative ^ arr2[i]; @@ -565,9 +566,11 @@ void ColumnNullable::applyNullMap(const ColumnNullable & other) void ColumnNullable::checkConsistency() const { if (null_map->size() != getNestedColumn().size()) - throw Exception("Logical error: Sizes of nested column and null map of Nullable column are not equal: null size is : " - + std::to_string(null_map->size()) + " column size is : " + std::to_string(getNestedColumn().size()), - ErrorCodes::SIZES_OF_NESTED_COLUMNS_ARE_INCONSISTENT); + throw Exception( + fmt::format("Logical error: Sizes of nested column and null map of Nullable column are not equal: null size is : {} column size is : {}", + null_map->size(), + getNestedColumn().size()), + ErrorCodes::SIZES_OF_NESTED_COLUMNS_ARE_INCONSISTENT); } diff --git a/dbms/src/Columns/ColumnNullable.h b/dbms/src/Columns/ColumnNullable.h index 7f2ca204942..5e1ea474a92 100644 --- a/dbms/src/Columns/ColumnNullable.h +++ b/dbms/src/Columns/ColumnNullable.h @@ -84,9 +84,9 @@ class ColumnNullable final : public COWPtrHelper ColumnPtr permute(const Permutation & perm, size_t limit) const override; std::tuple compareAtCheckNull(size_t n, size_t m, const ColumnNullable & rhs, int null_direction_hint) const; int compareAt(size_t n, size_t m, const IColumn & rhs_, int null_direction_hint) const override; - int compareAtWithCollation(size_t n, size_t m, const IColumn & rhs_, int null_direction_hint, const ICollator & collator) const override; + int compareAt(size_t n, size_t m, const IColumn & rhs_, int null_direction_hint, const ICollator & collator) const override; void getPermutation(bool reverse, size_t limit, int null_direction_hint, Permutation & res) const override; - void getPermutationWithCollation(const ICollator & collator, bool reverse, size_t limit, int null_direction_hint, Permutation & res) const override; + void getPermutation(const ICollator & collator, bool reverse, size_t limit, int null_direction_hint, Permutation & res) const override; void adjustPermutationWithNullDirection(bool reverse, size_t limit, int null_direction_hint, Permutation & res) const; void reserve(size_t n) override; size_t byteSize() const override; diff --git a/dbms/src/Columns/ColumnString.cpp b/dbms/src/Columns/ColumnString.cpp index de20fb7169b..54d4238616f 100644 --- a/dbms/src/Columns/ColumnString.cpp +++ b/dbms/src/Columns/ColumnString.cpp @@ -17,6 +17,7 @@ #include #include #include +#include /// Used in the `reserve` method, when the number of rows is known, but sizes of elements are not. #define APPROX_STRING_SIZE 64 @@ -80,7 +81,7 @@ void ColumnString::insertRangeFrom(const IColumn & src, size_t start, size_t len if (length == 0) return; - const ColumnString & src_concrete = static_cast(src); + const auto & src_concrete = static_cast(src); if (start + length > src_concrete.offsets.size()) throw Exception("Parameter out of bound in IColumnString::insertRangeFrom method.", @@ -310,7 +311,7 @@ void ColumnString::getExtremes(Field & min, Field & max) const int ColumnString::compareAtWithCollationImpl(size_t n, size_t m, const IColumn & rhs_, const ICollator & collator) const { - const ColumnString & rhs = static_cast(rhs_); + const auto & rhs = static_cast(rhs_); return collator.compare( reinterpret_cast(&chars[offsetAt(n)]), @@ -374,7 +375,7 @@ void ColumnString::updateWeakHash32(WeakHash32 & hash, const TiDB::TiDBCollatorP auto s = offsets.size(); if (hash.getData().size() != s) - throw Exception("Size of WeakHash32 does not match size of column: column size is " + std::to_string(s) + ", hash size is " + std::to_string(hash.getData().size()), ErrorCodes::LOGICAL_ERROR); + throw Exception(fmt::format("Size of WeakHash32 does not match size of column: column size is {}, hash size is {}", s, hash.getData().size()), ErrorCodes::LOGICAL_ERROR); const UInt8 * pos = chars.data(); UInt32 * hash_data = hash.getData().data(); diff --git a/dbms/src/Columns/ColumnString.h b/dbms/src/Columns/ColumnString.h index 7c12ef4b719..48b02388a6c 100644 --- a/dbms/src/Columns/ColumnString.h +++ b/dbms/src/Columns/ColumnString.h @@ -315,7 +315,7 @@ class ColumnString final : public COWPtrHelper return size > rhs_size ? 1 : (size < rhs_size ? -1 : 0); } - int compareAtWithCollation(size_t n, size_t m, const IColumn & rhs_, int, const ICollator & collator) const override + int compareAt(size_t n, size_t m, const IColumn & rhs_, int, const ICollator & collator) const override { return compareAtWithCollationImpl(n, m, rhs_, collator); } @@ -324,7 +324,7 @@ class ColumnString final : public COWPtrHelper void getPermutation(bool reverse, size_t limit, int nan_direction_hint, Permutation & res) const override; - void getPermutationWithCollation(const ICollator & collator, bool reverse, size_t limit, int, Permutation & res) const override + void getPermutation(const ICollator & collator, bool reverse, size_t limit, int, Permutation & res) const override { getPermutationWithCollationImpl(collator, reverse, limit, res); } diff --git a/dbms/src/Columns/IColumn.h b/dbms/src/Columns/IColumn.h index 3c764b1a6f8..a5a25e6e28c 100644 --- a/dbms/src/Columns/IColumn.h +++ b/dbms/src/Columns/IColumn.h @@ -239,9 +239,9 @@ class IColumn : public COWPtr */ virtual int compareAt(size_t n, size_t m, const IColumn & rhs, int nan_direction_hint) const = 0; - virtual int compareAtWithCollation(size_t, size_t, const IColumn &, int, const ICollator &) const + virtual int compareAt(size_t, size_t, const IColumn &, int, const ICollator &) const { - throw Exception("Method compareAtWithCollation is not supported for " + getName(), ErrorCodes::NOT_IMPLEMENTED); + throw Exception(fmt::format("Method compareAt with collation is not supported for {}" + getName()), ErrorCodes::NOT_IMPLEMENTED); } /** Returns a permutation that sorts elements of this column, @@ -252,9 +252,9 @@ class IColumn : public COWPtr */ virtual void getPermutation(bool reverse, size_t limit, int nan_direction_hint, Permutation & res) const = 0; - virtual void getPermutationWithCollation(const ICollator &, bool, size_t, int, Permutation &) const + virtual void getPermutation(const ICollator &, bool, size_t, int, Permutation &) const { - throw Exception("Method getPermutationWithCollation is not supported for " + getName(), ErrorCodes::NOT_IMPLEMENTED); + throw Exception(fmt::format("Method getPermutation with collation is not supported for {}", getName()), ErrorCodes::NOT_IMPLEMENTED); } /** Copies each element according offsets parameter. diff --git a/dbms/src/Core/SortCursor.h b/dbms/src/Core/SortCursor.h index b35b408de30..21c3d911958 100644 --- a/dbms/src/Core/SortCursor.h +++ b/dbms/src/Core/SortCursor.h @@ -219,7 +219,7 @@ struct SortCursorWithCollation int nulls_direction = impl->desc[i].nulls_direction; int res; if (impl->need_collation[i]) - res = impl->sort_columns[i]->compareAtWithCollation(lhs_pos, rhs_pos, *(rhs.impl->sort_columns[i]), nulls_direction, *impl->desc[i].collator); + res = impl->sort_columns[i]->compareAt(lhs_pos, rhs_pos, *(rhs.impl->sort_columns[i]), nulls_direction, *impl->desc[i].collator); else res = impl->sort_columns[i]->compareAt(lhs_pos, rhs_pos, *(rhs.impl->sort_columns[i]), nulls_direction); @@ -241,7 +241,7 @@ struct SortCursorWithCollation int res; if (impl->need_collation[i]) { - res = impl->sort_columns[i]->compareAtWithCollation(lhs_pos, rhs_pos, *(rhs.impl->sort_columns[i]), nulls_direction, *impl->desc[i].collator); + res = impl->sort_columns[i]->compareAt(lhs_pos, rhs_pos, *(rhs.impl->sort_columns[i]), nulls_direction, *impl->desc[i].collator); } else res = impl->sort_columns[i]->compareAt(lhs_pos, rhs_pos, *(rhs.impl->sort_columns[i]), nulls_direction); diff --git a/dbms/src/DataStreams/WindowBlockInputStream.cpp b/dbms/src/DataStreams/WindowBlockInputStream.cpp index 8d9fb13cbc5..bc63db52873 100644 --- a/dbms/src/DataStreams/WindowBlockInputStream.cpp +++ b/dbms/src/DataStreams/WindowBlockInputStream.cpp @@ -109,11 +109,11 @@ bool WindowBlockInputStream::isDifferentFromPrevPartition(UInt64 current_partiti const auto * compared_column = compared_columns[partition_column_indices[i]].get(); if (window_description.partition_by[i].collator) { - if (compared_column->compareAtWithCollation(current_partition_row, - prev_frame_start.row, - *reference_column, - 1 /* nan_direction_hint */, - *window_description.partition_by[i].collator) + if (compared_column->compareAt(current_partition_row, + prev_frame_start.row, + *reference_column, + 1 /* nan_direction_hint */, + *window_description.partition_by[i].collator) != 0) { return true; @@ -278,7 +278,7 @@ bool WindowBlockInputStream::arePeers(const RowNumber & x, const RowNumber & y) const auto * column_y = inputAt(y)[order_column_indices[i]].get(); if (window_description.order_by[i].collator) { - if (column_x->compareAtWithCollation(x.row, y.row, *column_y, 1 /* nan_direction_hint */, *window_description.order_by[i].collator) != 0) + if (column_x->compareAt(x.row, y.row, *column_y, 1 /* nan_direction_hint */, *window_description.order_by[i].collator) != 0) { return false; } diff --git a/dbms/src/Interpreters/sortBlock.cpp b/dbms/src/Interpreters/sortBlock.cpp index bfb0fa3afb7..81626d83fe2 100644 --- a/dbms/src/Interpreters/sortBlock.cpp +++ b/dbms/src/Interpreters/sortBlock.cpp @@ -51,7 +51,7 @@ static inline bool needCollation(const IColumn * column, const SortColumnDescrip { if (!description.collator) return false; - auto not_null_column = column->isColumnNullable() ? typeid_cast(column)->getNestedColumnPtr().get() : column; + const auto * not_null_column = column->isColumnNullable() ? typeid_cast(column)->getNestedColumnPtr().get() : column; if (not_null_column->isColumnConst()) return false; @@ -73,9 +73,9 @@ struct PartialSortingLess bool operator()(size_t a, size_t b) const { - for (ColumnsWithSortDescriptions::const_iterator it = columns.begin(); it != columns.end(); ++it) + for (const auto & column : columns) { - int res = it->second.direction * it->first->compareAt(a, b, *it->first, it->second.nulls_direction); + int res = column.second.direction * column.first->compareAt(a, b, *column.first, column.second.nulls_direction); if (res < 0) return true; else if (res > 0) @@ -95,15 +95,15 @@ struct PartialSortingLessWithCollation bool operator()(size_t a, size_t b) const { - for (ColumnsWithSortDescriptions::const_iterator it = columns.begin(); it != columns.end(); ++it) + for (const auto & column : columns) { int res; - if (needCollation(it->first, it->second)) - res = it->first->compareAtWithCollation(a, b, *it->first, it->second.nulls_direction, *it->second.collator); + if (needCollation(column.first, column.second)) + res = column.first->compareAt(a, b, *column.first, column.second.nulls_direction, *column.second.collator); else - res = it->first->compareAt(a, b, *it->first, it->second.nulls_direction); + res = column.first->compareAt(a, b, *column.first, column.second.nulls_direction); - res *= it->second.direction; + res *= column.second.direction; if (res < 0) return true; else if (res > 0) @@ -130,7 +130,7 @@ void sortBlock(Block & block, const SortDescription & description, size_t limit) IColumn::Permutation perm; if (needCollation(column, description[0])) - column->getPermutationWithCollation(*description[0].collator, reverse, limit, description[0].nulls_direction, perm); + column->getPermutation(*description[0].collator, reverse, limit, description[0].nulls_direction, perm); else column->getPermutation(reverse, limit, description[0].nulls_direction, perm); From 1412c25201e37596d65d3c54b1906b043d2a1648 Mon Sep 17 00:00:00 2001 From: JaySon Date: Tue, 24 May 2022 20:10:47 +0800 Subject: [PATCH 061/127] PageStorage: Fix the WAL snapshot dumped into file may contains invalid "being_ref_count" (#4987) close pingcap/tiflash#4957, close pingcap/tiflash#4986 --- dbms/src/Encryption/FileProvider.cpp | 6 +- dbms/src/Encryption/FileProvider.h | 1 + dbms/src/Encryption/MockKeyManager.cpp | 6 ++ dbms/src/Encryption/MockKeyManager.h | 11 ++- dbms/src/Storages/DeltaMerge/StoragePool.cpp | 2 +- dbms/src/Storages/Page/PageStorage.h | 3 +- dbms/src/Storages/Page/V3/PageDirectory.cpp | 22 ++++- dbms/src/Storages/Page/V3/PageDirectory.h | 2 +- .../Storages/Page/V3/PageDirectoryFactory.cpp | 9 +- .../Storages/Page/V3/PageDirectoryFactory.h | 3 + dbms/src/Storages/Page/V3/PageStorageImpl.cpp | 2 +- dbms/src/Storages/Page/V3/PageStorageImpl.h | 4 +- dbms/src/Storages/Page/V3/WALStore.cpp | 35 +++---- dbms/src/Storages/Page/V3/WALStore.h | 79 ++++++++-------- .../Storages/Page/V3/tests/entries_helper.h | 5 +- .../Page/V3/tests/gtest_page_directory.cpp | 92 ++++++++++++++++++- .../Page/V3/tests/gtest_page_storage.cpp | 29 ++++++ .../Page/V3/tests/gtest_wal_store.cpp | 10 +- dbms/src/Storages/Page/WALRecoveryMode.h | 61 ++++++++++++ 19 files changed, 295 insertions(+), 87 deletions(-) create mode 100644 dbms/src/Storages/Page/WALRecoveryMode.h diff --git a/dbms/src/Encryption/FileProvider.cpp b/dbms/src/Encryption/FileProvider.cpp index b4666cf6a71..f2f96fa8568 100644 --- a/dbms/src/Encryption/FileProvider.cpp +++ b/dbms/src/Encryption/FileProvider.cpp @@ -142,8 +142,12 @@ void FileProvider::deleteRegularFile(const String & file_path_, const Encryption { throw DB::TiFlashException("File: " + data_file.path() + " is not a regular file", Errors::Encryption::Internal); } - key_manager->deleteFile(encryption_path_.full_path, true); + // Remove the file on disk before removing the encryption key. Or we may leave an encrypted file without the encryption key + // and the encrypted file can not be read. + // In the worst case that TiFlash crash between removing the file on disk and removing the encryption key, we may leave + // the encryption key not deleted. However, this is a rare case and won't cause serious problem. data_file.remove(false); + key_manager->deleteFile(encryption_path_.full_path, true); } } diff --git a/dbms/src/Encryption/FileProvider.h b/dbms/src/Encryption/FileProvider.h index 79eec8f632b..1d23af7a2db 100644 --- a/dbms/src/Encryption/FileProvider.h +++ b/dbms/src/Encryption/FileProvider.h @@ -67,6 +67,7 @@ class FileProvider // If dir_path_as_encryption_path is true, use dir_path_ as EncryptionPath // If false, use every file's path inside dir_path_ as EncryptionPath + // Note this method is not atomic, and after calling it, the files in dir_path_ cannot be read again. void deleteDirectory( const String & dir_path_, bool dir_path_as_encryption_path = false, diff --git a/dbms/src/Encryption/MockKeyManager.cpp b/dbms/src/Encryption/MockKeyManager.cpp index d125961fd06..bbaeb37848a 100644 --- a/dbms/src/Encryption/MockKeyManager.cpp +++ b/dbms/src/Encryption/MockKeyManager.cpp @@ -13,8 +13,10 @@ // limitations under the License. #include +#include #include #include +#include #include #include @@ -40,12 +42,14 @@ MockKeyManager::MockKeyManager(EncryptionMethod method_, const String & key_, co , key{key_} , iv{iv} , encryption_enabled{encryption_enabled_} + , logger(DB::Logger::get("MockKeyManager")) {} FileEncryptionInfo MockKeyManager::newFile(const String & fname) { if (encryption_enabled) { + LOG_FMT_TRACE(logger, "Create mock encryption [file={}]", fname); files.emplace_back(fname); } return getFile(fname); @@ -64,6 +68,7 @@ void MockKeyManager::deleteFile(const String & fname, bool throw_on_error) { if (*iter == fname) { + LOG_FMT_TRACE(logger, "Delete mock encryption [file={}]", fname); files.erase(iter); break; } @@ -80,6 +85,7 @@ void MockKeyManager::linkFile(const String & src_fname, const String & dst_fname { throw DB::Exception(fmt::format("Can't find file which name is {}", src_fname), DB::ErrorCodes::LOGICAL_ERROR); } + LOG_FMT_TRACE(logger, "Link mock encryption file [src_file={}] [dst_file={}]", src_fname, dst_fname); files.emplace_back(dst_fname); } } diff --git a/dbms/src/Encryption/MockKeyManager.h b/dbms/src/Encryption/MockKeyManager.h index 914e6ab1fe4..268bb00d129 100644 --- a/dbms/src/Encryption/MockKeyManager.h +++ b/dbms/src/Encryption/MockKeyManager.h @@ -20,12 +20,15 @@ namespace DB { -class MockKeyManager : public KeyManager +class Logger; +using LoggerPtr = std::shared_ptr; + +class MockKeyManager final : public KeyManager { public: - ~MockKeyManager() = default; + ~MockKeyManager() override = default; - MockKeyManager(bool encryption_enabled_ = true); + explicit MockKeyManager(bool encryption_enabled_ = true); MockKeyManager(EncryptionMethod method_, const String & key_, const String & iv, bool encryption_enabled_ = true); @@ -50,5 +53,7 @@ class MockKeyManager : public KeyManager String key; String iv; bool encryption_enabled; + + LoggerPtr logger; }; } // namespace DB diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.cpp b/dbms/src/Storages/DeltaMerge/StoragePool.cpp index a040c5b6c6a..fa765cd9b1d 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.cpp +++ b/dbms/src/Storages/DeltaMerge/StoragePool.cpp @@ -130,7 +130,7 @@ void GlobalStoragePool::restore() bool GlobalStoragePool::gc() { - return gc(Settings(), true, DELTA_MERGE_GC_PERIOD); + return gc(global_context.getSettingsRef(), true, DELTA_MERGE_GC_PERIOD); } bool GlobalStoragePool::gc(const Settings & settings, bool immediately, const Seconds & try_gc_period) diff --git a/dbms/src/Storages/Page/PageStorage.h b/dbms/src/Storages/Page/PageStorage.h index 479c368a585..78d800dc3af 100644 --- a/dbms/src/Storages/Page/PageStorage.h +++ b/dbms/src/Storages/Page/PageStorage.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -140,7 +141,7 @@ class PageStorage : private boost::noncopyable SettingUInt64 blob_block_alignment_bytes = 0; SettingUInt64 wal_roll_size = PAGE_META_ROLL_SIZE; - SettingUInt64 wal_recover_mode = 0; + SettingUInt64 wal_recover_mode = static_cast(WALRecoveryMode::TolerateCorruptedTailRecords); SettingUInt64 wal_max_persisted_log_files = MAX_PERSISTED_LOG_FILES; void reload(const Config & rhs) diff --git a/dbms/src/Storages/Page/V3/PageDirectory.cpp b/dbms/src/Storages/Page/V3/PageDirectory.cpp index 64a3fead674..d944fe422d3 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectory.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -1191,16 +1192,31 @@ PageDirectory::getEntriesByBlobIds(const std::vector & blob_ids) con return std::make_pair(std::move(blob_versioned_entries), total_page_size); } -bool PageDirectory::tryDumpSnapshot(const WriteLimiterPtr & write_limiter) +bool PageDirectory::tryDumpSnapshot(const ReadLimiterPtr & read_limiter, const WriteLimiterPtr & write_limiter) { bool done_any_io = false; // In order not to make read amplification too high, only apply compact logs when ... auto files_snap = wal->getFilesSnapshot(); if (files_snap.needSave(max_persisted_log_files)) { + // To prevent writes from affecting dumping snapshot (and vice versa), old log files + // are read from disk and a temporary PageDirectory is generated for dumping snapshot. + // The main reason write affect dumping snapshot is that we can not get a read-only + // `being_ref_count` by the function `createSnapshot()`. + assert(!files_snap.persisted_log_files.empty()); // should not be empty when `needSave` return true + auto log_num = files_snap.persisted_log_files.rbegin()->log_num; + auto identifier = fmt::format("{}_dump_{}", wal->name(), log_num); + auto snapshot_reader = wal->createReaderForFiles(identifier, files_snap.persisted_log_files, read_limiter); + PageDirectoryFactory factory; + // we just use the `collapsed_dir` to dump edit of the snapshot, should never call functions like `apply` that + // persist new logs into disk. So we pass `nullptr` as `wal` to the factory. + PageDirectoryPtr collapsed_dir = factory.createFromReader( + identifier, + std::move(snapshot_reader), + /*wal=*/nullptr); // The records persisted in `files_snap` is older than or equal to all records in `edit` - auto edit = dumpSnapshotToEdit(); - done_any_io = wal->saveSnapshot(std::move(files_snap), std::move(edit), write_limiter); + auto edit_from_disk = collapsed_dir->dumpSnapshotToEdit(); + done_any_io = wal->saveSnapshot(std::move(files_snap), std::move(edit_from_disk), write_limiter); } return done_any_io; } diff --git a/dbms/src/Storages/Page/V3/PageDirectory.h b/dbms/src/Storages/Page/V3/PageDirectory.h index a3c6b079fee..39b5a05a40a 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.h +++ b/dbms/src/Storages/Page/V3/PageDirectory.h @@ -358,7 +358,7 @@ class PageDirectory void gcApply(PageEntriesEdit && migrated_edit, const WriteLimiterPtr & write_limiter = nullptr); - bool tryDumpSnapshot(const WriteLimiterPtr & write_limiter = nullptr); + bool tryDumpSnapshot(const ReadLimiterPtr & read_limiter = nullptr, const WriteLimiterPtr & write_limiter = nullptr); PageEntriesV3 gcInMemEntries(); diff --git a/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp b/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp index 9d20e0a64ab..483c5073ab5 100644 --- a/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp @@ -31,7 +31,12 @@ namespace PS::V3 PageDirectoryPtr PageDirectoryFactory::create(String storage_name, FileProviderPtr & file_provider, PSDiskDelegatorPtr & delegator, WALStore::Config config) { auto [wal, reader] = WALStore::create(storage_name, file_provider, delegator, config); - PageDirectoryPtr dir = std::make_unique(std::move(storage_name), std::move(wal), config.max_persisted_log_files); + return createFromReader(storage_name, reader, std::move(wal)); +} + +PageDirectoryPtr PageDirectoryFactory::createFromReader(String storage_name, WALStoreReaderPtr reader, WALStorePtr wal) +{ + PageDirectoryPtr dir = std::make_unique(storage_name, std::move(wal)); loadFromDisk(dir, std::move(reader)); // Reset the `sequence` to the maximum of persisted. @@ -40,7 +45,7 @@ PageDirectoryPtr PageDirectoryFactory::create(String storage_name, FileProviderP // After restoring from the disk, we need cleanup all invalid entries in memory, or it will // try to run GC again on some entries that are already marked as invalid in BlobStore. dir->gcInMemEntries(); - LOG_FMT_INFO(DB::Logger::get("PageDirectoryFactory"), "PageDirectory restored [max_page_id={}] [max_applied_ver={}]", dir->getMaxId(), dir->sequence); + LOG_FMT_INFO(DB::Logger::get("PageDirectoryFactory", storage_name), "PageDirectory restored [max_page_id={}] [max_applied_ver={}]", dir->getMaxId(), dir->sequence); if (blob_stats) { diff --git a/dbms/src/Storages/Page/V3/PageDirectoryFactory.h b/dbms/src/Storages/Page/V3/PageDirectoryFactory.h index 185e8fd19a5..a922db3b497 100644 --- a/dbms/src/Storages/Page/V3/PageDirectoryFactory.h +++ b/dbms/src/Storages/Page/V3/PageDirectoryFactory.h @@ -17,6 +17,7 @@ #include #include #include +#include namespace DB { @@ -47,6 +48,8 @@ class PageDirectoryFactory PageDirectoryPtr create(String storage_name, FileProviderPtr & file_provider, PSDiskDelegatorPtr & delegator, WALStore::Config config); + PageDirectoryPtr createFromReader(String storage_name, WALStoreReaderPtr reader, WALStorePtr wal); + // just for test PageDirectoryPtr createFromEdit(String storage_name, FileProviderPtr & file_provider, PSDiskDelegatorPtr & delegator, const PageEntriesEdit & edit); diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp index 58fe4b4dd4c..cfa07199637 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp @@ -289,7 +289,7 @@ bool PageStorageImpl::gcImpl(bool /*not_skip*/, const WriteLimiterPtr & write_li // 1. Do the MVCC gc, clean up expired snapshot. // And get the expired entries. - if (page_directory->tryDumpSnapshot(write_limiter)) + if (page_directory->tryDumpSnapshot(read_limiter, write_limiter)) { GET_METRIC(tiflash_storage_page_gc_count, type_v3_mvcc_dumped).Increment(); } diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.h b/dbms/src/Storages/Page/V3/PageStorageImpl.h index 082adb8df34..50d160e81da 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.h +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.h @@ -34,7 +34,7 @@ class PageStorageImpl : public DB::PageStorage const Config & config_, const FileProviderPtr & file_provider_); - ~PageStorageImpl(); + ~PageStorageImpl() override; static BlobStore::Config parseBlobConfig(const Config & config) { @@ -54,8 +54,8 @@ class PageStorageImpl : public DB::PageStorage WALStore::Config wal_config; wal_config.roll_size = config.wal_roll_size; - wal_config.wal_recover_mode = config.wal_recover_mode; wal_config.max_persisted_log_files = config.wal_max_persisted_log_files; + wal_config.setRecoverMode(config.wal_recover_mode); return wal_config; } diff --git a/dbms/src/Storages/Page/V3/WALStore.cpp b/dbms/src/Storages/Page/V3/WALStore.cpp index 1f1eaf3bc33..c7f11ee8b3c 100644 --- a/dbms/src/Storages/Page/V3/WALStore.cpp +++ b/dbms/src/Storages/Page/V3/WALStore.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include #include @@ -46,7 +47,7 @@ std::pair WALStore::create( auto reader = WALStoreReader::create(storage_name, provider, delegator, - static_cast(config.wal_recover_mode.get())); + config.getRecoverMode()); // Create a new LogFile for writing new logs auto last_log_num = reader->lastLogNum() + 1; // TODO reuse old file return { @@ -54,17 +55,23 @@ std::pair WALStore::create( reader}; } +WALStoreReaderPtr WALStore::createReaderForFiles(const String & identifier, const LogFilenameSet & log_filenames, const ReadLimiterPtr & read_limiter) +{ + return WALStoreReader::create(identifier, provider, log_filenames, config.getRecoverMode(), read_limiter); +} + WALStore::WALStore( - String storage_name, + String storage_name_, const PSDiskDelegatorPtr & delegator_, const FileProviderPtr & provider_, Format::LogNumberType last_log_num_, WALStore::Config config_) - : delegator(delegator_) + : storage_name(std::move(storage_name_)) + , delegator(delegator_) , provider(provider_) , last_log_num(last_log_num_) , wal_paths_index(0) - , logger(Logger::get("WALStore", std::move(storage_name))) + , logger(Logger::get("WALStore", storage_name)) , config(config_) { } @@ -186,7 +193,7 @@ bool WALStore::saveSnapshot(FilesSnapshot && files_snap, PageEntriesEdit && dire LOG_FMT_INFO(logger, "Saving directory snapshot"); - // Use {largest_log_num + 1, 1} to save the `edit` + // Use {largest_log_num, 1} to save the `edit` const auto log_num = files_snap.persisted_log_files.rbegin()->log_num; // Create a temporary file for saving directory snapshot auto [compact_log, log_filename] = createLogWriter({log_num, 1}, /*manual_flush*/ true); @@ -212,25 +219,11 @@ bool WALStore::saveSnapshot(FilesSnapshot && files_snap, PageEntriesEdit && dire true); LOG_FMT_INFO(logger, "Rename log file to normal done [fullname={}]", normal_fullname); - // #define ARCHIVE_COMPACTED_LOGS // keep for debug - // Remove compacted log files. for (const auto & filename : files_snap.persisted_log_files) { - if (auto f = Poco::File(filename.fullname(LogFileStage::Normal)); f.exists()) - { -#ifndef ARCHIVE_COMPACTED_LOGS - f.remove(); -#else - const Poco::Path archive_path(delegator->defaultPath(), "archive"); - Poco::File archive_dir(archive_path); - if (!archive_dir.exists()) - archive_dir.createDirectory(); - auto dest = archive_path.toString() + "/" + filename.filename(LogFileStage::Normal); - f.moveTo(dest); - LOG_FMT_INFO(logger, "archive {} to {}", filename.fullname(LogFileStage::Normal), dest); -#endif - } + const auto log_fullname = filename.fullname(LogFileStage::Normal); + provider->deleteRegularFile(log_fullname, EncryptionPath(log_fullname, "")); } FmtBuffer fmt_buf; diff --git a/dbms/src/Storages/Page/V3/WALStore.h b/dbms/src/Storages/Page/V3/WALStore.h index 039903a8608..f1ea00d3562 100644 --- a/dbms/src/Storages/Page/V3/WALStore.h +++ b/dbms/src/Storages/Page/V3/WALStore.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -34,45 +35,7 @@ class PSDiskDelegator; using PSDiskDelegatorPtr = std::shared_ptr; namespace PS::V3 { -enum class WALRecoveryMode : UInt8 -{ - // Original levelDB recovery - // - // We tolerate the last record in any log to be incomplete due to a crash - // while writing it. Zeroed bytes from preallocation are also tolerated in the - // trailing data of any log. - // - // Use case: Applications for which updates, once applied, must not be rolled - // back even after a crash-recovery. In this recovery mode, RocksDB guarantees - // this as long as `WritableFile::Append()` writes are durable. In case the - // user needs the guarantee in more situations (e.g., when - // `WritableFile::Append()` writes to page cache, but the user desires this - // guarantee in face of power-loss crash-recovery), RocksDB offers various - // mechanisms to additionally invoke `WritableFile::Sync()` in order to - // strengthen the guarantee. - // - // This differs from `kPointInTimeRecovery` in that, in case a corruption is - // detected during recovery, this mode will refuse to open the DB. Whereas, - // `kPointInTimeRecovery` will stop recovery just before the corruption since - // that is a valid point-in-time to which to recover. - TolerateCorruptedTailRecords = 0x00, - // Recover from clean shutdown - // We don't expect to find any corruption in the WAL - // Use case : This is ideal for unit tests and rare applications that - // can require high consistency guarantee - AbsoluteConsistency = 0x01, - // Recover to point-in-time consistency (default) - // We stop the WAL playback on discovering WAL inconsistency - // Use case : Ideal for systems that have disk controller cache like - // hard disk, SSD without super capacitor that store related data - PointInTimeRecovery = 0x02, - // Recovery after a disaster - // We ignore any corruption in the WAL and try to salvage as much data as - // possible - // Use case : Ideal for last ditch effort to recover data or systems that - // operate with low grade unrelated data - SkipAnyCorruptedRecords = 0x03, -}; + class WALStore; using WALStorePtr = std::unique_ptr; @@ -86,30 +49,56 @@ class WALStore struct Config { SettingUInt64 roll_size = PAGE_META_ROLL_SIZE; - SettingUInt64 wal_recover_mode = 0; SettingUInt64 max_persisted_log_files = MAX_PERSISTED_LOG_FILES; + + private: + SettingUInt64 wal_recover_mode = 0; + + public: + void setRecoverMode(UInt64 recover_mode) + { + if (unlikely(recover_mode != static_cast(WALRecoveryMode::TolerateCorruptedTailRecords) + && recover_mode != static_cast(WALRecoveryMode::AbsoluteConsistency) + && recover_mode != static_cast(WALRecoveryMode::PointInTimeRecovery) + && recover_mode != static_cast(WALRecoveryMode::SkipAnyCorruptedRecords))) + { + throw Exception("Unknow recover mode [num={}]", recover_mode); + } + wal_recover_mode = recover_mode; + } + + WALRecoveryMode getRecoverMode() + { + return static_cast(wal_recover_mode.get()); + } }; constexpr static const char * wal_folder_prefix = "/wal"; static std::pair create( - String storage_name, + String storage_name_, FileProviderPtr & provider, PSDiskDelegatorPtr & delegator, WALStore::Config config); + WALStoreReaderPtr createReaderForFiles(const String & identifier, const LogFilenameSet & log_filenames, const ReadLimiterPtr & read_limiter); + void apply(PageEntriesEdit & edit, const PageVersion & version, const WriteLimiterPtr & write_limiter = nullptr); void apply(const PageEntriesEdit & edit, const WriteLimiterPtr & write_limiter = nullptr); struct FilesSnapshot { Format::LogNumberType current_writting_log_num; + // The log files to generate snapshot from. Sorted by . + // If the WAL log file is not inited, it is an empty set. LogFilenameSet persisted_log_files; - bool needSave(const size_t & max_size) const + // Note that persisted_log_files should not be empty for needSave() == true, + // cause we get the largest log num from persisted_log_files as the new + // file name. + bool needSave(const size_t max_size) const { - // TODO: Make it configurable and check the reasonable of this number return persisted_log_files.size() > max_size; } }; @@ -121,6 +110,8 @@ class WALStore PageEntriesEdit && directory_snap, const WriteLimiterPtr & write_limiter = nullptr); + const String & name() { return storage_name; } + private: WALStore( String storage_name, @@ -134,6 +125,8 @@ class WALStore const std::pair & new_log_lvl, bool manual_flush); +private: + const String storage_name; PSDiskDelegatorPtr delegator; FileProviderPtr provider; mutable std::mutex log_file_mutex; diff --git a/dbms/src/Storages/Page/V3/tests/entries_helper.h b/dbms/src/Storages/Page/V3/tests/entries_helper.h index cce59919ec8..19e42755dae 100644 --- a/dbms/src/Storages/Page/V3/tests/entries_helper.h +++ b/dbms/src/Storages/Page/V3/tests/entries_helper.h @@ -25,6 +25,7 @@ #include #include #include +#include namespace DB { @@ -221,7 +222,9 @@ inline ::testing::AssertionResult getEntryNotExist( String error; try { - auto id_entry = dir->get(page_id, snap); + auto id_entry = dir->getOrNull(page_id, snap); + if (!id_entry.second.isValid()) + return ::testing::AssertionSuccess(); error = fmt::format( "Expect entry [id={}] from {} with snap{} not exist, but got <{}.{}, {}>", page_id_expr, diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp index 151b3b50657..6e2b0efa1ea 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp @@ -50,12 +50,17 @@ class PageDirectoryTest : public DB::base::TiFlashStorageTestBasic { auto path = getTemporaryPath(); dropDataOnDisk(path); + dir = restoreFromDisk(); + } + static PageDirectoryPtr restoreFromDisk() + { + auto path = getTemporaryPath(); auto ctx = DB::tests::TiFlashTestEnv::getContext(); FileProviderPtr provider = ctx.getFileProvider(); PSDiskDelegatorPtr delegator = std::make_shared(path); PageDirectoryFactory factory; - dir = factory.create("PageDirectoryTest", provider, delegator, WALStore::Config()); + return factory.create("PageDirectoryTest", provider, delegator, WALStore::Config()); } protected: @@ -1286,6 +1291,60 @@ class PageDirectoryGCTest : public PageDirectoryTest dir->apply(std::move(edit)); \ } +TEST_F(PageDirectoryGCTest, ManyEditsAndDumpSnapshot) +{ + PageId page_id0 = 50; + PageId page_id1 = 51; + PageId page_id2 = 52; + PageId page_id3 = 53; + + PageEntryV3 last_entry_for_0; + constexpr size_t num_edits_test = 50000; + for (size_t i = 0; i < num_edits_test; ++i) + { + { + INSERT_ENTRY(page_id0, i); + last_entry_for_0 = entry_vi; + } + { + INSERT_ENTRY(page_id1, i); + } + } + INSERT_DELETE(page_id1); + EXPECT_TRUE(dir->tryDumpSnapshot()); + dir.reset(); + + dir = restoreFromDisk(); + { + auto snap = dir->createSnapshot(); + ASSERT_SAME_ENTRY(dir->get(page_id0, snap).second, last_entry_for_0); + EXPECT_ENTRY_NOT_EXIST(dir, page_id1, snap); + } + + PageEntryV3 last_entry_for_2; + for (size_t i = 0; i < num_edits_test; ++i) + { + { + INSERT_ENTRY(page_id2, i); + last_entry_for_2 = entry_vi; + } + { + INSERT_ENTRY(page_id3, i); + } + } + INSERT_DELETE(page_id3); + EXPECT_TRUE(dir->tryDumpSnapshot()); + + dir = restoreFromDisk(); + { + auto snap = dir->createSnapshot(); + ASSERT_SAME_ENTRY(dir->get(page_id0, snap).second, last_entry_for_0); + EXPECT_ENTRY_NOT_EXIST(dir, page_id1, snap); + ASSERT_SAME_ENTRY(dir->get(page_id2, snap).second, last_entry_for_2); + EXPECT_ENTRY_NOT_EXIST(dir, page_id3, snap); + } +} + TEST_F(PageDirectoryGCTest, GCPushForward) try { @@ -1931,7 +1990,6 @@ try auto s0 = dir->createSnapshot(); auto edit = dir->dumpSnapshotToEdit(s0); - edit.size(); auto restore_from_edit = [](const PageEntriesEdit & edit) { auto deseri_edit = DB::PS::V3::ser::deserializeFrom(DB::PS::V3::ser::serializeTo(edit)); auto ctx = DB::tests::TiFlashTestEnv::getContext(); @@ -2214,6 +2272,36 @@ try } CATCH +TEST_F(PageDirectoryGCTest, CleanAfterDecreaseRef) +try +{ + PageEntryV3 entry_50_1{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_50_2{.file_id = 2, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + + auto restore_from_edit = [](const PageEntriesEdit & edit) { + auto ctx = ::DB::tests::TiFlashTestEnv::getContext(); + auto provider = ctx.getFileProvider(); + auto path = getTemporaryPath(); + PSDiskDelegatorPtr delegator = std::make_shared(path); + PageDirectoryFactory factory; + auto d = factory.createFromEdit(getCurrentTestName(), provider, delegator, edit); + return d; + }; + + { + PageEntriesEdit edit; + edit.put(50, entry_50_1); + edit.put(50, entry_50_2); + edit.ref(51, 50); + edit.del(50); + edit.del(51); + auto restored_dir = restore_from_edit(edit); + auto page_ids = restored_dir->getAllPageIds(); + ASSERT_EQ(page_ids.size(), 0); + } +} +CATCH + #undef INSERT_ENTRY_TO #undef INSERT_ENTRY #undef INSERT_ENTRY_ACQ_SNAP diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp index 498fd4124e5..ce2ba0adaf4 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp @@ -1380,5 +1380,34 @@ try } CATCH +TEST_F(PageStorageTest, CleanAfterDecreaseRef) +try +{ + // Make it in log_1_0 + { + WriteBatch batch; + batch.putExternal(1, 0); + page_storage->write(std::move(batch)); + } + + page_storage = reopenWithConfig(config); + + // Make it in log_2_0 + { + WriteBatch batch; + batch.putExternal(1, 0); + batch.putRefPage(2, 1); + batch.delPage(1); + batch.delPage(2); + page_storage->write(std::move(batch)); + } + page_storage = reopenWithConfig(config); + + auto alive_ids = page_storage->getAliveExternalPageIds(TEST_NAMESPACE_ID); + ASSERT_EQ(alive_ids.size(), 0); +} +CATCH + + } // namespace PS::V3::tests } // namespace DB diff --git a/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp b/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp index 89c4e54f7e7..6d47adabbc5 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp @@ -596,11 +596,12 @@ TEST_P(WALStoreTest, ManyEdits) try { auto ctx = DB::tests::TiFlashTestEnv::getContext(); - auto provider = ctx.getFileProvider(); + auto enc_key_manager = std::make_shared(/*encryption_enabled_=*/true); + auto enc_provider = std::make_shared(enc_key_manager, true); auto path = getTemporaryPath(); // Stage 1. empty - auto [wal, reader] = WALStore::create(getCurrentTestName(), provider, delegator, config); + auto [wal, reader] = WALStore::create(getCurrentTestName(), enc_provider, delegator, config); ASSERT_NE(wal, nullptr); std::mt19937 rd; @@ -633,7 +634,7 @@ try size_t num_edits_read = 0; size_t num_pages_read = 0; - std::tie(wal, reader) = WALStore::create(getCurrentTestName(), provider, delegator, config); + std::tie(wal, reader) = WALStore::create(getCurrentTestName(), enc_provider, delegator, config); while (reader->remained()) { auto [ok, edit] = reader->next(); @@ -653,8 +654,7 @@ try LOG_FMT_INFO(&Poco::Logger::get("WALStoreTest"), "Done test for {} persist pages in {} edits", num_pages_read, num_edits_test); // Test for save snapshot (with encryption) - auto enc_key_manager = std::make_shared(/*encryption_enabled_=*/true); - auto enc_provider = std::make_shared(enc_key_manager, true); + LogFilenameSet persisted_log_files = WALStoreReader::listAllFiles(delegator, log); WALStore::FilesSnapshot file_snap{.current_writting_log_num = 100, // just a fake value .persisted_log_files = persisted_log_files}; diff --git a/dbms/src/Storages/Page/WALRecoveryMode.h b/dbms/src/Storages/Page/WALRecoveryMode.h new file mode 100644 index 00000000000..740c9ed37a5 --- /dev/null +++ b/dbms/src/Storages/Page/WALRecoveryMode.h @@ -0,0 +1,61 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once +#include + +namespace DB +{ + +enum class WALRecoveryMode : UInt8 +{ + // Original levelDB recovery + // + // We tolerate the last record in any log to be incomplete due to a crash + // while writing it. Zeroed bytes from preallocation are also tolerated in the + // trailing data of any log. + // + // Use case: Applications for which updates, once applied, must not be rolled + // back even after a crash-recovery. In this recovery mode, RocksDB guarantees + // this as long as `WritableFile::Append()` writes are durable. In case the + // user needs the guarantee in more situations (e.g., when + // `WritableFile::Append()` writes to page cache, but the user desires this + // guarantee in face of power-loss crash-recovery), RocksDB offers various + // mechanisms to additionally invoke `WritableFile::Sync()` in order to + // strengthen the guarantee. + // + // This differs from `kPointInTimeRecovery` in that, in case a corruption is + // detected during recovery, this mode will refuse to open the DB. Whereas, + // `kPointInTimeRecovery` will stop recovery just before the corruption since + // that is a valid point-in-time to which to recover. + TolerateCorruptedTailRecords = 0x00, + // Recover from clean shutdown + // We don't expect to find any corruption in the WAL + // Use case : This is ideal for unit tests and rare applications that + // can require high consistency guarantee + AbsoluteConsistency = 0x01, + // Recover to point-in-time consistency (default) + // We stop the WAL playback on discovering WAL inconsistency + // Use case : Ideal for systems that have disk controller cache like + // hard disk, SSD without super capacitor that store related data + PointInTimeRecovery = 0x02, + // Recovery after a disaster + // We ignore any corruption in the WAL and try to salvage as much data as + // possible + // Use case : Ideal for last ditch effort to recover data or systems that + // operate with low grade unrelated data + SkipAnyCorruptedRecords = 0x03, +}; + +} // namespace DB From 2d578951911fc5fe720d28e1b768cd8ab982341d Mon Sep 17 00:00:00 2001 From: Calvin Neo Date: Wed, 25 May 2022 15:10:47 +0800 Subject: [PATCH 062/127] update tiflash proxy for bugs (#4993) ref pingcap/tiflash#4952 --- contrib/tiflash-proxy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tiflash-proxy b/contrib/tiflash-proxy index 4c10d3bcf95..7578b816399 160000 --- a/contrib/tiflash-proxy +++ b/contrib/tiflash-proxy @@ -1 +1 @@ -Subproject commit 4c10d3bcf95b288d6bb12acdf1872f41004c512e +Subproject commit 7578b8163992ce933074135f8687ad447d88ea9b From 0f56b15a770c60949241bb37fa63f9136d6706f1 Mon Sep 17 00:00:00 2001 From: hehechen Date: Wed, 25 May 2022 15:56:47 +0800 Subject: [PATCH 063/127] Enable PageStorage V3 in UT (#4884) close pingcap/tiflash#4835 --- dbms/src/Interpreters/Context.cpp | 27 ++- dbms/src/Server/tests/gtest_server_config.cpp | 8 +- .../tests/gtest_dm_delta_merge_store.cpp | 193 ++++++++++++++++++ dbms/src/Storages/Page/PageStorage.h | 2 + .../V3/tests/gtest_page_storage_mix_mode.cpp | 9 +- .../tests/gtest_region_persister.cpp | 13 +- dbms/src/TestUtils/TiFlashTestEnv.cpp | 1 + dbms/src/TestUtils/TiFlashTestEnv.h | 2 +- 8 files changed, 229 insertions(+), 26 deletions(-) diff --git a/dbms/src/Interpreters/Context.cpp b/dbms/src/Interpreters/Context.cpp index 26e950d7798..5d967b388d0 100644 --- a/dbms/src/Interpreters/Context.cpp +++ b/dbms/src/Interpreters/Context.cpp @@ -162,7 +162,7 @@ struct ContextShared PathCapacityMetricsPtr path_capacity_ptr; /// Path capacity metrics FileProviderPtr file_provider; /// File provider. IORateLimiter io_rate_limiter; - PageStorageRunMode storage_run_mode; + PageStorageRunMode storage_run_mode = PageStorageRunMode::ONLY_V3; DM::GlobalStoragePoolPtr global_storage_pool; /// Named sessions. The user could specify session identifier to reuse settings and temporary tables in subsequent requests. @@ -714,7 +714,7 @@ Dependencies Context::getDependencies(const String & database_name, const String checkDatabaseAccessRightsImpl(db); } - ViewDependencies::const_iterator iter = shared->view_dependencies.find(DatabaseAndTableName(db, table_name)); + auto iter = shared->view_dependencies.find(DatabaseAndTableName(db, table_name)); if (iter == shared->view_dependencies.end()) return {}; @@ -728,7 +728,7 @@ bool Context::isTableExist(const String & database_name, const String & table_na String db = resolveDatabase(database_name, current_database); checkDatabaseAccessRightsImpl(db); - Databases::const_iterator it = shared->databases.find(db); + auto it = shared->databases.find(db); return shared->databases.end() != it && it->second->isTableExist(*this, table_name); } @@ -754,7 +754,7 @@ void Context::assertTableExists(const String & database_name, const String & tab String db = resolveDatabase(database_name, current_database); checkDatabaseAccessRightsImpl(db); - Databases::const_iterator it = shared->databases.find(db); + auto it = shared->databases.find(db); if (shared->databases.end() == it) throw Exception(fmt::format("Database {} doesn't exist", backQuoteIfNeed(db)), ErrorCodes::UNKNOWN_DATABASE); @@ -771,7 +771,7 @@ void Context::assertTableDoesntExist(const String & database_name, const String if (check_database_access_rights) checkDatabaseAccessRightsImpl(db); - Databases::const_iterator it = shared->databases.find(db); + auto it = shared->databases.find(db); if (shared->databases.end() != it && it->second->isTableExist(*this, table_name)) throw Exception(fmt::format("Table {}.{} already exists.", backQuoteIfNeed(db), backQuoteIfNeed(table_name)), ErrorCodes::TABLE_ALREADY_EXISTS); } @@ -826,7 +826,7 @@ Tables Context::getExternalTables() const StoragePtr Context::tryGetExternalTable(const String & table_name) const { - TableAndCreateASTs::const_iterator jt = external_tables.find(table_name); + auto jt = external_tables.find(table_name); if (external_tables.end() == jt) return StoragePtr(); @@ -864,7 +864,7 @@ StoragePtr Context::getTableImpl(const String & database_name, const String & ta String db = resolveDatabase(database_name, current_database); checkDatabaseAccessRightsImpl(db); - Databases::const_iterator it = shared->databases.find(db); + auto it = shared->databases.find(db); if (shared->databases.end() == it) { if (exception) @@ -894,7 +894,7 @@ void Context::addExternalTable(const String & table_name, const StoragePtr & sto StoragePtr Context::tryRemoveExternalTable(const String & table_name) { - TableAndCreateASTs::const_iterator it = external_tables.find(table_name); + auto it = external_tables.find(table_name); if (external_tables.end() == it) return StoragePtr(); @@ -954,7 +954,7 @@ std::unique_ptr Context::getDDLGuardIfTableDoesntExist(const String & { auto lock = getLock(); - Databases::const_iterator it = shared->databases.find(database); + auto it = shared->databases.find(database); if (shared->databases.end() != it && it->second->isTableExist(*this, table)) return {}; @@ -993,7 +993,7 @@ ASTPtr Context::getCreateTableQuery(const String & database_name, const String & ASTPtr Context::getCreateExternalTableQuery(const String & table_name) const { - TableAndCreateASTs::const_iterator jt = external_tables.find(table_name); + auto jt = external_tables.find(table_name); if (external_tables.end() == jt) throw Exception(fmt::format("Temporary table {} doesn't exist", backQuoteIfNeed(table_name)), ErrorCodes::UNKNOWN_TABLE); @@ -1088,7 +1088,7 @@ void Context::setCurrentQueryId(const String & query_id) UInt64 a; UInt64 b; }; - } random; + } random{}; { auto lock = getLock(); @@ -1650,9 +1650,8 @@ bool Context::initializeGlobalStoragePoolIfNeed(const PathPool & path_pool) auto lock = getLock(); if (shared->global_storage_pool) { - // Can't init GlobalStoragePool twice. - // otherwise the pagestorage instances in `StoragePool` for each table won't be updated and cause unexpected problem. - throw Exception("GlobalStoragePool has already been initialized.", ErrorCodes::LOGICAL_ERROR); + // GlobalStoragePool may be initialized many times in some test cases for restore. + LOG_WARNING(shared->log, "GlobalStoragePool has already been initialized."); } CurrentMetrics::set(CurrentMetrics::GlobalStorageRunMode, static_cast(shared->storage_run_mode)); if (shared->storage_run_mode == PageStorageRunMode::MIX_MODE || shared->storage_run_mode == PageStorageRunMode::ONLY_V3) diff --git a/dbms/src/Server/tests/gtest_server_config.cpp b/dbms/src/Server/tests/gtest_server_config.cpp index 53705f1a351..cf53a8d6c18 100644 --- a/dbms/src/Server/tests/gtest_server_config.cpp +++ b/dbms/src/Server/tests/gtest_server_config.cpp @@ -371,10 +371,10 @@ dt_page_gc_low_write_prob = 0.2 std::unique_ptr path_pool = std::make_unique(global_ctx.getPathPool().withTable("test", "t1", false)); std::unique_ptr storage_pool = std::make_unique(global_ctx, /*ns_id*/ 100, *path_pool, "test.t1"); - auto verify_storage_pool_reload_config = [&global_ctx](std::unique_ptr & storage_pool) { + auto verify_storage_pool_reload_config = [&](std::unique_ptr & storage_pool) { DB::Settings & settings = global_ctx.getSettingsRef(); - auto cfg = storage_pool->data_storage_v2->getSettings(); + auto cfg = storage_pool->dataWriter()->getSettings(); EXPECT_NE(cfg.gc_min_files, settings.dt_storage_pool_data_gc_min_file_num); EXPECT_NE(cfg.gc_min_legacy_num, settings.dt_storage_pool_data_gc_min_legacy_num); EXPECT_NE(cfg.gc_min_bytes, settings.dt_storage_pool_data_gc_min_bytes); @@ -384,9 +384,9 @@ dt_page_gc_low_write_prob = 0.2 EXPECT_NE(cfg.open_file_max_idle_time, settings.dt_open_file_max_idle_seconds); EXPECT_NE(cfg.prob_do_gc_when_write_is_low, settings.dt_page_gc_low_write_prob * 1000); - storage_pool->gc(settings, DM::StoragePool::Seconds(0)); + global_ctx.getGlobalStoragePool()->gc(); - cfg = storage_pool->data_storage_v2->getSettings(); + cfg = storage_pool->dataWriter()->getSettings(); EXPECT_EQ(cfg.gc_min_files, settings.dt_storage_pool_data_gc_min_file_num); EXPECT_EQ(cfg.gc_min_legacy_num, settings.dt_storage_pool_data_gc_min_legacy_num); EXPECT_EQ(cfg.gc_min_bytes, settings.dt_storage_pool_data_gc_min_bytes); diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp index 1d0e00a5b58..35e6c3d00c6 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp @@ -50,6 +50,7 @@ extern const char force_triggle_foreground_flush[]; extern const char force_set_segment_ingest_packs_fail[]; extern const char segment_merge_after_ingest_packs[]; extern const char force_set_segment_physical_split[]; +extern const char force_set_page_file_write_errno[]; } // namespace FailPoints namespace DM @@ -495,6 +496,198 @@ try } CATCH +TEST_P(DeltaMergeStoreRWTest, WriteCrashBeforeWalWithoutCache) +try +{ + const ColumnDefine col_str_define(2, "col2", std::make_shared()); + const ColumnDefine col_i8_define(3, "i8", std::make_shared()); + { + auto table_column_defines = DMTestEnv::getDefaultColumns(); + table_column_defines->emplace_back(col_str_define); + table_column_defines->emplace_back(col_i8_define); + + store = reload(table_column_defines); + } + + { + // check column structure + const auto & cols = store->getTableColumns(); + ASSERT_EQ(cols.size(), 5UL); + const auto & str_col = cols[3]; + ASSERT_EQ(str_col.name, col_str_define.name); + ASSERT_EQ(str_col.id, col_str_define.id); + ASSERT_TRUE(str_col.type->equals(*col_str_define.type)); + const auto & i8_col = cols[4]; + ASSERT_EQ(i8_col.name, col_i8_define.name); + ASSERT_EQ(i8_col.id, col_i8_define.id); + ASSERT_TRUE(i8_col.type->equals(*col_i8_define.type)); + } + + const size_t num_rows_write = 128; + { + // write to store + Block block; + { + block = DMTestEnv::prepareSimpleWriteBlock(0, num_rows_write, false); + // Add a column of col2:String for test + block.insert(DB::tests::createColumn( + createNumberStrings(0, num_rows_write), + col_str_define.name, + col_str_define.id)); + // Add a column of i8:Int8 for test + block.insert(DB::tests::createColumn( + createSignedNumbers(0, num_rows_write), + col_i8_define.name, + col_i8_define.id)); + } + db_context->getSettingsRef().dt_segment_delta_cache_limit_rows = 8; + FailPointHelper::enableFailPoint(FailPoints::force_set_page_file_write_errno); + ASSERT_THROW(store->write(*db_context, db_context->getSettingsRef(), block), DB::Exception); + try + { + store->write(*db_context, db_context->getSettingsRef(), block); + } + catch (DB::Exception & e) + { + if (e.code() != ErrorCodes::CANNOT_WRITE_TO_FILE_DESCRIPTOR) + throw; + } + } + FailPointHelper::disableFailPoint(FailPoints::force_set_page_file_write_errno); + + { + // read all columns from store + const auto & columns = store->getTableColumns(); + BlockInputStreamPtr in = store->read(*db_context, + db_context->getSettingsRef(), + columns, + {RowKeyRange::newAll(store->isCommonHandle(), store->getRowKeyColumnSize())}, + /* num_streams= */ 1, + /* max_version= */ std::numeric_limits::max(), + EMPTY_FILTER, + TRACING_NAME, + /* expected_block_size= */ 1024)[0]; + + size_t num_rows_read = 0; + in->readPrefix(); + while (Block block = in->read()) + { + num_rows_read += block.rows(); + } + in->readSuffix(); + ASSERT_EQ(num_rows_read, 0); + } +} +CATCH + +TEST_P(DeltaMergeStoreRWTest, WriteCrashBeforeWalWithCache) +try +{ + const ColumnDefine col_str_define(2, "col2", std::make_shared()); + const ColumnDefine col_i8_define(3, "i8", std::make_shared()); + { + auto table_column_defines = DMTestEnv::getDefaultColumns(); + table_column_defines->emplace_back(col_str_define); + table_column_defines->emplace_back(col_i8_define); + + store = reload(table_column_defines); + } + + { + // check column structure + const auto & cols = store->getTableColumns(); + ASSERT_EQ(cols.size(), 5UL); + const auto & str_col = cols[3]; + ASSERT_EQ(str_col.name, col_str_define.name); + ASSERT_EQ(str_col.id, col_str_define.id); + ASSERT_TRUE(str_col.type->equals(*col_str_define.type)); + const auto & i8_col = cols[4]; + ASSERT_EQ(i8_col.name, col_i8_define.name); + ASSERT_EQ(i8_col.id, col_i8_define.id); + ASSERT_TRUE(i8_col.type->equals(*col_i8_define.type)); + } + + const size_t num_rows_write = 128; + { + // write to store + Block block; + { + block = DMTestEnv::prepareSimpleWriteBlock(0, num_rows_write, false); + // Add a column of col2:String for test + block.insert(DB::tests::createColumn( + createNumberStrings(0, num_rows_write), + col_str_define.name, + col_str_define.id)); + // Add a column of i8:Int8 for test + block.insert(DB::tests::createColumn( + createSignedNumbers(0, num_rows_write), + col_i8_define.name, + col_i8_define.id)); + } + + FailPointHelper::enableFailPoint(FailPoints::force_set_page_file_write_errno); + store->write(*db_context, db_context->getSettingsRef(), block); + ASSERT_THROW(store->flushCache(*db_context, RowKeyRange::newAll(store->isCommonHandle(), store->getRowKeyColumnSize())), DB::Exception); + try + { + store->flushCache(*db_context, RowKeyRange::newAll(store->isCommonHandle(), store->getRowKeyColumnSize())); + } + catch (DB::Exception & e) + { + if (e.code() != ErrorCodes::CANNOT_WRITE_TO_FILE_DESCRIPTOR) + throw; + } + } + FailPointHelper::disableFailPoint(FailPoints::force_set_page_file_write_errno); + + { + // read all columns from store + const auto & columns = store->getTableColumns(); + BlockInputStreamPtr in = store->read(*db_context, + db_context->getSettingsRef(), + columns, + {RowKeyRange::newAll(store->isCommonHandle(), store->getRowKeyColumnSize())}, + /* num_streams= */ 1, + /* max_version= */ std::numeric_limits::max(), + EMPTY_FILTER, + TRACING_NAME, + /* expected_block_size= */ 1024)[0]; + + size_t num_rows_read = 0; + in->readPrefix(); + while (Block block = in->read()) + { + num_rows_read += block.rows(); + for (auto && iter : block) + { + auto c = iter.column; + for (Int64 i = 0; i < Int64(c->size()); ++i) + { + if (iter.name == DMTestEnv::pk_name) + { + //printf("pk:%lld\n", c->getInt(i)); + EXPECT_EQ(c->getInt(i), i); + } + else if (iter.name == col_str_define.name) + { + //printf("%s:%s\n", col_str_define.name.c_str(), c->getDataAt(i).data); + EXPECT_EQ(c->getDataAt(i), DB::toString(i)); + } + else if (iter.name == col_i8_define.name) + { + //printf("%s:%lld\n", col_i8_define.name.c_str(), c->getInt(i)); + Int64 num = i * (i % 2 == 0 ? -1 : 1); + EXPECT_EQ(c->getInt(i), num); + } + } + } + } + in->readSuffix(); + ASSERT_EQ(num_rows_read, num_rows_write); + } +} +CATCH + TEST_P(DeltaMergeStoreRWTest, DeleteRead) try { diff --git a/dbms/src/Storages/Page/PageStorage.h b/dbms/src/Storages/Page/PageStorage.h index 78d800dc3af..cec6e297d0e 100644 --- a/dbms/src/Storages/Page/PageStorage.h +++ b/dbms/src/Storages/Page/PageStorage.h @@ -407,7 +407,9 @@ class PageWriter : private boost::noncopyable // Only used for DATA transform data void writeIntoV3(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const; +#ifndef DBMS_PUBLIC_GTEST private: +#endif void writeIntoMixMode(WriteBatch && write_batch, WriteLimiterPtr write_limiter) const; // A wrap of getSettings only used for `RegionPersister::gc` diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp index 98d84989dd9..f7ce3180172 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp @@ -44,17 +44,16 @@ class PageStorageMixedTest : public DB::base::TiFlashStorageTestBasic storage_path_pool_v3 = std::make_unique(Strings{path}, Strings{path}, Strings{}, std::make_shared(0, paths, caps, Strings{}, caps), global_context.getFileProvider(), true); global_context.setPageStorageRunMode(PageStorageRunMode::MIX_MODE); - if (!global_context.getGlobalStoragePool()) - global_context.initializeGlobalStoragePoolIfNeed(*storage_path_pool_v3); } void SetUp() override { + auto & global_context = DB::tests::TiFlashTestEnv::getGlobalContext(); + global_context.setPageStorageRunMode(PageStorageRunMode::MIX_MODE); TiFlashStorageTestBasic::SetUp(); const auto & path = getTemporaryPath(); createIfNotExist(path); - auto & global_context = DB::tests::TiFlashTestEnv::getGlobalContext(); std::vector caps = {}; Strings paths = {path}; @@ -76,7 +75,7 @@ class PageStorageMixedTest : public DB::base::TiFlashStorageTestBasic PageStorageRunMode reloadMixedStoragePool() { - DB::tests::TiFlashTestEnv::getContext().setPageStorageRunMode(PageStorageRunMode::MIX_MODE); + db_context->setPageStorageRunMode(PageStorageRunMode::MIX_MODE); PageStorageRunMode run_mode = storage_pool_mix->restore(); page_writer_mix = storage_pool_mix->logWriter(); page_reader_mix = storage_pool_mix->logReader(); @@ -85,7 +84,7 @@ class PageStorageMixedTest : public DB::base::TiFlashStorageTestBasic void reloadV2StoragePool() { - DB::tests::TiFlashTestEnv::getContext().setPageStorageRunMode(PageStorageRunMode::ONLY_V2); + db_context->setPageStorageRunMode(PageStorageRunMode::ONLY_V2); storage_pool_v2->restore(); page_writer_v2 = storage_pool_v2->logWriter(); page_reader_v2 = storage_pool_v2->logReader(); diff --git a/dbms/src/Storages/Transaction/tests/gtest_region_persister.cpp b/dbms/src/Storages/Transaction/tests/gtest_region_persister.cpp index 16a35f42da1..963e3a3571d 100644 --- a/dbms/src/Storages/Transaction/tests/gtest_region_persister.cpp +++ b/dbms/src/Storages/Transaction/tests/gtest_region_persister.cpp @@ -65,6 +65,11 @@ class RegionPersister_test : public ::testing::Test String dir_path; DB::Timestamp tso = 0; + + String getPageStorageV3MetaPath(String & path) + { + return path + "/page/kvstore/wal/log_1_0"; + } }; static ::testing::AssertionResult PeerCompare( @@ -251,7 +256,7 @@ try } // If we truncate page data file, exception will throw instead of droping last region. - auto meta_path = path + "/kvstore/page_1_0/meta"; // First page + auto meta_path = getPageStorageV3MetaPath(path); // First page Poco::File meta_file(meta_path); size_t size = meta_file.getSize(); int rt = ::truncate(meta_path.c_str(), size - 1); // Remove last one byte @@ -288,9 +293,13 @@ try { std::string path = dir_path + "/compatible_mode"; + auto current_storage_run_mode = TiFlashTestEnv::getGlobalContext().getPageStorageRunMode(); // Force to run in compatible mode for the default region persister FailPointHelper::enableFailPoint(FailPoints::force_enable_region_persister_compatible_mode); - SCOPE_EXIT({ FailPointHelper::disableFailPoint(FailPoints::force_enable_region_persister_compatible_mode); }); + SCOPE_EXIT( + { FailPointHelper::disableFailPoint(FailPoints::force_enable_region_persister_compatible_mode); + TiFlashTestEnv::getGlobalContext().setPageStorageRunMode(current_storage_run_mode); }); + TiFlashTestEnv::getGlobalContext().setPageStorageRunMode(PageStorageRunMode::ONLY_V2); auto ctx = TiFlashTestEnv::getContext(DB::Settings(), Strings{ path, diff --git a/dbms/src/TestUtils/TiFlashTestEnv.cpp b/dbms/src/TestUtils/TiFlashTestEnv.cpp index 34355d43775..264fd6009a3 100644 --- a/dbms/src/TestUtils/TiFlashTestEnv.cpp +++ b/dbms/src/TestUtils/TiFlashTestEnv.cpp @@ -94,6 +94,7 @@ Context TiFlashTestEnv::getContext(const DB::Settings & settings, Strings testda context.setPath(root_path); auto paths = getPathPool(testdata_path); context.setPathPool(paths.first, paths.second, Strings{}, true, context.getPathCapacity(), context.getFileProvider()); + global_context->initializeGlobalStoragePoolIfNeed(context.getPathPool()); context.getSettingsRef() = settings; return context; } diff --git a/dbms/src/TestUtils/TiFlashTestEnv.h b/dbms/src/TestUtils/TiFlashTestEnv.h index 65dad63d937..0264d87ef9f 100644 --- a/dbms/src/TestUtils/TiFlashTestEnv.h +++ b/dbms/src/TestUtils/TiFlashTestEnv.h @@ -88,7 +88,7 @@ class TiFlashTestEnv static Context getContext(const DB::Settings & settings = DB::Settings(), Strings testdata_path = {}); - static void initializeGlobalContext(Strings testdata_path = {}, bool enable_ps_v3 = false); + static void initializeGlobalContext(Strings testdata_path = {}, bool enable_ps_v3 = true); static Context & getGlobalContext() { return *global_context; } static void shutdown(); From 35c8ea12e9d2934aa8515279f73c1be37264aabb Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Wed, 25 May 2022 20:44:48 +0800 Subject: [PATCH 064/127] Interpreter: Print BlockInputStream (#4911) ref pingcap/tiflash#4609 --- .../DataStreams/FilterBlockInputStream.cpp | 2 +- .../HashJoinBuildBlockInputStream.cpp | 23 ++ .../HashJoinBuildBlockInputStream.h | 1 + dbms/src/DataStreams/IBlockInputStream.cpp | 8 +- dbms/src/DataStreams/IBlockInputStream.h | 6 + .../src/DataStreams/LimitBlockInputStream.cpp | 4 + dbms/src/DataStreams/LimitBlockInputStream.h | 1 + .../MergeSortingBlockInputStream.cpp | 4 + .../MergeSortingBlockInputStream.h | 1 + .../ParallelAggregatingBlockInputStream.cpp | 8 + .../ParallelAggregatingBlockInputStream.h | 4 +- .../PartialSortingBlockInputStream.cpp | 11 +- .../PartialSortingBlockInputStream.h | 1 + .../DataStreams/TiRemoteBlockInputStream.h | 28 +- dbms/src/Flash/Coprocessor/DAGContext.h | 2 +- .../Coprocessor/DAGQueryBlockInterpreter.cpp | 68 ++-- .../Coprocessor/DAGQueryBlockInterpreter.h | 6 +- .../Coprocessor/DAGStorageInterpreter.cpp | 6 +- dbms/src/Flash/Coprocessor/InterpreterDAG.cpp | 6 +- .../Flash/Coprocessor/InterpreterUtils.cpp | 11 +- dbms/src/Flash/Coprocessor/InterpreterUtils.h | 3 +- dbms/src/Flash/tests/gtest_interpreter.cpp | 324 +++++++++--------- dbms/src/Interpreters/ExpressionActions.cpp | 20 +- dbms/src/Interpreters/ExpressionActions.h | 2 +- dbms/src/Interpreters/executeQuery.cpp | 2 +- 25 files changed, 327 insertions(+), 225 deletions(-) diff --git a/dbms/src/DataStreams/FilterBlockInputStream.cpp b/dbms/src/DataStreams/FilterBlockInputStream.cpp index 3739b57d82d..a7ecf12d8cb 100644 --- a/dbms/src/DataStreams/FilterBlockInputStream.cpp +++ b/dbms/src/DataStreams/FilterBlockInputStream.cpp @@ -60,7 +60,7 @@ FilterBlockInputStream::FilterBlockInputStream( Block FilterBlockInputStream::getTotals() { - if (IProfilingBlockInputStream * child = dynamic_cast(&*children.back())) + if (auto * child = dynamic_cast(&*children.back())) { totals = child->getTotals(); expression->executeOnTotals(totals); diff --git a/dbms/src/DataStreams/HashJoinBuildBlockInputStream.cpp b/dbms/src/DataStreams/HashJoinBuildBlockInputStream.cpp index 91fd34bfff4..61808b48c50 100644 --- a/dbms/src/DataStreams/HashJoinBuildBlockInputStream.cpp +++ b/dbms/src/DataStreams/HashJoinBuildBlockInputStream.cpp @@ -13,6 +13,7 @@ // limitations under the License. +#include #include namespace DB { @@ -25,4 +26,26 @@ Block HashJoinBuildBlockInputStream::readImpl() return block; } +void HashJoinBuildBlockInputStream::appendInfo(FmtBuffer & buffer) const +{ + static const std::unordered_map join_type_map{ + {ASTTableJoin::Kind::Inner, "Inner"}, + {ASTTableJoin::Kind::Left, "Left"}, + {ASTTableJoin::Kind::Right, "Right"}, + {ASTTableJoin::Kind::Full, "Full"}, + {ASTTableJoin::Kind::Cross, "Cross"}, + {ASTTableJoin::Kind::Comma, "Comma"}, + {ASTTableJoin::Kind::Anti, "Anti"}, + {ASTTableJoin::Kind::LeftSemi, "Left_Semi"}, + {ASTTableJoin::Kind::LeftAnti, "Left_Anti"}, + {ASTTableJoin::Kind::Cross_Left, "Cross_Left"}, + {ASTTableJoin::Kind::Cross_Right, "Cross_Right"}, + {ASTTableJoin::Kind::Cross_Anti, "Cross_Anti"}, + {ASTTableJoin::Kind::Cross_LeftSemi, "Cross_LeftSemi"}, + {ASTTableJoin::Kind::Cross_LeftAnti, "Cross_LeftAnti"}}; + auto join_type_it = join_type_map.find(join->getKind()); + if (join_type_it == join_type_map.end()) + throw TiFlashException("Unknown join type", Errors::Coprocessor::Internal); + buffer.fmtAppend(", join_kind = {}", join_type_it->second); +} } // namespace DB diff --git a/dbms/src/DataStreams/HashJoinBuildBlockInputStream.h b/dbms/src/DataStreams/HashJoinBuildBlockInputStream.h index 57b505f5237..dbfc7f30310 100644 --- a/dbms/src/DataStreams/HashJoinBuildBlockInputStream.h +++ b/dbms/src/DataStreams/HashJoinBuildBlockInputStream.h @@ -41,6 +41,7 @@ class HashJoinBuildBlockInputStream : public IProfilingBlockInputStream protected: Block readImpl() override; + void appendInfo(FmtBuffer & buffer) const override; private: JoinPtr join; diff --git a/dbms/src/DataStreams/IBlockInputStream.cpp b/dbms/src/DataStreams/IBlockInputStream.cpp index 57dbe0e6ad0..a05fbf83c96 100644 --- a/dbms/src/DataStreams/IBlockInputStream.cpp +++ b/dbms/src/DataStreams/IBlockInputStream.cpp @@ -77,15 +77,17 @@ size_t IBlockInputStream::checkDepthImpl(size_t max_depth, size_t level) const return res + 1; } - void IBlockInputStream::dumpTree(FmtBuffer & buffer, size_t indent, size_t multiplier) { - // todo append getHeader().dumpStructure() buffer.fmtAppend( - "{}{}{}\n", + "{}{}{}", String(indent, ' '), getName(), multiplier > 1 ? fmt::format(" x {}", multiplier) : ""); + if (!extra_info.empty()) + buffer.fmtAppend(": <{}>", extra_info); + appendInfo(buffer); + buffer.append("\n"); ++indent; /// If the subtree is repeated several times, then we output it once with the multiplier. diff --git a/dbms/src/DataStreams/IBlockInputStream.h b/dbms/src/DataStreams/IBlockInputStream.h index 75fdffb3d29..472eac282d4 100644 --- a/dbms/src/DataStreams/IBlockInputStream.h +++ b/dbms/src/DataStreams/IBlockInputStream.h @@ -135,6 +135,7 @@ class IBlockInputStream : private boost::noncopyable */ void addTableLock(const TableLockHolder & lock) { table_locks.push_back(lock); } + void setExtraInfo(String info) { extra_info = info; } template void forEachChild(F && f) @@ -176,6 +177,8 @@ class IBlockInputStream : private boost::noncopyable } } + virtual void appendInfo(FmtBuffer & /*buffer*/) const {}; + protected: BlockInputStreams children; mutable std::shared_mutex children_mutex; @@ -188,6 +191,9 @@ class IBlockInputStream : private boost::noncopyable mutable std::mutex tree_id_mutex; mutable String tree_id; + /// The info that hints why the inputStream is needed to run. + String extra_info; + /// Get text with names of this source and the entire subtree, this function should only be called after the /// InputStream tree is constructed. String getTreeID() const; diff --git a/dbms/src/DataStreams/LimitBlockInputStream.cpp b/dbms/src/DataStreams/LimitBlockInputStream.cpp index 81c31fc5d77..4ec6157257c 100644 --- a/dbms/src/DataStreams/LimitBlockInputStream.cpp +++ b/dbms/src/DataStreams/LimitBlockInputStream.cpp @@ -83,4 +83,8 @@ Block LimitBlockInputStream::readImpl() return res; } +void LimitBlockInputStream::appendInfo(FmtBuffer & buffer) const +{ + buffer.fmtAppend(", limit = {}", limit); +} } // namespace DB diff --git a/dbms/src/DataStreams/LimitBlockInputStream.h b/dbms/src/DataStreams/LimitBlockInputStream.h index 21978773daf..e6a7013210b 100644 --- a/dbms/src/DataStreams/LimitBlockInputStream.h +++ b/dbms/src/DataStreams/LimitBlockInputStream.h @@ -43,6 +43,7 @@ class LimitBlockInputStream : public IProfilingBlockInputStream protected: Block readImpl() override; + void appendInfo(FmtBuffer & buffer) const override; private: size_t limit; diff --git a/dbms/src/DataStreams/MergeSortingBlockInputStream.cpp b/dbms/src/DataStreams/MergeSortingBlockInputStream.cpp index 0975ace963a..e79426f686e 100644 --- a/dbms/src/DataStreams/MergeSortingBlockInputStream.cpp +++ b/dbms/src/DataStreams/MergeSortingBlockInputStream.cpp @@ -287,5 +287,9 @@ Block MergeSortingBlocksBlockInputStream::mergeImpl(std::priority_queue +#include +#include +#include #include #include #include @@ -275,4 +278,9 @@ void ParallelAggregatingBlockInputStream::execute() no_more_keys); } +void ParallelAggregatingBlockInputStream::appendInfo(FmtBuffer & buffer) const +{ + buffer.fmtAppend(", max_threads: {}, final: {}", max_threads, final ? "true" : "false"); +} + } // namespace DB diff --git a/dbms/src/DataStreams/ParallelAggregatingBlockInputStream.h b/dbms/src/DataStreams/ParallelAggregatingBlockInputStream.h index 398c3d35bbc..41e61786370 100644 --- a/dbms/src/DataStreams/ParallelAggregatingBlockInputStream.h +++ b/dbms/src/DataStreams/ParallelAggregatingBlockInputStream.h @@ -50,7 +50,7 @@ class ParallelAggregatingBlockInputStream : public IProfilingBlockInputStream Block getHeader() const override; - virtual void collectNewThreadCountOfThisLevel(int & cnt) override + void collectNewThreadCountOfThisLevel(int & cnt) override { cnt += processor.getMaxThreads(); } @@ -62,6 +62,8 @@ class ParallelAggregatingBlockInputStream : public IProfilingBlockInputStream } Block readImpl() override; + void appendInfo(FmtBuffer & buffer) const override; + private: const LoggerPtr log; diff --git a/dbms/src/DataStreams/PartialSortingBlockInputStream.cpp b/dbms/src/DataStreams/PartialSortingBlockInputStream.cpp index 30f520fdec3..4069f3818a8 100644 --- a/dbms/src/DataStreams/PartialSortingBlockInputStream.cpp +++ b/dbms/src/DataStreams/PartialSortingBlockInputStream.cpp @@ -12,15 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include - +#include #include +#include namespace DB { - - Block PartialSortingBlockInputStream::readImpl() { Block res = children.back()->read(); @@ -28,5 +26,8 @@ Block PartialSortingBlockInputStream::readImpl() return res; } - +void PartialSortingBlockInputStream::appendInfo(FmtBuffer & buffer) const +{ + buffer.fmtAppend(": limit = {}", limit); } +} // namespace DB diff --git a/dbms/src/DataStreams/PartialSortingBlockInputStream.h b/dbms/src/DataStreams/PartialSortingBlockInputStream.h index 4a7a62474df..1b2f554ef94 100644 --- a/dbms/src/DataStreams/PartialSortingBlockInputStream.h +++ b/dbms/src/DataStreams/PartialSortingBlockInputStream.h @@ -50,6 +50,7 @@ class PartialSortingBlockInputStream : public IProfilingBlockInputStream protected: Block readImpl() override; + void appendInfo(FmtBuffer & buffer) const override; private: SortDescription description; diff --git a/dbms/src/DataStreams/TiRemoteBlockInputStream.h b/dbms/src/DataStreams/TiRemoteBlockInputStream.h index c1e29617586..f8e313a25be 100644 --- a/dbms/src/DataStreams/TiRemoteBlockInputStream.h +++ b/dbms/src/DataStreams/TiRemoteBlockInputStream.h @@ -14,6 +14,7 @@ #pragma once +#include #include #include #include @@ -60,11 +61,11 @@ class TiRemoteBlockInputStream : public IProfilingBlockInputStream void initRemoteExecutionSummaries(tipb::SelectResponse & resp, size_t index) { - for (auto & execution_summary : resp.execution_summaries()) + for (const auto & execution_summary : resp.execution_summaries()) { if (execution_summary.has_executor_id()) { - auto & executor_id = execution_summary.executor_id(); + const auto & executor_id = execution_summary.executor_id(); execution_summaries[index][executor_id].time_processed_ns = execution_summary.time_processed_ns(); execution_summaries[index][executor_id].num_produced_rows = execution_summary.num_produced_rows(); execution_summaries[index][executor_id].num_iterations = execution_summary.num_iterations(); @@ -84,11 +85,11 @@ class TiRemoteBlockInputStream : public IProfilingBlockInputStream return; } auto & execution_summaries_map = execution_summaries[index]; - for (auto & execution_summary : resp.execution_summaries()) + for (const auto & execution_summary : resp.execution_summaries()) { if (execution_summary.has_executor_id()) { - auto & executor_id = execution_summary.executor_id(); + const auto & executor_id = execution_summary.executor_id(); if (unlikely(execution_summaries_map.find(executor_id) == execution_summaries_map.end())) { LOG_FMT_WARNING(log, "execution {} not found in execution_summaries, this should not happen", executor_id); @@ -224,12 +225,12 @@ class TiRemoteBlockInputStream : public IProfilingBlockInputStream bool isStreamingCall() const { return is_streaming_reader; } const std::vector & getConnectionProfileInfos() const { return connection_profile_infos; } - virtual void collectNewThreadCountOfThisLevel(int & cnt) override + void collectNewThreadCountOfThisLevel(int & cnt) override { remote_reader->collectNewThreadCount(cnt); } - virtual void resetNewThreadCountCompute() override + void resetNewThreadCountCompute() override { if (collected) { @@ -239,11 +240,24 @@ class TiRemoteBlockInputStream : public IProfilingBlockInputStream } protected: - virtual void readSuffixImpl() override + void readSuffixImpl() override { LOG_FMT_DEBUG(log, "finish read {} rows from remote", total_rows); remote_reader->close(); } + + void appendInfo(FmtBuffer & buffer) const override + { + buffer.append(": schema: {"); + buffer.joinStr( + sample_block.begin(), + sample_block.end(), + [](const auto & arg, FmtBuffer & fb) { + fb.fmtAppend("<{}, {}>", arg.name, arg.type->getName()); + }, + ", "); + buffer.append("}"); + } }; using ExchangeReceiverInputStream = TiRemoteBlockInputStream; diff --git a/dbms/src/Flash/Coprocessor/DAGContext.h b/dbms/src/Flash/Coprocessor/DAGContext.h index d031ff103ff..e3e5efdcbc6 100644 --- a/dbms/src/Flash/Coprocessor/DAGContext.h +++ b/dbms/src/Flash/Coprocessor/DAGContext.h @@ -174,7 +174,7 @@ class DAGContext explicit DAGContext(const tipb::DAGRequest & dag_request_, String log_identifier, size_t concurrency) : dag_request(&dag_request_) , initialize_concurrency(concurrency) - , is_mpp_task(false) + , is_mpp_task(true) , is_root_mpp_task(false) , tunnel_set(nullptr) , log(Logger::get(log_identifier)) diff --git a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp index 4d8faffde6c..5fac49faaed 100644 --- a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp +++ b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp @@ -258,20 +258,22 @@ void DAGQueryBlockInterpreter::handleJoin(const tipb::Join & join, DAGPipeline & size_t join_build_concurrency = settings.join_concurrent_build ? std::min(max_streams, build_pipeline.streams.size()) : 1; /// build side streams - executeExpression(build_pipeline, build_side_prepare_actions); + executeExpression(build_pipeline, build_side_prepare_actions, "append join key and join filters for build side"); // add a HashJoinBuildBlockInputStream to build a shared hash table auto get_concurrency_build_index = JoinInterpreterHelper::concurrencyBuildIndexGenerator(join_build_concurrency); build_pipeline.transform([&](auto & stream) { stream = std::make_shared(stream, join_ptr, get_concurrency_build_index(), log->identifier()); + stream->setExtraInfo( + fmt::format("join build, build_side_root_executor_id = {}", dagContext().getJoinExecuteInfoMap()[query_block.source_name].build_side_root_executor_id)); }); - executeUnion(build_pipeline, max_streams, log, /*ignore_block=*/true); + executeUnion(build_pipeline, max_streams, log, /*ignore_block=*/true, "for join"); right_query.source = build_pipeline.firstStream(); right_query.join = join_ptr; join_ptr->init(right_query.source->getHeader(), join_build_concurrency); /// probe side streams - executeExpression(probe_pipeline, probe_side_prepare_actions); + executeExpression(probe_pipeline, probe_side_prepare_actions, "append join key and join filters for probe side"); NamesAndTypes source_columns; for (const auto & p : probe_pipeline.firstStream()->getHeader()) source_columns.emplace_back(p.name, p.type); @@ -291,12 +293,16 @@ void DAGQueryBlockInterpreter::handleJoin(const tipb::Join & join, DAGPipeline & i, not_joined_concurrency, settings.max_block_size); + non_joined_stream->setExtraInfo("add stream with non_joined_data if full_or_right_join"); pipeline.streams_with_non_joined_data.push_back(non_joined_stream); join_execute_info.non_joined_streams.push_back(non_joined_stream); } } for (auto & stream : pipeline.streams) + { stream = std::make_shared(stream, chain.getLastActions(), log->identifier()); + stream->setExtraInfo(fmt::format("join probe, join_executor_id = {}", query_block.source_name)); + } /// add a project to remove all the useless column NamesWithAliases project_cols; @@ -306,7 +312,7 @@ void DAGQueryBlockInterpreter::handleJoin(const tipb::Join & join, DAGPipeline & /// it is guaranteed by its children query block project_cols.emplace_back(c.name, c.name); } - executeProject(pipeline, project_cols); + executeProject(pipeline, project_cols, "remove useless column after join"); analyzer = std::make_unique(std::move(join_output_columns), context); } @@ -320,19 +326,22 @@ void DAGQueryBlockInterpreter::recordJoinExecuteInfo(size_t build_side_index, co dagContext().getJoinExecuteInfoMap()[query_block.source_name] = std::move(join_execute_info); } -void DAGQueryBlockInterpreter::executeWhere(DAGPipeline & pipeline, const ExpressionActionsPtr & expr, String & filter_column) +void DAGQueryBlockInterpreter::executeWhere(DAGPipeline & pipeline, const ExpressionActionsPtr & expr, String & filter_column, const String & extra_info) { - pipeline.transform([&](auto & stream) { stream = std::make_shared(stream, expr, filter_column, log->identifier()); }); + pipeline.transform([&](auto & stream) { + stream = std::make_shared(stream, expr, filter_column, log->identifier()); + stream->setExtraInfo(extra_info); + }); } void DAGQueryBlockInterpreter::executeWindow( DAGPipeline & pipeline, WindowDescription & window_description) { - executeExpression(pipeline, window_description.before_window); + executeExpression(pipeline, window_description.before_window, "before window"); /// If there are several streams, we merge them into one - executeUnion(pipeline, max_streams, log); + executeUnion(pipeline, max_streams, log, false, "merge into one for window input"); assert(pipeline.streams.size() == 1); pipeline.firstStream() = std::make_shared(pipeline.firstStream(), window_description, log->identifier()); } @@ -345,7 +354,10 @@ void DAGQueryBlockInterpreter::executeAggregation( AggregateDescriptions & aggregate_descriptions, bool is_final_agg) { - pipeline.transform([&](auto & stream) { stream = std::make_shared(stream, expression_actions_ptr, log->identifier()); }); + pipeline.transform([&](auto & stream) { + stream = std::make_shared(stream, expression_actions_ptr, log->identifier()); + stream->setExtraInfo("before aggregation"); + }); Block before_agg_header = pipeline.firstStream()->getHeader(); @@ -398,11 +410,14 @@ void DAGQueryBlockInterpreter::executeAggregation( } } -void DAGQueryBlockInterpreter::executeExpression(DAGPipeline & pipeline, const ExpressionActionsPtr & expressionActionsPtr) +void DAGQueryBlockInterpreter::executeExpression(DAGPipeline & pipeline, const ExpressionActionsPtr & expressionActionsPtr, const String & extra_info) { if (!expressionActionsPtr->getActions().empty()) { - pipeline.transform([&](auto & stream) { stream = std::make_shared(stream, expressionActionsPtr, log->identifier()); }); + pipeline.transform([&](auto & stream) { + stream = std::make_shared(stream, expressionActionsPtr, log->identifier()); + stream->setExtraInfo(extra_info); + }); } } @@ -434,7 +449,7 @@ void DAGQueryBlockInterpreter::orderStreams(DAGPipeline & pipeline, SortDescript }); /// If there are several streams, we merge them into one - executeUnion(pipeline, max_streams, log); + executeUnion(pipeline, max_streams, log, false, "for partial order"); /// Merge the sorted blocks. pipeline.firstStream() = std::make_shared( @@ -465,6 +480,7 @@ void DAGQueryBlockInterpreter::handleExchangeReceiver(DAGPipeline & pipeline) BlockInputStreamPtr stream = std::make_shared(it->second, log->identifier(), query_block.source_name); exchange_receiver_io_input_streams.push_back(stream); stream = std::make_shared(stream, 8192, 0, log->identifier()); + stream->setExtraInfo("squashing after exchange receiver"); pipeline.streams.push_back(stream); } NamesAndTypes source_columns; @@ -511,8 +527,11 @@ void DAGQueryBlockInterpreter::handleProjection(DAGPipeline & pipeline, const ti output_columns.emplace_back(alias, col.type); project_cols.emplace_back(col.name, alias); } - pipeline.transform([&](auto & stream) { stream = std::make_shared(stream, chain.getLastActions(), log->identifier()); }); - executeProject(pipeline, project_cols); + pipeline.transform([&](auto & stream) { + stream = std::make_shared(stream, chain.getLastActions(), log->identifier()); + stream->setExtraInfo("before projection"); + }); + executeProject(pipeline, project_cols, "projection"); analyzer = std::make_unique(std::move(output_columns), context); } @@ -526,7 +545,7 @@ void DAGQueryBlockInterpreter::handleWindow(DAGPipeline & pipeline, const tipb:: DAGExpressionAnalyzer dag_analyzer(input_columns, context); WindowDescription window_description = dag_analyzer.buildWindowDescription(window); executeWindow(pipeline, window_description); - executeExpression(pipeline, window_description.after_window); + executeExpression(pipeline, window_description.after_window, "cast after window"); analyzer = std::make_unique(window_description.after_window_columns, context); } @@ -613,7 +632,7 @@ void DAGQueryBlockInterpreter::executeImpl(DAGPipeline & pipeline) if (res.before_where) { // execute where - executeWhere(pipeline, res.before_where, res.filter_column_name); + executeWhere(pipeline, res.before_where, res.filter_column_name, "execute where"); recordProfileStreams(pipeline, query_block.selection_name); } @@ -633,12 +652,12 @@ void DAGQueryBlockInterpreter::executeImpl(DAGPipeline & pipeline) if (res.before_having) { // execute having - executeWhere(pipeline, res.before_having, res.having_column_name); + executeWhere(pipeline, res.before_having, res.having_column_name, "execute having"); recordProfileStreams(pipeline, query_block.having_name); } if (res.before_order_and_select) { - executeExpression(pipeline, res.before_order_and_select); + executeExpression(pipeline, res.before_order_and_select, "before order and select"); } if (!res.order_columns.empty()) @@ -649,7 +668,7 @@ void DAGQueryBlockInterpreter::executeImpl(DAGPipeline & pipeline) } // execute final project action - executeProject(pipeline, final_project); + executeProject(pipeline, final_project, "final projection"); // execute limit if (query_block.limit_or_topn && query_block.limit_or_topn->tp() == tipb::TypeLimit) { @@ -669,12 +688,15 @@ void DAGQueryBlockInterpreter::executeImpl(DAGPipeline & pipeline) } } -void DAGQueryBlockInterpreter::executeProject(DAGPipeline & pipeline, NamesWithAliases & project_cols) +void DAGQueryBlockInterpreter::executeProject(DAGPipeline & pipeline, NamesWithAliases & project_cols, const String & extra_info) { if (project_cols.empty()) return; ExpressionActionsPtr project = generateProjectExpressionActions(pipeline.firstStream(), context, project_cols); - pipeline.transform([&](auto & stream) { stream = std::make_shared(stream, project, log->identifier()); }); + pipeline.transform([&](auto & stream) { + stream = std::make_shared(stream, project, log->identifier()); + stream->setExtraInfo(extra_info); + }); } void DAGQueryBlockInterpreter::executeLimit(DAGPipeline & pipeline) @@ -687,7 +709,7 @@ void DAGQueryBlockInterpreter::executeLimit(DAGPipeline & pipeline) pipeline.transform([&](auto & stream) { stream = std::make_shared(stream, limit, 0, log->identifier(), false); }); if (pipeline.hasMoreThanOneStream()) { - executeUnion(pipeline, max_streams, log); + executeUnion(pipeline, max_streams, log, false, "for partial limit"); pipeline.transform([&](auto & stream) { stream = std::make_shared(stream, limit, 0, log->identifier(), false); }); } } @@ -734,7 +756,7 @@ BlockInputStreams DAGQueryBlockInterpreter::execute() executeImpl(pipeline); if (!pipeline.streams_with_non_joined_data.empty()) { - executeUnion(pipeline, max_streams, log); + executeUnion(pipeline, max_streams, log, false, "final union for non_joined_data"); restorePipelineConcurrency(pipeline); } diff --git a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h index 9b95a5c3e93..e68c4f91cee 100644 --- a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h +++ b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h @@ -66,8 +66,8 @@ class DAGQueryBlockInterpreter void handleProjection(DAGPipeline & pipeline, const tipb::Projection & projection); void handleWindow(DAGPipeline & pipeline, const tipb::Window & window); void handleWindowOrder(DAGPipeline & pipeline, const tipb::Sort & window_sort); - void executeWhere(DAGPipeline & pipeline, const ExpressionActionsPtr & expressionActionsPtr, String & filter_column); - void executeExpression(DAGPipeline & pipeline, const ExpressionActionsPtr & expressionActionsPtr); + void executeWhere(DAGPipeline & pipeline, const ExpressionActionsPtr & expressionActionsPtr, String & filter_column, const String & extra_info = ""); + void executeExpression(DAGPipeline & pipeline, const ExpressionActionsPtr & expressionActionsPtr, const String & extra_info = ""); void executeWindowOrder(DAGPipeline & pipeline, SortDescription sort_desc); void orderStreams(DAGPipeline & pipeline, SortDescription order_descr, Int64 limit); void executeOrder(DAGPipeline & pipeline, const NamesAndTypes & order_columns); @@ -82,7 +82,7 @@ class DAGQueryBlockInterpreter const TiDB::TiDBCollators & collators, AggregateDescriptions & aggregate_descriptions, bool is_final_agg); - void executeProject(DAGPipeline & pipeline, NamesWithAliases & project_cols); + void executeProject(DAGPipeline & pipeline, NamesWithAliases & project_cols, const String & extra_info = ""); void handleExchangeSender(DAGPipeline & pipeline); void handleMockExchangeSender(DAGPipeline & pipeline); diff --git a/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp b/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp index f5354994b44..d91b18254f6 100644 --- a/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp +++ b/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp @@ -186,7 +186,7 @@ void setQuotaAndLimitsOnTableScan(Context & context, DAGPipeline & pipeline) QuotaForIntervals & quota = context.getQuota(); pipeline.transform([&](auto & stream) { - if (IProfilingBlockInputStream * p_stream = dynamic_cast(stream.get())) + if (auto * p_stream = dynamic_cast(stream.get())) { p_stream->setLimits(limits); p_stream->setQuota(quota); @@ -373,8 +373,10 @@ void DAGStorageInterpreter::executePushedDownFilter( { auto & stream = pipeline.streams[i]; stream = std::make_shared(stream, before_where, filter_column_name, log->identifier()); + stream->setExtraInfo("push down filter"); // after filter, do project action to keep the schema of local streams and remote streams the same. stream = std::make_shared(stream, project_after_where, log->identifier()); + stream->setExtraInfo("projection after push down filter"); } } @@ -412,6 +414,7 @@ void DAGStorageInterpreter::executeCastAfterTableScan( { auto & stream = pipeline.streams[i++]; stream = std::make_shared(stream, extra_cast, log->identifier()); + stream->setExtraInfo("cast after local tableScan"); } // remote streams if (i < pipeline.streams.size()) @@ -424,6 +427,7 @@ void DAGStorageInterpreter::executeCastAfterTableScan( { auto & stream = pipeline.streams[i++]; stream = std::make_shared(stream, project_for_cop_read, log->identifier()); + stream->setExtraInfo("cast after remote tableScan"); } } } diff --git a/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp b/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp index b7c75c06e67..741aa7b5e26 100644 --- a/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp +++ b/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp @@ -26,7 +26,7 @@ InterpreterDAG::InterpreterDAG(Context & context_, const DAGQuerySource & dag_) , dag(dag_) { const Settings & settings = context.getSettingsRef(); - if (dagContext().isBatchCop() || dagContext().isMPPTask()) + if (dagContext().isBatchCop() || (dagContext().isMPPTask() && !dagContext().isTest())) max_streams = settings.max_threads; else if (dagContext().isTest()) max_streams = dagContext().initialize_concurrency; @@ -85,9 +85,9 @@ BlockIO InterpreterDAG::execute() /// add union to run in parallel if needed if (dagContext().isMPPTask()) /// MPPTask do not need the returned blocks. - executeUnion(pipeline, max_streams, dagContext().log, /*ignore_block=*/true); + executeUnion(pipeline, max_streams, dagContext().log, /*ignore_block=*/true, "for mpp"); else - executeUnion(pipeline, max_streams, dagContext().log); + executeUnion(pipeline, max_streams, dagContext().log, false, "for non mpp"); if (dagContext().hasSubquery()) { const Settings & settings = context.getSettingsRef(); diff --git a/dbms/src/Flash/Coprocessor/InterpreterUtils.cpp b/dbms/src/Flash/Coprocessor/InterpreterUtils.cpp index 69060071997..c9810454218 100644 --- a/dbms/src/Flash/Coprocessor/InterpreterUtils.cpp +++ b/dbms/src/Flash/Coprocessor/InterpreterUtils.cpp @@ -34,6 +34,7 @@ void restoreConcurrency( { BlockInputStreamPtr shared_query_block_input_stream = std::make_shared(concurrency * 5, pipeline.firstStream(), log->identifier()); + shared_query_block_input_stream->setExtraInfo("restore concurrency"); pipeline.streams.assign(concurrency, shared_query_block_input_stream); } } @@ -50,9 +51,15 @@ BlockInputStreamPtr combinedNonJoinedDataStream( else if (pipeline.streams_with_non_joined_data.size() > 1) { if (ignore_block) + { ret = std::make_shared(pipeline.streams_with_non_joined_data, nullptr, max_threads, log->identifier()); + ret->setExtraInfo("combine non joined(ignore block)"); + } else + { ret = std::make_shared(pipeline.streams_with_non_joined_data, nullptr, max_threads, log->identifier()); + ret->setExtraInfo("combine non joined"); + } } pipeline.streams_with_non_joined_data.clear(); return ret; @@ -62,7 +69,8 @@ void executeUnion( DAGPipeline & pipeline, size_t max_streams, const LoggerPtr & log, - bool ignore_block) + bool ignore_block, + const String & extra_info) { if (pipeline.streams.size() == 1 && pipeline.streams_with_non_joined_data.empty()) return; @@ -73,6 +81,7 @@ void executeUnion( pipeline.firstStream() = std::make_shared(pipeline.streams, non_joined_data_stream, max_streams, log->identifier()); else pipeline.firstStream() = std::make_shared(pipeline.streams, non_joined_data_stream, max_streams, log->identifier()); + pipeline.firstStream()->setExtraInfo(extra_info); pipeline.streams.resize(1); } else if (non_joined_data_stream != nullptr) diff --git a/dbms/src/Flash/Coprocessor/InterpreterUtils.h b/dbms/src/Flash/Coprocessor/InterpreterUtils.h index 91e6d483220..5c4d4721d5e 100644 --- a/dbms/src/Flash/Coprocessor/InterpreterUtils.h +++ b/dbms/src/Flash/Coprocessor/InterpreterUtils.h @@ -37,7 +37,8 @@ void executeUnion( DAGPipeline & pipeline, size_t max_streams, const LoggerPtr & log, - bool ignore_block = false); + bool ignore_block = false, + const String & extra_info = ""); ExpressionActionsPtr generateProjectExpressionActions( const BlockInputStreamPtr & stream, diff --git a/dbms/src/Flash/tests/gtest_interpreter.cpp b/dbms/src/Flash/tests/gtest_interpreter.cpp index 0d07159b8fc..aed9d9e90f9 100644 --- a/dbms/src/Flash/tests/gtest_interpreter.cpp +++ b/dbms/src/Flash/tests/gtest_interpreter.cpp @@ -47,18 +47,18 @@ try .build(context); { String expected = R"( -Union - SharedQuery x 10 - Expression - MergeSorting - Union - PartialSorting x 10 - Expression - Filter - SharedQuery - ParallelAggregating - Expression x 10 - Filter +Union: + SharedQuery x 10: + Expression: + MergeSorting, limit = 10 + Union: + PartialSorting x 10: limit = 10 + Expression: + Filter: + SharedQuery: + ParallelAggregating, max_threads: 10, final: true + Expression x 10: + Filter: MockTableScan)"; ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); } @@ -72,18 +72,18 @@ Union { String expected = R"( -Union - SharedQuery x 10 - Limit - Union - Limit x 10 - Expression - Expression - Filter - SharedQuery - ParallelAggregating - Expression x 10 - Filter +Union: + SharedQuery x 10: + Limit, limit = 10 + Union: + Limit x 10, limit = 10 + Expression: + Expression: + Filter: + SharedQuery: + ParallelAggregating, max_threads: 10, final: true + Expression x 10: + Filter: MockTableScan)"; ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); } @@ -100,17 +100,17 @@ try .build(context); { String expected = R"( -Union - Expression x 10 - Expression - Expression - Expression - Expression - Expression - Expression - Expression - Expression - Expression +Union: + Expression x 10: + Expression: + Expression: + Expression: + Expression: + Expression: + Expression: + Expression: + Expression: + Expression: MockTableScan)"; ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); } @@ -122,18 +122,18 @@ Union .build(context); { String expected = R"( -Union - Expression x 10 - Expression - Expression - SharedQuery - Expression - MergeSorting - Union - PartialSorting x 10 - Expression - Expression - Expression +Union: + Expression x 10: + Expression: + Expression: + SharedQuery: + Expression: + MergeSorting, limit = 10 + Union: + PartialSorting x 10: limit = 10 + Expression: + Expression: + Expression: MockTableScan)"; ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); } @@ -147,24 +147,24 @@ Union .build(context); { String expected = R"( -Union - Expression x 10 - Expression - Expression - Expression - SharedQuery - ParallelAggregating - Expression x 10 - Expression - Expression - SharedQuery - Expression - MergeSorting - Union - PartialSorting x 10 - Expression - Expression - Expression +Union: + Expression x 10: + Expression: + Expression: + Expression: + SharedQuery: + ParallelAggregating, max_threads: 10, final: true + Expression x 10: + Expression: + Expression: + SharedQuery: + Expression: + MergeSorting, limit = 10 + Union: + PartialSorting x 10: limit = 10 + Expression: + Expression: + Expression: MockTableScan)"; ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); } @@ -181,33 +181,33 @@ Union .build(context); { String expected = R"( -Union - SharedQuery x 10 - Limit - Union - Limit x 10 - Expression - Expression - Expression - Expression - Expression - Filter - Expression - Expression - Expression - SharedQuery - ParallelAggregating - Expression x 10 - Expression - Expression - SharedQuery - Expression - MergeSorting - Union - PartialSorting x 10 - Expression - Expression - Expression +Union: + SharedQuery x 10: + Limit, limit = 10 + Union: + Limit x 10, limit = 10 + Expression: + Expression: + Expression: + Expression: + Expression: + Filter: + Expression: + Expression: + Expression: + SharedQuery: + ParallelAggregating, max_threads: 10, final: true + Expression x 10: + Expression: + Expression: + SharedQuery: + Expression: + MergeSorting, limit = 10 + Union: + PartialSorting x 10: limit = 10 + Expression: + Expression: + Expression: MockTableScan)"; ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); } @@ -231,24 +231,24 @@ Union { String expected = R"( CreatingSets - Union - HashJoinBuildBlockInputStream x 10 - Expression - Expression + Union: + HashJoinBuildBlockInputStream x 10: , join_kind = Left + Expression: + Expression: MockTableScan - Union x 2 - HashJoinBuildBlockInputStream x 10 - Expression - Expression - Expression - HashJoinProbe - Expression + Union x 2: + HashJoinBuildBlockInputStream x 10: , join_kind = Left + Expression: + Expression: + Expression: + HashJoinProbe: + Expression: MockTableScan - Union - Expression x 10 - Expression - HashJoinProbe - Expression + Union: + Expression x 10: + Expression: + HashJoinProbe: + Expression: MockTableScan)"; ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); } @@ -260,17 +260,17 @@ CreatingSets .build(context); { String expected = R"( -Union - Expression x 10 - Expression - Expression - Expression - Expression - Expression - Expression - Expression - Expression - Expression +Union: + Expression x 10: + Expression: + Expression: + Expression: + Expression: + Expression: + Expression: + Expression: + Expression: + Expression: MockExchangeReceiver)"; ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); } @@ -283,18 +283,18 @@ Union .build(context); { String expected = R"( -Union +Union: MockExchangeSender x 10 - Expression - Expression - Expression - Expression - Expression - Expression - Expression - Expression - Expression - Expression + Expression: + Expression: + Expression: + Expression: + Expression: + Expression: + Expression: + Expression: + Expression: + Expression: MockExchangeReceiver)"; ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); } @@ -318,24 +318,24 @@ Union { String expected = R"( CreatingSets - Union - HashJoinBuildBlockInputStream x 10 - Expression - Expression + Union: + HashJoinBuildBlockInputStream x 10: , join_kind = Left + Expression: + Expression: MockExchangeReceiver - Union x 2 - HashJoinBuildBlockInputStream x 10 - Expression - Expression - Expression - HashJoinProbe - Expression + Union x 2: + HashJoinBuildBlockInputStream x 10: , join_kind = Left + Expression: + Expression: + Expression: + HashJoinProbe: + Expression: MockExchangeReceiver - Union - Expression x 10 - Expression - HashJoinProbe - Expression + Union: + Expression x 10: + Expression: + HashJoinProbe: + Expression: MockExchangeReceiver)"; ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); } @@ -360,25 +360,25 @@ CreatingSets { String expected = R"( CreatingSets - Union - HashJoinBuildBlockInputStream x 10 - Expression - Expression + Union: + HashJoinBuildBlockInputStream x 10: , join_kind = Left + Expression: + Expression: MockExchangeReceiver - Union x 2 - HashJoinBuildBlockInputStream x 10 - Expression - Expression - Expression - HashJoinProbe - Expression + Union x 2: + HashJoinBuildBlockInputStream x 10: , join_kind = Left + Expression: + Expression: + Expression: + HashJoinProbe: + Expression: MockExchangeReceiver - Union + Union: MockExchangeSender x 10 - Expression - Expression - HashJoinProbe - Expression + Expression: + Expression: + HashJoinProbe: + Expression: MockExchangeReceiver)"; ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); } diff --git a/dbms/src/Interpreters/ExpressionActions.cpp b/dbms/src/Interpreters/ExpressionActions.cpp index 45d5293d584..8e75a64427c 100644 --- a/dbms/src/Interpreters/ExpressionActions.cpp +++ b/dbms/src/Interpreters/ExpressionActions.cpp @@ -225,7 +225,7 @@ void ExpressionAction::prepare(Block & sample_block) for (const auto & name : array_joined_columns) { ColumnWithTypeAndName & current = sample_block.getByName(name); - const DataTypeArray * array_type = typeid_cast(&*current.type); + const auto * array_type = typeid_cast(&*current.type); if (!array_type) throw Exception("ARRAY JOIN requires array argument", ErrorCodes::TYPE_MISMATCH); current.type = array_type->getNestedType(); @@ -354,7 +354,7 @@ void ExpressionAction::execute(Block & block) const if (ColumnPtr converted = any_array_ptr->convertToFullColumnIfConst()) any_array_ptr = converted; - const ColumnArray * any_array = typeid_cast(&*any_array_ptr); + const auto * any_array = typeid_cast(&*any_array_ptr); if (!any_array) throw Exception("ARRAY JOIN of not array: " + *array_joined_columns.begin(), ErrorCodes::TYPE_MISMATCH); @@ -461,8 +461,7 @@ void ExpressionAction::executeOnTotals(Block & block) const join->joinTotals(block); } - -std::string ExpressionAction::toString() const +String ExpressionAction::toString() const { std::stringstream ss; switch (type) @@ -496,7 +495,7 @@ std::string ExpressionAction::toString() const case ARRAY_JOIN: ss << (array_join_is_left ? "LEFT " : "") << "ARRAY JOIN "; - for (NameSet::const_iterator it = array_joined_columns.begin(); it != array_joined_columns.end(); ++it) + for (auto it = array_joined_columns.begin(); it != array_joined_columns.end(); ++it) { if (it != array_joined_columns.begin()) ss << ", "; @@ -506,7 +505,7 @@ std::string ExpressionAction::toString() const case JOIN: ss << "JOIN "; - for (NamesAndTypesList::const_iterator it = columns_added_by_join.begin(); it != columns_added_by_join.end(); ++it) + for (auto it = columns_added_by_join.begin(); it != columns_added_by_join.end(); ++it) { if (it != columns_added_by_join.begin()) ss << ", "; @@ -529,7 +528,6 @@ std::string ExpressionAction::toString() const default: throw Exception("Unexpected Action type", ErrorCodes::LOGICAL_ERROR); } - return ss.str(); } @@ -842,9 +840,9 @@ void ExpressionActions::finalize(const Names & output_columns) if (final_columns.empty() && !input_columns.empty()) final_columns.insert(getSmallestColumn(input_columns)); - for (NamesAndTypesList::iterator it = input_columns.begin(); it != input_columns.end();) + for (auto it = input_columns.begin(); it != input_columns.end();) { - NamesAndTypesList::iterator it0 = it; + auto it0 = it; ++it; if (!needed_columns.count(it0->name)) { @@ -931,8 +929,8 @@ std::string ExpressionActions::dumpActions() const ss << "\noutput:\n"; NamesAndTypesList output_columns = sample_block.getNamesAndTypesList(); - for (NamesAndTypesList::const_iterator it = output_columns.begin(); it != output_columns.end(); ++it) - ss << it->name << " " << it->type->getName() << "\n"; + for (const auto & output_column : output_columns) + ss << output_column.name << " " << output_column.type->getName() << "\n"; return ss.str(); } diff --git a/dbms/src/Interpreters/ExpressionActions.h b/dbms/src/Interpreters/ExpressionActions.h index 7ec54a1a8ae..e8fb48f4e3f 100644 --- a/dbms/src/Interpreters/ExpressionActions.h +++ b/dbms/src/Interpreters/ExpressionActions.h @@ -251,7 +251,7 @@ struct ExpressionActionsChain ExpressionActionsPtr actions; Names required_output; - Step(const ExpressionActionsPtr & actions_ = nullptr, const Names & required_output_ = Names()) + explicit Step(const ExpressionActionsPtr & actions_ = nullptr, const Names & required_output_ = Names()) : actions(actions_) , required_output(required_output_) {} diff --git a/dbms/src/Interpreters/executeQuery.cpp b/dbms/src/Interpreters/executeQuery.cpp index 6c96e7c22ad..96cfc0a58ae 100644 --- a/dbms/src/Interpreters/executeQuery.cpp +++ b/dbms/src/Interpreters/executeQuery.cpp @@ -480,7 +480,7 @@ void executeQuery( if (streams.in) { - const ASTQueryWithOutput * ast_query_with_output = dynamic_cast(ast.get()); + const auto * ast_query_with_output = dynamic_cast(ast.get()); WriteBuffer * out_buf = &ostr; std::optional out_file_buf; From 03862ea0623a0590e2f0228ce9f043e9b59b8600 Mon Sep 17 00:00:00 2001 From: xufei Date: Thu, 26 May 2022 09:22:46 +0800 Subject: [PATCH 065/127] update client-c to add retry when init PD leader during the start up of TiFlash server (#4997) close pingcap/tiflash#4983 --- contrib/client-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/client-c b/contrib/client-c index 2c6c5fed8d7..36e05cb0f24 160000 --- a/contrib/client-c +++ b/contrib/client-c @@ -1 +1 @@ -Subproject commit 2c6c5fed8d7c48bcb52198f1107d4c58dd22f7e2 +Subproject commit 36e05cb0f24c085785abf367176dac2a45bfd67b From b34cd1d7c4dfe0cf3a84b21fa5c1b6e0583e0283 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Thu, 26 May 2022 12:46:47 +0800 Subject: [PATCH 066/127] Fix v3 can not find entry in mix mode. (#5001) close pingcap/tiflash#5000 --- dbms/src/Storages/Page/V3/PageDirectory.cpp | 22 +++++- .../V3/tests/gtest_page_storage_mix_mode.cpp | 76 +++++++++++++++++++ 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/dbms/src/Storages/Page/V3/PageDirectory.cpp b/dbms/src/Storages/Page/V3/PageDirectory.cpp index d944fe422d3..e9b754854b8 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectory.cpp @@ -797,7 +797,17 @@ PageIDAndEntryV3 PageDirectory::get(PageIdV3Internal page_id, const PageDirector } } - throw Exception(fmt::format("Fail to get entry [page_id={}] [seq={}] [resolve_id={}] [resolve_ver={}]", page_id, snap->sequence, id_to_resolve, ver_to_resolve), ErrorCodes::PS_ENTRY_NO_VALID_VERSION); + // Only mix mode throw_on_not_exist is false. + // In mix mode, storage will create a snapshot contains V2 and V3. + // If we find a del entry in V3, we still need find it in V2. + if (throw_on_not_exist) + { + throw Exception(fmt::format("Fail to get entry [page_id={}] [seq={}] [resolve_id={}] [resolve_ver={}]", page_id, snap->sequence, id_to_resolve, ver_to_resolve), ErrorCodes::PS_ENTRY_NO_VALID_VERSION); + } + else + { + return PageIDAndEntryV3{page_id, PageEntryV3{.file_id = INVALID_BLOBFILE_ID}}; + } } std::pair PageDirectory::get(const PageIdV3Internals & page_ids, const PageDirectorySnapshotPtr & snap, bool throw_on_not_exist) const @@ -846,7 +856,15 @@ std::pair PageDirectory::get(const PageIdV3Internal break; // continue the resolving } } - throw Exception(fmt::format("Fail to get entry [page_id={}] [ver={}] [resolve_id={}] [resolve_ver={}] [idx={}]", page_id, init_ver_to_resolve, id_to_resolve, ver_to_resolve, idx), ErrorCodes::PS_ENTRY_NO_VALID_VERSION); + + if (throw_on_not_exist) + { + throw Exception(fmt::format("Fail to get entry [page_id={}] [ver={}] [resolve_id={}] [resolve_ver={}] [idx={}]", page_id, init_ver_to_resolve, id_to_resolve, ver_to_resolve, idx), ErrorCodes::PS_ENTRY_NO_VALID_VERSION); + } + else + { + return false; + } }; PageIDAndEntriesV3 id_entries; diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp index f7ce3180172..97eac841018 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp @@ -567,6 +567,82 @@ try } CATCH +TEST_F(PageStorageMixedTest, ReadWithSnapshotAfterMergeDelta) +try +{ + UInt64 tag = 0; + const size_t buf_sz = 1024; + char c_buff[buf_sz]; + for (size_t i = 0; i < buf_sz; ++i) + { + c_buff[i] = i % 0xff; + } + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, tag, buff, buf_sz); + buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(2, tag, buff, buf_sz, {20, 120, 400, 200, 15, 75, 170, 24}); + page_writer_v2->write(std::move(batch), nullptr); + } + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + const size_t buf_sz2 = 2048; + char c_buff2[buf_sz2] = {0}; + { + WriteBatch batch; + ReadBufferPtr buff2 = std::make_shared(c_buff2, sizeof(c_buff2)); + batch.putPage(3, tag, buff2, buf_sz2); + page_writer_mix->write(std::move(batch), nullptr); + } + // Thread A create snapshot for read + auto snapshot_mix_before_merge_delta = page_reader_mix->getSnapshot("ReadWithSnapshotAfterMergeDelta"); + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_mix_before_merge_delta); + const auto & page1 = page_reader_mix_with_snap.read(1); + const auto & page2 = page_reader_mix_with_snap.read(2); + const auto & page3 = page_reader_mix_with_snap.read(3); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page2, 2); + ASSERT_PAGE_EQ(c_buff2, buf_sz2, page3, 3); + } + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, true, "ReadWithSnapshotAfterMergeDelta"); + const auto & page1 = page_reader_mix_with_snap.read(1); + const auto & page2 = page_reader_mix_with_snap.read(2); + const auto & page3 = page_reader_mix_with_snap.read(3); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page2, 2); + ASSERT_PAGE_EQ(c_buff2, buf_sz2, page3, 3); + } + // Thread B apply merge delta, create page 4, and delete the origin page 1, 3 + { + WriteBatch batch; + ReadBufferPtr buff2 = std::make_shared(c_buff2, sizeof(c_buff2)); + batch.putPage(4, tag, buff2, buf_sz2); + batch.delPage(1); + batch.delPage(3); + page_writer_mix->write(std::move(batch), nullptr); + } + // Thread A continue to read 1, 3 + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_mix_before_merge_delta); + // read 1, 3 with snapshot, should be success + const auto & page1 = page_reader_mix_with_snap.read(1); + const auto & page3 = page_reader_mix_with_snap.read(3); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 1); + ASSERT_PAGE_EQ(c_buff2, buf_sz2, page3, 3); + ASSERT_THROW(page_reader_mix_with_snap.read(4), DB::Exception); + } + { + // Revert v3 + WriteBatch batch; + batch.delPage(3); + batch.delPage(4); + page_writer_mix->write(std::move(batch), nullptr); + } +} +CATCH + } // namespace PS::V3::tests } // namespace DB From 973de13fef5e787be1f2867f2a4fbb4af20ee77d Mon Sep 17 00:00:00 2001 From: xufei Date: Thu, 26 May 2022 21:12:47 +0800 Subject: [PATCH 067/127] Refine function test framework (#4861) close pingcap/tiflash#4830 --- dbms/src/Debug/astToExecutor.cpp | 190 ++++++++-------- dbms/src/Debug/astToExecutor.h | 2 + .../Coprocessor/DAGExpressionAnalyzer.cpp | 24 +-- .../Flash/Coprocessor/DAGExpressionAnalyzer.h | 4 - .../DAGExpressionAnalyzerHelper.cpp | 33 ++- .../Coprocessor/DAGExpressionAnalyzerHelper.h | 9 + dbms/src/Flash/Coprocessor/DAGUtils.cpp | 18 +- dbms/src/Flash/Coprocessor/DAGUtils.h | 1 + dbms/src/TestUtils/ColumnsToTiPBExpr.cpp | 202 ++++++++++++++++++ dbms/src/TestUtils/ColumnsToTiPBExpr.h | 35 +++ dbms/src/TestUtils/FunctionTestUtils.cpp | 136 ++++++++---- dbms/src/TestUtils/FunctionTestUtils.h | 27 ++- 12 files changed, 521 insertions(+), 160 deletions(-) create mode 100644 dbms/src/TestUtils/ColumnsToTiPBExpr.cpp create mode 100644 dbms/src/TestUtils/ColumnsToTiPBExpr.h diff --git a/dbms/src/Debug/astToExecutor.cpp b/dbms/src/Debug/astToExecutor.cpp index 999eb6d2e68..82f894905e6 100644 --- a/dbms/src/Debug/astToExecutor.cpp +++ b/dbms/src/Debug/astToExecutor.cpp @@ -31,6 +31,93 @@ namespace DB { +void literalFieldToTiPBExpr(const ColumnInfo & ci, const Field & val_field, tipb::Expr * expr, Int32 collator_id) +{ + *(expr->mutable_field_type()) = columnInfoToFieldType(ci); + expr->mutable_field_type()->set_collate(collator_id); + if (!val_field.isNull()) + { + WriteBufferFromOwnString ss; + switch (ci.tp) + { + case TiDB::TypeLongLong: + case TiDB::TypeLong: + case TiDB::TypeShort: + case TiDB::TypeTiny: + case TiDB::TypeInt24: + if (ci.hasUnsignedFlag()) + { + expr->set_tp(tipb::ExprType::Uint64); + UInt64 val = val_field.safeGet(); + encodeDAGUInt64(val, ss); + } + else + { + expr->set_tp(tipb::ExprType::Int64); + Int64 val = val_field.safeGet(); + encodeDAGInt64(val, ss); + } + break; + case TiDB::TypeFloat: + { + expr->set_tp(tipb::ExprType::Float32); + auto val = static_cast(val_field.safeGet()); + encodeDAGFloat32(val, ss); + break; + } + case TiDB::TypeDouble: + { + expr->set_tp(tipb::ExprType::Float64); + Float64 val = val_field.safeGet(); + encodeDAGFloat64(val, ss); + break; + } + case TiDB::TypeString: + { + expr->set_tp(tipb::ExprType::String); + const auto & val = val_field.safeGet(); + encodeDAGString(val, ss); + break; + } + case TiDB::TypeNewDecimal: + { + expr->set_tp(tipb::ExprType::MysqlDecimal); + encodeDAGDecimal(val_field, ss); + break; + } + case TiDB::TypeDate: + { + expr->set_tp(tipb::ExprType::MysqlTime); + UInt64 val = val_field.safeGet(); + encodeDAGUInt64(MyDate(val).toPackedUInt(), ss); + break; + } + case TiDB::TypeDatetime: + case TiDB::TypeTimestamp: + { + expr->set_tp(tipb::ExprType::MysqlTime); + UInt64 val = val_field.safeGet(); + encodeDAGUInt64(MyDateTime(val).toPackedUInt(), ss); + break; + } + case TiDB::TypeTime: + { + expr->set_tp(tipb::ExprType::MysqlDuration); + Int64 val = val_field.safeGet(); + encodeDAGInt64(val, ss); + break; + } + default: + throw Exception(fmt::format("Type {} does not support literal in function unit test", getDataTypeByColumnInfo(ci)->getName())); + } + expr->set_val(ss.releaseStr()); + } + else + { + expr->set_tp(tipb::ExprType::Null); + } +} + namespace { std::unordered_map func_name_to_sig({ @@ -112,76 +199,9 @@ DAGColumnInfo toNullableDAGColumnInfo(const DAGColumnInfo & input) void literalToPB(tipb::Expr * expr, const Field & value, uint32_t collator_id) { - WriteBufferFromOwnString ss; - switch (value.getType()) - { - case Field::Types::Which::Null: - { - expr->set_tp(tipb::Null); - auto * ft = expr->mutable_field_type(); - ft->set_tp(TiDB::TypeNull); - ft->set_collate(collator_id); - // Null literal expr doesn't need value. - break; - } - case Field::Types::Which::UInt64: - { - expr->set_tp(tipb::Uint64); - auto * ft = expr->mutable_field_type(); - ft->set_tp(TiDB::TypeLongLong); - ft->set_flag(TiDB::ColumnFlagUnsigned | TiDB::ColumnFlagNotNull); - ft->set_collate(collator_id); - encodeDAGUInt64(value.get(), ss); - break; - } - case Field::Types::Which::Int64: - { - expr->set_tp(tipb::Int64); - auto * ft = expr->mutable_field_type(); - ft->set_tp(TiDB::TypeLongLong); - ft->set_flag(TiDB::ColumnFlagNotNull); - ft->set_collate(collator_id); - encodeDAGInt64(value.get(), ss); - break; - } - case Field::Types::Which::Float64: - { - expr->set_tp(tipb::Float64); - auto * ft = expr->mutable_field_type(); - ft->set_tp(TiDB::TypeFloat); - ft->set_flag(TiDB::ColumnFlagNotNull); - ft->set_collate(collator_id); - encodeDAGFloat64(value.get(), ss); - break; - } - case Field::Types::Which::Decimal32: - case Field::Types::Which::Decimal64: - case Field::Types::Which::Decimal128: - case Field::Types::Which::Decimal256: - { - expr->set_tp(tipb::MysqlDecimal); - auto * ft = expr->mutable_field_type(); - ft->set_tp(TiDB::TypeNewDecimal); - ft->set_flag(TiDB::ColumnFlagNotNull); - ft->set_collate(collator_id); - encodeDAGDecimal(value, ss); - break; - } - case Field::Types::Which::String: - { - expr->set_tp(tipb::String); - auto * ft = expr->mutable_field_type(); - ft->set_tp(TiDB::TypeString); - ft->set_flag(TiDB::ColumnFlagNotNull); - ft->set_collate(collator_id); - // TODO: Align with TiDB. - encodeDAGBytes(value.get(), ss); - break; - } - default: - throw Exception(String("Unsupported literal type: ") + value.getTypeName(), ErrorCodes::LOGICAL_ERROR); - } - expr->set_val(ss.releaseStr()); + DataTypePtr type = applyVisitor(FieldToDataType(), value); + ColumnInfo ci = reverseGetColumnInfo({"", type}, 0, Field(), true); + literalFieldToTiPBExpr(ci, value, expr, collator_id); } String getFunctionNameForConstantFolding(tipb::Expr * expr) @@ -262,15 +282,15 @@ void identifierToPB(const DAGSchema & input, ASTIdentifier * id, tipb::Expr * ex void astToPB(const DAGSchema & input, ASTPtr ast, tipb::Expr * expr, uint32_t collator_id, const Context & context) { - if (ASTIdentifier * id = typeid_cast(ast.get())) + if (auto * id = typeid_cast(ast.get())) { identifierToPB(input, id, expr, collator_id); } - else if (ASTFunction * func = typeid_cast(ast.get())) + else if (auto * func = typeid_cast(ast.get())) { functionToPB(input, func, expr, collator_id, context); } - else if (ASTLiteral * lit = typeid_cast(ast.get())) + else if (auto * lit = typeid_cast(ast.get())) { literalToPB(expr, lit->value, collator_id); } @@ -505,7 +525,7 @@ void identifierToPB(const DAGSchema & input, ASTIdentifier * id, tipb::Expr * ex void collectUsedColumnsFromExpr(const DAGSchema & input, ASTPtr ast, std::unordered_set & used_columns) { - if (ASTIdentifier * id = typeid_cast(ast.get())) + if (auto * id = typeid_cast(ast.get())) { auto column_name = splitQualifiedName(id->getColumnName()); if (!column_name.first.empty()) @@ -526,7 +546,7 @@ void collectUsedColumnsFromExpr(const DAGSchema & input, ASTPtr ast, std::unorde } } } - else if (ASTFunction * func = typeid_cast(ast.get())) + else if (auto * func = typeid_cast(ast.get())) { if (AggregateFunctionFactory::instance().isAggregateFunctionName(func->name)) { @@ -559,7 +579,7 @@ void collectUsedColumnsFromExpr(const DAGSchema & input, ASTPtr ast, std::unorde TiDB::ColumnInfo compileExpr(const DAGSchema & input, ASTPtr ast) { TiDB::ColumnInfo ci; - if (ASTIdentifier * id = typeid_cast(ast.get())) + if (auto * id = typeid_cast(ast.get())) { /// check column auto ft = std::find_if(input.begin(), input.end(), [&](const auto & field) { @@ -574,7 +594,7 @@ TiDB::ColumnInfo compileExpr(const DAGSchema & input, ASTPtr ast) throw Exception("No such column " + id->getColumnName(), ErrorCodes::NO_SUCH_COLUMN_IN_TABLE); ci = ft->second; } - else if (ASTFunction * func = typeid_cast(ast.get())) + else if (auto * func = typeid_cast(ast.get())) { /// check function String func_name_lowercase = Poco::toLower(func->name); @@ -692,7 +712,7 @@ TiDB::ColumnInfo compileExpr(const DAGSchema & input, ASTPtr ast) compileExpr(input, child_ast); } } - else if (ASTLiteral * lit = typeid_cast(ast.get())) + else if (auto * lit = typeid_cast(ast.get())) { switch (lit->value.getType()) { @@ -909,7 +929,7 @@ bool TopN::toTiPBExecutor(tipb::Executor * tipb_executor, uint32_t collator_id, tipb::TopN * topn = tipb_executor->mutable_topn(); for (const auto & child : order_columns) { - ASTOrderByElement * elem = typeid_cast(child.get()); + auto * elem = typeid_cast(child.get()); if (!elem) throw Exception("Invalid order by element", ErrorCodes::LOGICAL_ERROR); tipb::ByItem * by = topn->add_order_by(); @@ -954,7 +974,7 @@ bool Aggregation::toTiPBExecutor(tipb::Executor * tipb_executor, uint32_t collat auto & input_schema = children[0]->output_schema; for (const auto & expr : agg_exprs) { - const ASTFunction * func = typeid_cast(expr.get()); + const auto * func = typeid_cast(expr.get()); if (!func || !AggregateFunctionFactory::instance().isAggregateFunctionName(func->name)) throw Exception("Only agg function is allowed in select for a query with aggregation", ErrorCodes::LOGICAL_ERROR); @@ -1024,7 +1044,7 @@ void Aggregation::columnPrune(std::unordered_set & used_columns) { if (used_columns.find(func->getColumnName()) != used_columns.end()) { - const ASTFunction * agg_func = typeid_cast(func.get()); + const auto * agg_func = typeid_cast(func.get()); if (agg_func != nullptr) { /// agg_func should not be nullptr, just double check @@ -1075,7 +1095,7 @@ void Aggregation::toMPPSubPlan(size_t & executor_index, const DAGProperties & pr /// re-construct agg_exprs and gby_exprs in final_agg for (size_t i = 0; i < partial_agg->agg_exprs.size(); i++) { - const ASTFunction * agg_func = typeid_cast(partial_agg->agg_exprs[i].get()); + const auto * agg_func = typeid_cast(partial_agg->agg_exprs[i].get()); ASTPtr update_agg_expr = agg_func->clone(); auto * update_agg_func = typeid_cast(update_agg_expr.get()); if (agg_func->name == "count") @@ -1368,7 +1388,7 @@ ExecutorPtr compileTopN(ExecutorPtr input, size_t & executor_index, ASTPtr order std::vector order_columns; for (const auto & child : order_exprs->children) { - ASTOrderByElement * elem = typeid_cast(child.get()); + auto * elem = typeid_cast(child.get()); if (!elem) throw Exception("Invalid order by element", ErrorCodes::LOGICAL_ERROR); order_columns.push_back(child); @@ -1399,7 +1419,7 @@ ExecutorPtr compileAggregation(ExecutorPtr input, size_t & executor_index, ASTPt { for (const auto & expr : agg_funcs->children) { - const ASTFunction * func = typeid_cast(expr.get()); + const auto * func = typeid_cast(expr.get()); if (!func || !AggregateFunctionFactory::instance().isAggregateFunctionName(func->name)) { need_append_project = true; @@ -1490,7 +1510,7 @@ ExecutorPtr compileProject(ExecutorPtr input, size_t & executor_index, ASTPtr se output_schema.emplace_back(ft->first, ft->second); continue; } - const ASTFunction * func = typeid_cast(expr.get()); + const auto * func = typeid_cast(expr.get()); if (func && AggregateFunctionFactory::instance().isAggregateFunctionName(func->name)) { throw Exception("No such agg " + func->getColumnName(), ErrorCodes::NO_SUCH_COLUMN_IN_TABLE); diff --git a/dbms/src/Debug/astToExecutor.h b/dbms/src/Debug/astToExecutor.h index 54839e60dc6..37d3f22b6e1 100644 --- a/dbms/src/Debug/astToExecutor.h +++ b/dbms/src/Debug/astToExecutor.h @@ -294,6 +294,8 @@ ExecutorPtr compileExchangeSender(ExecutorPtr input, size_t & executor_index, ti ExecutorPtr compileExchangeReceiver(size_t & executor_index, DAGSchema schema); +void literalFieldToTiPBExpr(const ColumnInfo & ci, const Field & field, tipb::Expr * expr, Int32 collator_id); + //TODO: add compileWindow } // namespace DB \ No newline at end of file diff --git a/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.cpp b/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.cpp index cffae76cb81..aa269469cdb 100644 --- a/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.cpp +++ b/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.cpp @@ -1279,15 +1279,7 @@ String DAGExpressionAnalyzer::getActions(const tipb::Expr & expr, const Expressi } else if (isScalarFunctionExpr(expr)) { - const String & func_name = getFunctionName(expr); - if (DAGExpressionAnalyzerHelper::function_builder_map.count(func_name) != 0) - { - ret = DAGExpressionAnalyzerHelper::function_builder_map[func_name](this, expr, actions); - } - else - { - ret = buildFunction(expr, actions); - } + ret = DAGExpressionAnalyzerHelper::buildFunction(this, expr, actions); } else { @@ -1341,18 +1333,4 @@ String DAGExpressionAnalyzer::buildTupleFunctionForGroupConcat( return applyFunction(func_name, argument_names, actions, nullptr); } -String DAGExpressionAnalyzer::buildFunction( - const tipb::Expr & expr, - const ExpressionActionsPtr & actions) -{ - const String & func_name = getFunctionName(expr); - Names argument_names; - for (const auto & child : expr.children()) - { - String name = getActions(child, actions); - argument_names.push_back(name); - } - return applyFunction(func_name, argument_names, actions, getCollatorFromExpr(expr)); -} - } // namespace DB diff --git a/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h b/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h index f565e7a6348..3b7112af02d 100644 --- a/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h +++ b/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h @@ -275,10 +275,6 @@ class DAGExpressionAnalyzer : private boost::noncopyable const ExpressionActionsPtr & actions, const String & column_name); - String buildFunction( - const tipb::Expr & expr, - const ExpressionActionsPtr & actions); - String buildFilterColumn( const ExpressionActionsPtr & actions, const std::vector & conditions); diff --git a/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.cpp b/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.cpp index cabd88e0ba7..ee529680d28 100644 --- a/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.cpp +++ b/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.cpp @@ -248,7 +248,7 @@ String DAGExpressionAnalyzerHelper::buildCastFunctionInternal( return result_name; FunctionBuilderPtr function_builder = FunctionFactory::instance().get(tidb_cast_name, analyzer->getContext()); - FunctionBuilderTiDBCast * function_builder_tidb_cast = dynamic_cast(function_builder.get()); + auto * function_builder_tidb_cast = dynamic_cast(function_builder.get()); function_builder_tidb_cast->setInUnion(in_union); function_builder_tidb_cast->setTiDBFieldType(field_type); @@ -401,6 +401,37 @@ String DAGExpressionAnalyzerHelper::buildRegexpFunction( return analyzer->applyFunction(func_name, argument_names, actions, collator); } +String DAGExpressionAnalyzerHelper::buildDefaultFunction( + DAGExpressionAnalyzer * analyzer, + const tipb::Expr & expr, + const ExpressionActionsPtr & actions) +{ + const String & func_name = getFunctionName(expr); + Names argument_names; + for (const auto & child : expr.children()) + { + String name = analyzer->getActions(child, actions); + argument_names.push_back(name); + } + return analyzer->applyFunction(func_name, argument_names, actions, getCollatorFromExpr(expr)); +} + +String DAGExpressionAnalyzerHelper::buildFunction( + DAGExpressionAnalyzer * analyzer, + const tipb::Expr & expr, + const ExpressionActionsPtr & actions) +{ + const String & func_name = getFunctionName(expr); + if (function_builder_map.count(func_name) != 0) + { + return function_builder_map[func_name](analyzer, expr, actions); + } + else + { + return buildDefaultFunction(analyzer, expr, actions); + } +} + DAGExpressionAnalyzerHelper::FunctionBuilderMap DAGExpressionAnalyzerHelper::function_builder_map( {{"in", DAGExpressionAnalyzerHelper::buildInFunction}, {"notIn", DAGExpressionAnalyzerHelper::buildInFunction}, diff --git a/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.h b/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.h index c8f2b658388..fcafcc57819 100644 --- a/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.h +++ b/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.h @@ -25,6 +25,10 @@ class DAGExpressionAnalyzer; class DAGExpressionAnalyzerHelper { public: + static String buildFunction( + DAGExpressionAnalyzer * analyzer, + const tipb::Expr & expr, + const ExpressionActionsPtr & actions); static String buildInFunction( DAGExpressionAnalyzer * analyzer, const tipb::Expr & expr, @@ -83,6 +87,11 @@ class DAGExpressionAnalyzerHelper const tipb::Expr & expr, const ExpressionActionsPtr & actions); + static String buildDefaultFunction( + DAGExpressionAnalyzer * analyzer, + const tipb::Expr & expr, + const ExpressionActionsPtr & actions); + using FunctionBuilder = std::function; using FunctionBuilderMap = std::unordered_map; diff --git a/dbms/src/Flash/Coprocessor/DAGUtils.cpp b/dbms/src/Flash/Coprocessor/DAGUtils.cpp index 66f5d7031d7..87f58131c8c 100644 --- a/dbms/src/Flash/Coprocessor/DAGUtils.cpp +++ b/dbms/src/Flash/Coprocessor/DAGUtils.cpp @@ -707,7 +707,16 @@ void assertBlockSchema( actual->getName())); } } - +/// used by test +std::unordered_map getFuncNameToSigMap() +{ + std::unordered_map ret; + for (const auto & element : scalar_func_map) + { + ret[element.second] = element.first; + } + return ret; +} } // namespace bool isScalarFunctionExpr(const tipb::Expr & expr) @@ -1420,5 +1429,12 @@ tipb::EncodeType analyzeDAGEncodeType(DAGContext & dag_context) return tipb::EncodeType::TypeDefault; return encode_type; } +tipb::ScalarFuncSig reverseGetFuncSigByFuncName(const String & name) +{ + static std::unordered_map func_name_sig_map = getFuncNameToSigMap(); + if (func_name_sig_map.find(name) == func_name_sig_map.end()) + throw Exception(fmt::format("Unsupported function {}", name)); + return func_name_sig_map[name]; +} } // namespace DB diff --git a/dbms/src/Flash/Coprocessor/DAGUtils.h b/dbms/src/Flash/Coprocessor/DAGUtils.h index 4d6a62bbe6f..5776edf0098 100644 --- a/dbms/src/Flash/Coprocessor/DAGUtils.h +++ b/dbms/src/Flash/Coprocessor/DAGUtils.h @@ -104,5 +104,6 @@ class UniqueNameGenerator tipb::DAGRequest getDAGRequestFromStringWithRetry(const String & s); tipb::EncodeType analyzeDAGEncodeType(DAGContext & dag_context); +tipb::ScalarFuncSig reverseGetFuncSigByFuncName(const String & name); } // namespace DB diff --git a/dbms/src/TestUtils/ColumnsToTiPBExpr.cpp b/dbms/src/TestUtils/ColumnsToTiPBExpr.cpp new file mode 100644 index 00000000000..2c3bf243176 --- /dev/null +++ b/dbms/src/TestUtils/ColumnsToTiPBExpr.cpp @@ -0,0 +1,202 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include +#include +#include + +namespace DB +{ +namespace tests +{ +namespace +{ +void columnToTiPBExpr(tipb::Expr * expr, const ColumnWithTypeAndName column, size_t index) +{ + ColumnInfo ci = reverseGetColumnInfo({column.name, column.type}, 0, Field(), true); + bool is_const = false; + if (column.column != nullptr) + { + is_const = column.column->isColumnConst(); + if (!is_const) + { + if (column.column->isColumnNullable()) + { + auto [col, null_map] = removeNullable(column.column.get()); + is_const = col->isColumnConst(); + } + } + } + if (is_const) + { + Field val_field; + column.column->get(0, val_field); + literalFieldToTiPBExpr(ci, val_field, expr, 0); + } + else + { + *(expr->mutable_field_type()) = columnInfoToFieldType(ci); + expr->set_tp(tipb::ExprType::ColumnRef); + WriteBufferFromOwnString ss; + encodeDAGInt64(index, ss); + expr->set_val(ss.releaseStr()); + } +} +void columnsToTiPBExprForRegExp( + tipb::Expr * expr, + const String &, + const ColumnNumbers & argument_column_number, + const ColumnsWithTypeAndName & columns, + const TiDB::TiDBCollatorPtr & collator) +{ + expr->set_tp(tipb::ExprType::ScalarFunc); + if (collator == nullptr || !collator->isBinary()) + expr->set_sig(tipb::ScalarFuncSig::RegexpUTF8Sig); + else + expr->set_sig(tipb::ScalarFuncSig::RegexpSig); + for (size_t i = 0; i < argument_column_number.size(); ++i) + { + auto * argument_expr = expr->add_children(); + columnToTiPBExpr(argument_expr, columns[argument_column_number[i]], i); + } + /// since we don't know the type, just set a fake one + expr->mutable_field_type()->set_tp(TiDB::TypeLongLong); + if (collator != nullptr) + expr->mutable_field_type()->set_collate(-collator->getCollatorId()); +} +void columnsToTiPBExprForTiDBCast( + tipb::Expr * expr, + const String & func_name, + const ColumnNumbers & argument_column_number, + const ColumnsWithTypeAndName & columns, + const TiDB::TiDBCollatorPtr & collator) +{ + expr->set_tp(tipb::ExprType::ScalarFunc); + expr->set_sig(reverseGetFuncSigByFuncName(func_name)); + assert(argument_column_number.size() == 2); + const auto & type_column = columns[argument_column_number[1]]; + bool is_const = false; + if (type_column.column != nullptr) + { + is_const = type_column.column->isColumnConst(); + if (!is_const) + { + if (type_column.column->isColumnNullable()) + { + auto [col, null_map] = removeNullable(type_column.column.get()); + is_const = col->isColumnConst(); + } + } + } + assert(is_const && removeNullable(type_column.type)->isString()); + Field val; + type_column.column->get(0, val); + String type_string = val.safeGet(); + DataTypePtr target_type = DataTypeFactory::instance().get(type_string); + auto * argument_expr = expr->add_children(); + columnToTiPBExpr(argument_expr, columns[argument_column_number[0]], 0); + ColumnInfo ci = reverseGetColumnInfo({type_string, target_type}, 0, Field(), true); + *(expr->mutable_field_type()) = columnInfoToFieldType(ci); + if (collator != nullptr) + expr->mutable_field_type()->set_collate(-collator->getCollatorId()); +} + +const std::unordered_map date_add_sub_map({ + {"addDays", "DAY"}, + {"addWeeks", "WEEK"}, + {"addMonths", "MONTH"}, + {"addYears", "YEAR"}, + {"addHours", "HOUR"}, + {"addMinutes", "MINUTE"}, + {"addSeconds", "SECOND"}, + {"subtractDays", "DAY"}, + {"subtractWeeks", "WEEK"}, + {"subtractMonths", "MONTH"}, + {"subtractYears", "YEAR"}, + {"subtractHours", "HOUR"}, + {"subtractMinutes", "MINUTE"}, + {"subtractSeconds", "SECOND"}, +}); + +void columnsToTiPBExprForDateAddSub( + tipb::Expr * expr, + const String & func_name, + const ColumnNumbers & argument_column_number, + const ColumnsWithTypeAndName & columns, + const TiDB::TiDBCollatorPtr & collator) +{ + String name = func_name.substr(0, 3) == "add" ? "date_add" : "date_sub"; + expr->set_tp(tipb::ExprType::ScalarFunc); + expr->set_sig(reverseGetFuncSigByFuncName(name)); + for (size_t i = 0; i < argument_column_number.size(); ++i) + { + auto * argument_expr = expr->add_children(); + columnToTiPBExpr(argument_expr, columns[argument_column_number[i]], i); + } + String unit = date_add_sub_map.find(func_name)->second; + *(expr->add_children()) = constructStringLiteralTiExpr(unit); + /// since we don't know the type, just set a fake one + expr->mutable_field_type()->set_tp(TiDB::TypeLongLong); + if (collator != nullptr) + expr->mutable_field_type()->set_collate(-collator->getCollatorId()); +} +void columnsToTiPBExpr( + tipb::Expr * expr, + const String & func_name, + const ColumnNumbers & argument_column_number, + const ColumnsWithTypeAndName & columns, + const TiDB::TiDBCollatorPtr & collator) +{ + if (func_name == "tidb_cast") + { + columnsToTiPBExprForTiDBCast(expr, func_name, argument_column_number, columns, collator); + } + else if (func_name == "regexp") + { + columnsToTiPBExprForRegExp(expr, func_name, argument_column_number, columns, collator); + } + else if (date_add_sub_map.find(func_name) != date_add_sub_map.end()) + { + columnsToTiPBExprForDateAddSub(expr, func_name, argument_column_number, columns, collator); + } + else + { + expr->set_tp(tipb::ExprType::ScalarFunc); + expr->set_sig(reverseGetFuncSigByFuncName(func_name)); + for (size_t i = 0; i < argument_column_number.size(); ++i) + { + auto * argument_expr = expr->add_children(); + columnToTiPBExpr(argument_expr, columns[argument_column_number[i]], i); + } + /// since we don't know the type, just set a fake one + expr->mutable_field_type()->set_tp(TiDB::TypeLongLong); + if (collator != nullptr) + expr->mutable_field_type()->set_collate(-collator->getCollatorId()); + } +} +} // namespace + +tipb::Expr columnsToTiPBExpr( + const String & func_name, + const ColumnNumbers & argument_column_number, + const ColumnsWithTypeAndName & columns, + const TiDB::TiDBCollatorPtr & collator) +{ + tipb::Expr ret; + columnsToTiPBExpr(&ret, func_name, argument_column_number, columns, collator); + return ret; +} +} // namespace tests +} // namespace DB diff --git a/dbms/src/TestUtils/ColumnsToTiPBExpr.h b/dbms/src/TestUtils/ColumnsToTiPBExpr.h new file mode 100644 index 00000000000..e7a2e81d59e --- /dev/null +++ b/dbms/src/TestUtils/ColumnsToTiPBExpr.h @@ -0,0 +1,35 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace DB +{ +namespace tests +{ +tipb::Expr columnsToTiPBExpr( + const String & func_name, + const ColumnNumbers & argument_column_number, + const ColumnsWithTypeAndName & columns, + const TiDB::TiDBCollatorPtr & collator); +} // namespace tests +} // namespace DB diff --git a/dbms/src/TestUtils/FunctionTestUtils.cpp b/dbms/src/TestUtils/FunctionTestUtils.cpp index 6aa7541ee59..dae07f7123b 100644 --- a/dbms/src/TestUtils/FunctionTestUtils.cpp +++ b/dbms/src/TestUtils/FunctionTestUtils.cpp @@ -12,10 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include +#include +#include +#include #include #include +#include #include #include #include @@ -115,60 +120,113 @@ void blockEqual( } } +std::pair buildFunction( + Context & context, + const String & func_name, + const ColumnNumbers & argument_column_numbers, + const ColumnsWithTypeAndName & columns, + const TiDB::TiDBCollatorPtr & collator) +{ + tipb::Expr tipb_expr = columnsToTiPBExpr(func_name, argument_column_numbers, columns, collator); + NamesAndTypes source_columns; + for (size_t index : argument_column_numbers) + source_columns.emplace_back(columns[index].name, columns[index].type); + DAGExpressionAnalyzer analyzer(source_columns, context); + ExpressionActionsChain chain; + auto & last_step = analyzer.initAndGetLastStep(chain); + auto result_name = DB::DAGExpressionAnalyzerHelper::buildFunction(&analyzer, tipb_expr, last_step.actions); + last_step.required_output.push_back(result_name); + chain.finalize(); + return std::make_pair(last_step.actions, result_name); +} -ColumnWithTypeAndName executeFunction(Context & context, const String & func_name, const ColumnsWithTypeAndName & columns, const TiDB::TiDBCollatorPtr & collator) +ColumnsWithTypeAndName toColumnsWithUniqueName(const ColumnsWithTypeAndName & columns) { - auto & factory = FunctionFactory::instance(); + ColumnsWithTypeAndName columns_with_distinct_name = columns; + std::string base_name = "col"; + for (size_t i = 0; i < columns.size(); ++i) + { + columns_with_distinct_name[i].name = fmt::format("{}_{}", base_name, i); + } + return columns_with_distinct_name; +} - Block block(columns); - ColumnNumbers cns; +ColumnWithTypeAndName executeFunction( + Context & context, + const String & func_name, + const ColumnsWithTypeAndName & columns, + const TiDB::TiDBCollatorPtr & collator, + bool raw_function_test) +{ + ColumnNumbers argument_column_numbers; for (size_t i = 0; i < columns.size(); ++i) - cns.push_back(i); - - auto bp = factory.tryGet(func_name, context); - if (!bp) - throw TiFlashTestException(fmt::format("Function {} not found!", func_name)); - auto func = bp->build(columns, collator); - block.insert({nullptr, func->getReturnType(), "res"}); - func->execute(block, cns, columns.size()); - return block.getByPosition(columns.size()); + argument_column_numbers.push_back(i); + return executeFunction(context, func_name, argument_column_numbers, columns, collator, raw_function_test); } -ColumnWithTypeAndName executeFunction(Context & context, const String & func_name, const ColumnNumbers & argument_column_numbers, const ColumnsWithTypeAndName & columns) +ColumnWithTypeAndName executeFunction( + Context & context, + const String & func_name, + const ColumnNumbers & argument_column_numbers, + const ColumnsWithTypeAndName & columns, + const TiDB::TiDBCollatorPtr & collator, + bool raw_function_test) { - auto & factory = FunctionFactory::instance(); - Block block(columns); - ColumnsWithTypeAndName arguments; - for (size_t i = 0; i < argument_column_numbers.size(); ++i) - arguments.push_back(columns.at(i)); - auto bp = factory.tryGet(func_name, context); - if (!bp) - throw TiFlashTestException(fmt::format("Function {} not found!", func_name)); - auto func = bp->build(arguments); - block.insert({nullptr, func->getReturnType(), "res"}); - func->execute(block, argument_column_numbers, columns.size()); - return block.getByPosition(columns.size()); + if (raw_function_test) + { + auto & factory = FunctionFactory::instance(); + Block block(columns); + ColumnsWithTypeAndName arguments; + for (size_t i = 0; i < argument_column_numbers.size(); ++i) + arguments.push_back(columns.at(i)); + auto bp = factory.tryGet(func_name, context); + if (!bp) + throw TiFlashTestException(fmt::format("Function {} not found!", func_name)); + auto func = bp->build(arguments, collator); + block.insert({nullptr, func->getReturnType(), "res"}); + func->execute(block, argument_column_numbers, columns.size()); + return block.getByPosition(columns.size()); + } + auto columns_with_unique_name = toColumnsWithUniqueName(columns); + auto [actions, result_name] = buildFunction(context, func_name, argument_column_numbers, columns_with_unique_name, collator); + Block block(columns_with_unique_name); + actions->execute(block); + return block.getByName(result_name); } DataTypePtr getReturnTypeForFunction( Context & context, const String & func_name, const ColumnsWithTypeAndName & columns, - const TiDB::TiDBCollatorPtr & collator) + const TiDB::TiDBCollatorPtr & collator, + bool raw_function_test) { - auto & factory = FunctionFactory::instance(); - - Block block(columns); - ColumnNumbers cns; - for (size_t i = 0; i < columns.size(); ++i) - cns.push_back(i); - - auto bp = factory.tryGet(func_name, context); - if (!bp) - throw TiFlashTestException(fmt::format("Function {} not found!", func_name)); - auto func = bp->build(columns, collator); - return func->getReturnType(); + if (raw_function_test) + { + auto & factory = FunctionFactory::instance(); + + Block block(columns); + ColumnNumbers cns; + for (size_t i = 0; i < columns.size(); ++i) + cns.push_back(i); + + auto bp = factory.tryGet(func_name, context); + if (!bp) + throw TiFlashTestException(fmt::format("Function {} not found!", func_name)); + auto func = bp->build(columns, collator); + return func->getReturnType(); + } + else + { + ColumnNumbers argument_column_numbers; + for (size_t i = 0; i < columns.size(); ++i) + argument_column_numbers.push_back(i); + auto columns_with_unique_name = toColumnsWithUniqueName(columns); + auto [actions, result_name] = buildFunction(context, func_name, argument_column_numbers, columns_with_unique_name, collator); + return actions->getSampleBlock().getByName(result_name).type; + } } + ColumnWithTypeAndName createOnlyNullColumnConst(size_t size, const String & name) { DataTypePtr data_type = std::make_shared(std::make_shared()); diff --git a/dbms/src/TestUtils/FunctionTestUtils.h b/dbms/src/TestUtils/FunctionTestUtils.h index e88f33a5ca7..615a58ebda5 100644 --- a/dbms/src/TestUtils/FunctionTestUtils.h +++ b/dbms/src/TestUtils/FunctionTestUtils.h @@ -535,13 +535,16 @@ ColumnWithTypeAndName executeFunction( Context & context, const String & func_name, const ColumnsWithTypeAndName & columns, - const TiDB::TiDBCollatorPtr & collator = nullptr); + const TiDB::TiDBCollatorPtr & collator = nullptr, + bool raw_function_test = true); ColumnWithTypeAndName executeFunction( Context & context, const String & func_name, const ColumnNumbers & argument_column_numbers, - const ColumnsWithTypeAndName & columns); + const ColumnsWithTypeAndName & columns, + const TiDB::TiDBCollatorPtr & collator = nullptr, + bool raw_function_test = true); template ColumnWithTypeAndName executeFunction( @@ -558,7 +561,8 @@ DataTypePtr getReturnTypeForFunction( Context & context, const String & func_name, const ColumnsWithTypeAndName & columns, - const TiDB::TiDBCollatorPtr & collator = nullptr); + const TiDB::TiDBCollatorPtr & collator = nullptr, + bool raw_function_test = true); template ColumnWithTypeAndName createNullableColumn(InferredDataVector init_vec, const std::vector & null_map, const String name = "") @@ -679,9 +683,13 @@ class FunctionTest : public ::testing::Test context.setDAGContext(dag_context_ptr.get()); } - ColumnWithTypeAndName executeFunction(const String & func_name, const ColumnsWithTypeAndName & columns, const TiDB::TiDBCollatorPtr & collator = nullptr) + ColumnWithTypeAndName executeFunction( + const String & func_name, + const ColumnsWithTypeAndName & columns, + const TiDB::TiDBCollatorPtr & collator = nullptr, + bool raw_function_test = true) { - return DB::tests::executeFunction(context, func_name, columns, collator); + return DB::tests::executeFunction(context, func_name, columns, collator, raw_function_test); } template @@ -691,9 +699,14 @@ class FunctionTest : public ::testing::Test return executeFunction(func_name, vec); } - ColumnWithTypeAndName executeFunction(const String & func_name, const ColumnNumbers & argument_column_numbers, const ColumnsWithTypeAndName & columns) + ColumnWithTypeAndName executeFunction( + const String & func_name, + const ColumnNumbers & argument_column_numbers, + const ColumnsWithTypeAndName & columns, + const TiDB::TiDBCollatorPtr & collator = nullptr, + bool raw_function_test = true) { - return DB::tests::executeFunction(context, func_name, argument_column_numbers, columns); + return DB::tests::executeFunction(context, func_name, argument_column_numbers, columns, collator, raw_function_test); } template From f10b6d27c6e0986d5db28f3f372491d5972464db Mon Sep 17 00:00:00 2001 From: yibin Date: Thu, 26 May 2022 22:26:02 +0800 Subject: [PATCH 068/127] Add mutex to protect exchange receiver's async client (#5008) * Add to_seconds support for tiflash Signed-off-by: yibin * Fix format issue Signed-off-by: yibin * Add mutex lock to protect async reciever async reader Signed-off-by: yibin * Fix a rebase issue Signed-off-by: yibin * Change raw pointer to unique ptr Signed-off-by: yibin * Fix format issue Signed-off-by: yibin Co-authored-by: Ti Chi Robot --- dbms/src/Flash/Mpp/ExchangeReceiver.cpp | 11 +++++++++-- dbms/src/Functions/FunctionsDateTime.cpp | 1 - dbms/src/Functions/FunctionsDateTime.h | 1 - 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/dbms/src/Flash/Mpp/ExchangeReceiver.cpp b/dbms/src/Flash/Mpp/ExchangeReceiver.cpp index 9639771c586..f194afee31f 100644 --- a/dbms/src/Flash/Mpp/ExchangeReceiver.cpp +++ b/dbms/src/Flash/Mpp/ExchangeReceiver.cpp @@ -92,10 +92,14 @@ class AsyncRequestHandler : public UnaryCallback switch (stage) { case AsyncRequestStage::WAIT_MAKE_READER: + { + // Use lock to ensure reader is created already in reactor thread + std::unique_lock lock(mu); if (!ok) reader.reset(); notifyReactor(); break; + } case AsyncRequestStage::WAIT_BATCH_READ: if (ok) ++read_packet_index; @@ -227,6 +231,8 @@ class AsyncRequestHandler : public UnaryCallback void start() { stage = AsyncRequestStage::WAIT_MAKE_READER; + // Use lock to ensure async reader is unreachable from grpc thread before this function returns + std::unique_lock lock(mu); rpc_context->makeAsyncReader(*request, reader, thisAsUnaryCallback()); } @@ -283,6 +289,7 @@ class AsyncRequestHandler : public UnaryCallback size_t read_packet_index = 0; Status finish_status = RPCContext::getStatusOK(); LoggerPtr log; + std::mutex mu; }; } // namespace @@ -393,10 +400,10 @@ void ExchangeReceiverBase::reactor(const std::vector & asyn MPMCQueue ready_requests(alive_async_connections * 2); std::vector waiting_for_retry_requests; - std::vector> handlers; + std::vector> handlers; handlers.reserve(alive_async_connections); for (const auto & req : async_requests) - handlers.emplace_back(&ready_requests, &msg_channel, rpc_context, req, exc_log->identifier()); + handlers.emplace_back(std::make_unique(&ready_requests, &msg_channel, rpc_context, req, exc_log->identifier())); while (alive_async_connections > 0) { diff --git a/dbms/src/Functions/FunctionsDateTime.cpp b/dbms/src/Functions/FunctionsDateTime.cpp index dd072a00f76..607f6bc4c99 100644 --- a/dbms/src/Functions/FunctionsDateTime.cpp +++ b/dbms/src/Functions/FunctionsDateTime.cpp @@ -254,7 +254,6 @@ void registerFunctionsDateTime(FunctionFactory & factory) factory.registerFunction(); factory.registerFunction(); factory.registerFunction(); - factory.registerFunction(); factory.registerFunction(); } diff --git a/dbms/src/Functions/FunctionsDateTime.h b/dbms/src/Functions/FunctionsDateTime.h index df579a1bab8..08f5a5887d4 100644 --- a/dbms/src/Functions/FunctionsDateTime.h +++ b/dbms/src/Functions/FunctionsDateTime.h @@ -3450,7 +3450,6 @@ using FunctionToTiDBDayOfYear = FunctionMyDateOrMyDateTimeToSomething; using FunctionToTiDBToSeconds = FunctionMyDateOrMyDateTimeToSomething; using FunctionToTiDBToDays = FunctionMyDateOrMyDateTimeToSomething; - using FunctionToRelativeYearNum = FunctionDateOrDateTimeToSomething; using FunctionToRelativeQuarterNum = FunctionDateOrDateTimeToSomething; using FunctionToRelativeMonthNum = FunctionDateOrDateTimeToSomething; From d84f273182aabc70e445497cb51bad9ecfb7deec Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Fri, 27 May 2022 17:42:48 +0800 Subject: [PATCH 069/127] Optimize blobstore gc when current stat total size is 0 (#5015) close pingcap/tiflash#5016 --- dbms/src/Storages/Page/V3/BlobStore.cpp | 19 +++++++++++++++++-- dbms/src/Storages/Page/V3/BlobStore.h | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/dbms/src/Storages/Page/V3/BlobStore.cpp b/dbms/src/Storages/Page/V3/BlobStore.cpp index dc5ed536f9e..f03bc5bcf73 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.cpp +++ b/dbms/src/Storages/Page/V3/BlobStore.cpp @@ -914,7 +914,22 @@ std::vector BlobStore::getGCStats() // Avoid divide by zero if (right_margin == 0) { - LOG_FMT_TRACE(log, "Current blob is empty [blob_id={}, total size(all invalid)={}].", stat->id, stat->sm_total_size); + if (unlikely(stat->sm_valid_rate != 0)) + { + throw Exception(fmt::format("Current blob is empty, but valid rate is not 0. [blob_id={}][valid_size={}][valid_rate={}]", + stat->id, + stat->sm_valid_size, + stat->sm_valid_rate)); + } + + LOG_FMT_TRACE(log, "Current blob is empty [blob_id={}, total size(all invalid)={}] [valid_rate={}].", stat->id, stat->sm_total_size, stat->sm_valid_rate); + + // If current blob empty, the size of in disk blob may not empty + // So we need truncate current blob, and let it be reused. + auto blobfile = getBlobFile(stat->id); + LOG_FMT_TRACE(log, "Truncate empty blob file [blob_id={}] to 0.", stat->id); + blobfile->truncate(right_margin); + blobstore_gc_info.appendToTruncatedBlob(stat->id, stat->sm_valid_rate); continue; } @@ -1468,7 +1483,7 @@ void BlobStore::BlobStats::BlobStat::recalculateSpaceMap() const auto & [total_size, valid_size] = smap->getSizes(); sm_total_size = total_size; sm_valid_size = valid_size; - sm_valid_rate = valid_size * 1.0 / total_size; + sm_valid_rate = total_size == 0 ? 0.0 : valid_size * 1.0 / total_size; recalculateCapacity(); } diff --git a/dbms/src/Storages/Page/V3/BlobStore.h b/dbms/src/Storages/Page/V3/BlobStore.h index e527eb0f3bf..16c775d0667 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.h +++ b/dbms/src/Storages/Page/V3/BlobStore.h @@ -107,7 +107,7 @@ class BlobStore : private Allocator UInt64 sm_max_caps = 0; UInt64 sm_total_size = 0; UInt64 sm_valid_size = 0; - double sm_valid_rate = 1.0; + double sm_valid_rate = 0.0; public: BlobStat(BlobFileId id_, SpaceMap::SpaceMapType sm_type, UInt64 sm_max_caps_, BlobStatType type_ = BlobStatType::NORMAL) From ca3e1c6be883b479f7169e5ce4a317266010e66c Mon Sep 17 00:00:00 2001 From: Lloyd-Pottiger <60744015+Lloyd-Pottiger@users.noreply.github.com> Date: Fri, 27 May 2022 19:24:48 +0800 Subject: [PATCH 070/127] Fix an invalid default value cause bootstrap failed (#4916) close pingcap/tiflash#3157 --- dbms/src/Storages/Transaction/TiDB.cpp | 29 ++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/dbms/src/Storages/Transaction/TiDB.cpp b/dbms/src/Storages/Transaction/TiDB.cpp index 763dcac39fc..3810e25372f 100644 --- a/dbms/src/Storages/Transaction/TiDB.cpp +++ b/dbms/src/Storages/Transaction/TiDB.cpp @@ -26,6 +26,8 @@ #include #include +#include + namespace DB { namespace ErrorCodes @@ -110,14 +112,28 @@ Field ColumnInfo::defaultValueToField() const } switch (tp) { - // TODO: Consider unsigned? // Integer Type. case TypeTiny: case TypeShort: case TypeLong: case TypeLongLong: case TypeInt24: - return value.convert(); + { + // In c++, cast a unsigned integer to signed integer will not change the value. + // like 9223372036854775808 which is larger than the maximum value of Int64, + // static_cast(static_cast(9223372036854775808)) == 9223372036854775808 + // so we don't need consider unsigned here. + try + { + return value.convert(); + } + catch (...) + { + // due to https://github.com/pingcap/tidb/issues/34881 + // we do this to avoid exception in older version of TiDB. + return static_cast(std::llround(value.convert())); + } + } case TypeBit: { // TODO: We shall use something like `orig_default_bit`, which will never change once created, @@ -615,6 +631,8 @@ catch (const Poco::Exception & e) /////////////////////// IndexColumnInfo::IndexColumnInfo(Poco::JSON::Object::Ptr json) + : offset() + , length() { deserialize(json); } @@ -664,6 +682,13 @@ catch (const Poco::Exception & e) /////////////////////// IndexInfo::IndexInfo(Poco::JSON::Object::Ptr json) + : id() + , state() + , index_type() + , is_unique() + , is_primary() + , is_invisible() + , is_global() { deserialize(json); } From d1ecebd92d8d88a224f28125d2c31c4f17d3c069 Mon Sep 17 00:00:00 2001 From: JaySon Date: Fri, 27 May 2022 19:58:48 +0800 Subject: [PATCH 071/127] PageStorage: Report the file usage of BlobStore (#4999) close pingcap/tiflash#4922 --- dbms/src/Interpreters/AsynchronousMetrics.cpp | 32 + dbms/src/Interpreters/AsynchronousMetrics.h | 5 + dbms/src/Server/Server.cpp | 11 +- dbms/src/Storages/DeltaMerge/StoragePool.h | 2 + dbms/src/Storages/Page/FileUsage.h | 29 + dbms/src/Storages/Page/PageStorage.cpp | 18 + dbms/src/Storages/Page/PageStorage.h | 9 + dbms/src/Storages/Page/Snapshot.h | 2 +- dbms/src/Storages/Page/V3/BlobStore.cpp | 45 +- dbms/src/Storages/Page/V3/BlobStore.h | 14 +- dbms/src/Storages/Page/V3/PageStorageImpl.cpp | 5 + dbms/src/Storages/Page/V3/PageStorageImpl.h | 2 + dbms/src/Storages/Transaction/KVStore.h | 5 + .../Storages/Transaction/RegionPersister.cpp | 6 + .../Storages/Transaction/RegionPersister.h | 3 + metrics/grafana/tiflash_summary.json | 986 ++++++++++-------- 16 files changed, 721 insertions(+), 453 deletions(-) create mode 100644 dbms/src/Storages/Page/FileUsage.h diff --git a/dbms/src/Interpreters/AsynchronousMetrics.cpp b/dbms/src/Interpreters/AsynchronousMetrics.cpp index e96d57e0370..8095fbb0e59 100644 --- a/dbms/src/Interpreters/AsynchronousMetrics.cpp +++ b/dbms/src/Interpreters/AsynchronousMetrics.cpp @@ -21,8 +21,12 @@ #include #include #include +#include #include +#include #include +#include +#include #include #include @@ -125,6 +129,26 @@ static void calculateMaxAndSum(Max & max, Sum & sum, T x) max = x; } +FileUsageStatistics AsynchronousMetrics::getPageStorageFileUsage() +{ + // Get from RegionPersister + auto & tmt = context.getTMTContext(); + auto & kvstore = tmt.getKVStore(); + FileUsageStatistics usage = kvstore->getFileUsageStatistics(); + + // Get the blob file status from all PS V3 instances + if (auto global_storage_pool = context.getGlobalStoragePool(); global_storage_pool != nullptr) + { + const auto log_usage = global_storage_pool->log_storage->getFileUsageStatistics(); + const auto meta_usage = global_storage_pool->meta_storage->getFileUsageStatistics(); + const auto data_usage = global_storage_pool->data_storage->getFileUsageStatistics(); + + usage.total_file_num += log_usage.total_file_num + meta_usage.total_file_num + data_usage.total_file_num; + usage.total_disk_size += log_usage.total_disk_size + meta_usage.total_disk_size + data_usage.total_disk_size; + usage.total_valid_size += log_usage.total_valid_size + meta_usage.total_valid_size + data_usage.total_valid_size; + } + return usage; +} void AsynchronousMetrics::update() { @@ -147,6 +171,7 @@ void AsynchronousMetrics::update() set("Uptime", context.getUptimeSeconds()); { + // Get the snapshot status from all delta tree tables auto databases = context.getDatabases(); double max_dt_stable_oldest_snapshot_lifetime = 0.0; @@ -177,6 +202,13 @@ void AsynchronousMetrics::update() set("MaxDTBackgroundTasksLength", max_dt_background_tasks_length); } + { + const FileUsageStatistics usage = getPageStorageFileUsage(); + set("BlobFileNums", usage.total_file_num); + set("BlobDiskBytes", usage.total_disk_size); + set("BlobValidBytes", usage.total_valid_size); + } + #if USE_TCMALLOC { /// tcmalloc related metrics. Remove if you switch to different allocator. diff --git a/dbms/src/Interpreters/AsynchronousMetrics.h b/dbms/src/Interpreters/AsynchronousMetrics.h index 5de328601a6..536e6a6b6f6 100644 --- a/dbms/src/Interpreters/AsynchronousMetrics.h +++ b/dbms/src/Interpreters/AsynchronousMetrics.h @@ -14,6 +14,8 @@ #pragma once +#include + #include #include #include @@ -47,6 +49,9 @@ class AsynchronousMetrics /// Returns copy of all values. Container getValues() const; +private: + FileUsageStatistics getPageStorageFileUsage(); + private: Context & context; diff --git a/dbms/src/Server/Server.cpp b/dbms/src/Server/Server.cpp index 705b8a533f3..fcf820eb958 100644 --- a/dbms/src/Server/Server.cpp +++ b/dbms/src/Server/Server.cpp @@ -464,7 +464,7 @@ struct RaftStoreProxyRunner : boost::noncopyable } RunRaftStoreProxyParms parms; - pthread_t thread; + pthread_t thread{}; Poco::Logger * log; }; @@ -477,6 +477,11 @@ void initStores(Context & global_context, Poco::Logger * log, bool lazily_init_s int err_cnt = 0; for (auto & [table_id, storage] : storages) { + // This will skip the init of storages that do not contain any data. TiFlash now sync the schema and + // create all tables regardless the table have define TiFlash replica or not, so there may be lots + // of empty tables in TiFlash. + // Note that we still need to init stores that contains data (defined by the stable dir of this storage + // is exist), or the data used size reported to PD is not correct. try { init_cnt += storage->initStoreIfDataDirExist() ? 1 : 0; @@ -498,6 +503,7 @@ void initStores(Context & global_context, Poco::Logger * log, bool lazily_init_s if (lazily_init_store) { LOG_FMT_INFO(log, "Lazily init store."); + // apply the inited in another thread to shorten the start time of TiFlash std::thread(do_init_stores).detach(); } else @@ -1149,7 +1155,7 @@ int Server::main(const std::vector & /*args*/) /// Try to increase limit on number of open files. { - rlimit rlim; + rlimit rlim{}; if (getrlimit(RLIMIT_NOFILE, &rlim)) throw Poco::Exception("Cannot getrlimit"); @@ -1437,6 +1443,7 @@ int Server::main(const std::vector & /*args*/) } /// This object will periodically calculate some metrics. + /// should init after `createTMTContext` cause we collect some data from the TiFlash context object. AsynchronousMetrics async_metrics(*global_context); attachSystemTablesAsync(*global_context->getDatabase("system"), async_metrics); diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.h b/dbms/src/Storages/DeltaMerge/StoragePool.h index d05454a5431..77684ea46cb 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.h +++ b/dbms/src/Storages/DeltaMerge/StoragePool.h @@ -28,6 +28,7 @@ struct Settings; class Context; class StoragePathPool; class StableDiskDelegator; +class AsynchronousMetrics; namespace DM { @@ -50,6 +51,7 @@ class GlobalStoragePool : private boost::noncopyable void restore(); friend class StoragePool; + friend class ::DB::AsynchronousMetrics; // GC immediately // Only used on dbgFuncMisc diff --git a/dbms/src/Storages/Page/FileUsage.h b/dbms/src/Storages/Page/FileUsage.h new file mode 100644 index 00000000000..6319f4a4acf --- /dev/null +++ b/dbms/src/Storages/Page/FileUsage.h @@ -0,0 +1,29 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once +#include + +#include + +namespace DB +{ +struct FileUsageStatistics +{ + size_t total_disk_size = 0; + size_t total_valid_size = 0; + size_t total_file_num = 0; +}; + +} // namespace DB diff --git a/dbms/src/Storages/Page/PageStorage.cpp b/dbms/src/Storages/Page/PageStorage.cpp index 6e1addae093..d8b767a6c15 100644 --- a/dbms/src/Storages/Page/PageStorage.cpp +++ b/dbms/src/Storages/Page/PageStorage.cpp @@ -66,6 +66,8 @@ class PageReaderImpl : private boost::noncopyable // Get some statistics of all living snapshots and the oldest living snapshot. virtual SnapshotsStatistics getSnapshotsStat() const = 0; + virtual FileUsageStatistics getFileUsageStatistics() const = 0; + virtual void traverse(const std::function & acceptor, bool only_v2, bool only_v3) const = 0; }; @@ -137,6 +139,11 @@ class PageReaderImplNormal : public PageReaderImpl storage->traverse(acceptor, nullptr); } + FileUsageStatistics getFileUsageStatistics() const override + { + return storage->getFileUsageStatistics(); + } + private: NamespaceId ns_id; PageStoragePtr storage; @@ -294,6 +301,11 @@ class PageReaderImplMixed : public PageReaderImpl return statistics_total; } + FileUsageStatistics getFileUsageStatistics() const override + { + return storage_v3->getFileUsageStatistics(); + } + void traverse(const std::function & acceptor, bool only_v2, bool only_v3) const override { // Used by RegionPersister::restore @@ -424,6 +436,12 @@ SnapshotsStatistics PageReader::getSnapshotsStat() const return impl->getSnapshotsStat(); } + +FileUsageStatistics PageReader::getFileUsageStatistics() const +{ + return impl->getFileUsageStatistics(); +} + void PageReader::traverse(const std::function & acceptor, bool only_v2, bool only_v3) const { impl->traverse(acceptor, only_v2, only_v3); diff --git a/dbms/src/Storages/Page/PageStorage.h b/dbms/src/Storages/Page/PageStorage.h index cec6e297d0e..0059c0570c1 100644 --- a/dbms/src/Storages/Page/PageStorage.h +++ b/dbms/src/Storages/Page/PageStorage.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -251,6 +252,12 @@ class PageStorage : private boost::noncopyable // Get some statistics of all living snapshots and the oldest living snapshot. virtual SnapshotsStatistics getSnapshotsStat() const = 0; + virtual FileUsageStatistics getFileUsageStatistics() const + { + // return all zeros by default + return FileUsageStatistics{}; + } + virtual size_t getNumberOfPages() = 0; virtual std::set getAliveExternalPageIds(NamespaceId ns_id) = 0; @@ -380,6 +387,8 @@ class PageReader : private boost::noncopyable // Get some statistics of all living snapshots and the oldest living snapshot. SnapshotsStatistics getSnapshotsStat() const; + FileUsageStatistics getFileUsageStatistics() const; + void traverse(const std::function & acceptor, bool only_v2 = false, bool only_v3 = false) const; private: diff --git a/dbms/src/Storages/Page/Snapshot.h b/dbms/src/Storages/Page/Snapshot.h index 77e68f1b054..073fc0a2830 100644 --- a/dbms/src/Storages/Page/Snapshot.h +++ b/dbms/src/Storages/Page/Snapshot.h @@ -61,7 +61,7 @@ class PageStorageSnapshotMixed : public PageStorageSnapshot }; using PageStorageSnapshotMixedPtr = std::shared_ptr; -static inline PageStorageSnapshotMixedPtr +inline PageStorageSnapshotMixedPtr toConcreteMixedSnapshot(const PageStorageSnapshotPtr & ptr) { return std::static_pointer_cast(ptr); diff --git a/dbms/src/Storages/Page/V3/BlobStore.cpp b/dbms/src/Storages/Page/V3/BlobStore.cpp index f03bc5bcf73..d5f71841b91 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.cpp +++ b/dbms/src/Storages/Page/V3/BlobStore.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -66,7 +67,7 @@ using ChecksumClass = Digest::CRC64; * BlobStore methods * *********************/ -BlobStore::BlobStore(String storage_name, const FileProviderPtr & file_provider_, PSDiskDelegatorPtr delegator_, BlobStore::Config config_) +BlobStore::BlobStore(String storage_name, const FileProviderPtr & file_provider_, PSDiskDelegatorPtr delegator_, const BlobStore::Config & config_) : delegator(std::move(delegator_)) , file_provider(file_provider_) , config(config_) @@ -115,6 +116,38 @@ void BlobStore::registerPaths() } } +FileUsageStatistics BlobStore::getFileUsageStatistics() const +{ + FileUsageStatistics usage; + + // Get a copy of stats map to avoid the big lock on stats map + const auto stats_list = blob_stats.getStats(); + + for (const auto & [path, stats] : stats_list) + { + (void)path; + for (const auto & stat : stats) + { + // We can access to these type without any locking. + if (stat->isReadOnly() || stat->isBigBlob()) + { + usage.total_disk_size += stat->sm_total_size; + usage.total_valid_size += stat->sm_valid_size; + } + else + { + // Else the stat may being updated, acquire a lock to avoid data race. + auto lock = stat->lock(); + usage.total_disk_size += stat->sm_total_size; + usage.total_valid_size += stat->sm_valid_size; + } + } + usage.total_file_num += stats.size(); + } + + return usage; +} + PageEntriesEdit BlobStore::handleLargeWrite(DB::WriteBatch & wb, const WriteLimiterPtr & write_limiter) { auto ns_id = wb.getNamespaceId(); @@ -872,6 +905,7 @@ struct BlobStoreGCInfo std::vector BlobStore::getGCStats() { + // Get a copy of stats map to avoid the big lock on stats map const auto stats_list = blob_stats.getStats(); std::vector blob_need_gc; BlobStoreGCInfo blobstore_gc_info; @@ -1211,7 +1245,7 @@ BlobStatPtr BlobStore::BlobStats::createStat(BlobFileId blob_file_id, const std: // New blob file id won't bigger than roll_id if (blob_file_id > roll_id) { - throw Exception(fmt::format("BlobStats won't create [blob_id={}], which is bigger than [RollMaxId={}]", + throw Exception(fmt::format("BlobStats won't create [blob_id={}], which is bigger than [roll_id={}]", blob_file_id, roll_id), ErrorCodes::LOGICAL_ERROR); @@ -1274,8 +1308,7 @@ BlobStatPtr BlobStore::BlobStats::createBigPageStatNotChecking(BlobFileId blob_f BlobStatPtr stat = std::make_shared( blob_file_id, SpaceMap::SpaceMapType::SMAP64_BIG, - config.file_limit_size, - BlobStatType::BIG_BLOB); + config.file_limit_size); PageFileIdAndLevel id_lvl{blob_file_id, 0}; stats_map[delegator->choosePath(id_lvl)].emplace_back(stat); @@ -1453,7 +1486,7 @@ bool BlobStore::BlobStats::BlobStat::removePosFromStat(BlobFileOffset offset, si if (!smap->markFree(offset, buf_size)) { smap->logDebugString(); - throw Exception(fmt::format("Remove postion from BlobStat failed, [offset={} , buf_size={}, blob_id={}] is invalid.", + throw Exception(fmt::format("Remove postion from BlobStat failed, invalid position [offset={}] [buf_size={}] [blob_id={}]", offset, buf_size, id), @@ -1470,7 +1503,7 @@ void BlobStore::BlobStats::BlobStat::restoreSpaceMap(BlobFileOffset offset, size if (!smap->markUsed(offset, buf_size)) { smap->logDebugString(); - throw Exception(fmt::format("Restore postion from BlobStat failed, [offset={}] [buf_size={}] [blob_id={}] is used or subspan is used", + throw Exception(fmt::format("Restore postion from BlobStat failed, the space/subspace is already being used [offset={}] [buf_size={}] [blob_id={}]", offset, buf_size, id), diff --git a/dbms/src/Storages/Page/V3/BlobStore.h b/dbms/src/Storages/Page/V3/BlobStore.h index 16c775d0667..5a3e98400d1 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.h +++ b/dbms/src/Storages/Page/V3/BlobStore.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -110,12 +111,17 @@ class BlobStore : private Allocator double sm_valid_rate = 0.0; public: - BlobStat(BlobFileId id_, SpaceMap::SpaceMapType sm_type, UInt64 sm_max_caps_, BlobStatType type_ = BlobStatType::NORMAL) + BlobStat(BlobFileId id_, SpaceMap::SpaceMapType sm_type, UInt64 sm_max_caps_) : id(id_) - , type(type_) + , type(BlobStatType::NORMAL) , smap(SpaceMap::createSpaceMap(sm_type, 0, sm_max_caps_)) , sm_max_caps(sm_max_caps_) { + if (sm_type == SpaceMap::SpaceMapType::SMAP64_BIG) + { + type = BlobStatType::BIG_BLOB; + } + // Won't create read-only blob by default. assert(type != BlobStatType::READ_ONLY); } @@ -246,10 +252,12 @@ class BlobStore : private Allocator std::map> stats_map; }; - BlobStore(String storage_name, const FileProviderPtr & file_provider_, PSDiskDelegatorPtr delegator_, BlobStore::Config config); + BlobStore(String storage_name, const FileProviderPtr & file_provider_, PSDiskDelegatorPtr delegator_, const BlobStore::Config & config); void registerPaths(); + FileUsageStatistics getFileUsageStatistics() const; + std::vector getGCStats(); PageEntriesEdit gc(std::map & entries_need_gc, diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp index cfa07199637..a568bb5087f 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp @@ -80,6 +80,11 @@ DB::PageStorage::SnapshotPtr PageStorageImpl::getSnapshot(const String & tracing return page_directory->createSnapshot(tracing_id); } +FileUsageStatistics PageStorageImpl::getFileUsageStatistics() const +{ + return blob_store.getFileUsageStatistics(); +} + SnapshotsStatistics PageStorageImpl::getSnapshotsStat() const { return page_directory->getSnapshotsStat(); diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.h b/dbms/src/Storages/Page/V3/PageStorageImpl.h index 50d160e81da..f49601ce2ad 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.h +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.h @@ -72,6 +72,8 @@ class PageStorageImpl : public DB::PageStorage SnapshotsStatistics getSnapshotsStat() const override; + FileUsageStatistics getFileUsageStatistics() const override; + size_t getNumberOfPages() override; std::set getAliveExternalPageIds(NamespaceId ns_id) override; diff --git a/dbms/src/Storages/Transaction/KVStore.h b/dbms/src/Storages/Transaction/KVStore.h index 9d30f249e60..ef851d67958 100644 --- a/dbms/src/Storages/Transaction/KVStore.h +++ b/dbms/src/Storages/Transaction/KVStore.h @@ -162,6 +162,11 @@ class KVStore final : private boost::noncopyable ~KVStore(); + FileUsageStatistics getFileUsageStatistics() const + { + return region_persister.getFileUsageStatistics(); + } + private: friend class MockTiDB; friend struct MockTiDBTable; diff --git a/dbms/src/Storages/Transaction/RegionPersister.cpp b/dbms/src/Storages/Transaction/RegionPersister.cpp index c3db88daece..7ce52c6caa1 100644 --- a/dbms/src/Storages/Transaction/RegionPersister.cpp +++ b/dbms/src/Storages/Transaction/RegionPersister.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -379,4 +380,9 @@ bool RegionPersister::gc() return stable_page_storage->gc(); } +FileUsageStatistics RegionPersister::getFileUsageStatistics() const +{ + return page_reader->getFileUsageStatistics(); +} + } // namespace DB diff --git a/dbms/src/Storages/Transaction/RegionPersister.h b/dbms/src/Storages/Transaction/RegionPersister.h index f2828add202..a6b400345f8 100644 --- a/dbms/src/Storages/Transaction/RegionPersister.h +++ b/dbms/src/Storages/Transaction/RegionPersister.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -57,6 +58,8 @@ class RegionPersister final : private boost::noncopyable PageStorage::Config getPageStorageSettings() const; + FileUsageStatistics getFileUsageStatistics() const; + #ifndef DBMS_PUBLIC_GTEST private: #endif diff --git a/metrics/grafana/tiflash_summary.json b/metrics/grafana/tiflash_summary.json index a6d1abac46f..f899a47ed10 100644 --- a/metrics/grafana/tiflash_summary.json +++ b/metrics/grafana/tiflash_summary.json @@ -33,12 +33,6 @@ "id": "prometheus", "name": "Prometheus", "version": "1.0.0" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" } ], "annotations": { @@ -58,7 +52,7 @@ "gnetId": null, "graphTooltip": 1, "id": null, - "iteration": 1652861766192, + "iteration": 1653635389238, "links": [], "panels": [ { @@ -5336,14 +5330,30 @@ "align": false, "alignLevel": null } - }, + } + ], + "repeat": null, + "title": "Storage", + "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 5 + }, + "id": 119, + "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "${DS_TEST-CLUSTER}", - "description": "The states of BlobStore (an internal component of storage engine)", + "description": "The states of BlobStore (an internal component of PageStorage)", "fieldConfig": { "defaults": {}, "overrides": [] @@ -5354,20 +5364,20 @@ "h": 8, "w": 12, "x": 0, - "y": 69 + "y": 6 }, "hiddenSeries": false, "id": 85, "legend": { - "alignAsTable": false, + "alignAsTable": true, "avg": false, - "current": false, + "current": true, "max": false, "min": false, - "rightSide": false, + "rightSide": true, "show": true, "total": false, - "values": false + "values": true }, "lines": true, "linewidth": 1, @@ -5383,11 +5393,11 @@ "renderer": "flot", "seriesOverrides": [ { - "alias": "/^BlobAllocated/", + "alias": "/^allocated/", "yaxis": 1 }, { - "alias": "/^BlobExpandRate/", + "alias": "/^expand_rate/", "yaxis": 2 } ], @@ -5402,7 +5412,7 @@ "hide": false, "interval": "", "intervalFactor": 2, - "legendFormat": "BlobAllocated-{{instance}}", + "legendFormat": "allocated-{{instance}}", "refId": "A" }, { @@ -5412,7 +5422,7 @@ "hide": false, "interval": "", "intervalFactor": 1, - "legendFormat": "BlobExpandRate-{{instance}}", + "legendFormat": "expand_rate-{{instance}}", "refId": "B" } ], @@ -5420,7 +5430,7 @@ "timeFrom": null, "timeRegions": [], "timeShift": null, - "title": "BlobStore Status", + "title": "PageStorage Blob Status", "tooltip": { "shared": true, "sort": 0, @@ -5457,23 +5467,7 @@ "align": false, "alignLevel": null } - } - ], - "repeat": null, - "title": "Storage", - "type": "row" - }, - { - "collapsed": true, - "datasource": null, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 5 - }, - "id": 64, - "panels": [ + }, { "aliasColors": {}, "bars": false, @@ -5481,42 +5475,40 @@ "dashes": false, "datasource": "${DS_TEST-CLUSTER}", "decimals": 1, - "description": "The throughput of write and delta's background management", + "description": "The disk usage of PageStorage instances in each TiFlash node", + "editable": true, + "error": false, "fieldConfig": { "defaults": {}, "overrides": [] }, - "fill": 1, + "fill": 0, "fillGradient": 0, + "grid": {}, "gridPos": { - "h": 9, - "w": 24, - "x": 0, + "h": 8, + "w": 12, + "x": 12, "y": 6 }, - "height": "", "hiddenSeries": false, - "id": 70, + "id": 128, "legend": { "alignAsTable": true, "avg": false, "current": true, - "hideEmpty": false, - "hideZero": false, - "max": true, + "max": false, "min": false, "rightSide": true, "show": true, "sideWidth": null, - "sort": "current", - "sortDesc": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], - "nullPointMode": "null", + "nullPointMode": "null as zero", "options": { "alertThreshold": true }, @@ -5525,11 +5517,14 @@ "pointradius": 5, "points": false, "renderer": "flot", - "repeatedByRow": true, "seriesOverrides": [ { - "alias": "/total/", + "alias": "/^valid_rate/", "yaxis": 2 + }, + { + "alias": "/size/", + "linewidth": 3 } ], "spaceLength": 10, @@ -5538,47 +5533,51 @@ "targets": [ { "exemplar": true, - "expr": "sum(rate(tiflash_storage_throughput_bytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\", type=~\"write|ingest\"}[1m]))", - "format": "time_series", + "expr": "tiflash_system_asynchronous_metric_BlobDiskBytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}", "hide": false, "interval": "", - "intervalFactor": 1, - "legendFormat": "throughput_write+ingest", - "refId": "A", - "step": 10 + "intervalFactor": 2, + "legendFormat": "disk_size-{{instance}}", + "refId": "A" }, { - "expr": "sum(rate(tiflash_storage_throughput_bytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\", type!~\"write|ingest\"}[1m]))", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "throughput_delta-management", + "exemplar": true, + "expr": "sum(tiflash_system_asynchronous_metric_BlobValidBytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}) by (instance)", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "valid_size-{{instance}}", "refId": "B" }, { "exemplar": true, - "expr": "sum(tiflash_storage_throughput_bytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\", type=~\"write|ingest\"})", - "format": "time_series", + "expr": "sum((tiflash_system_asynchronous_metric_BlobValidBytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}) / (tiflash_system_asynchronous_metric_BlobDiskBytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"})) by (instance)", + "hide": false, "interval": "", - "intervalFactor": 1, - "legendFormat": "total_write+ingest", + "legendFormat": "valid_rate-{{instance}}", "refId": "C" }, { - "expr": "sum(tiflash_storage_throughput_bytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\", type!~\"write|ingest\"})", + "exemplar": true, + "expr": "sum(tiflash_system_asynchronous_metric_BlobFileNums{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}) by (instance)", "format": "time_series", - "intervalFactor": 1, - "legendFormat": "total_delta-management", - "refId": "D" + "hide": true, + "interval": "", + "intervalFactor": 2, + "legendFormat": "num_file-{{instance}}", + "refId": "E", + "step": 10 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, - "title": "Write & Delta Management Throughput", + "title": "PageStorage Disk Usage", "tooltip": { + "msResolution": false, "shared": true, - "sort": 0, + "sort": 2, "value_type": "individual" }, "type": "graph", @@ -5591,7 +5590,7 @@ }, "yaxes": [ { - "format": "binBps", + "format": "bytes", "label": null, "logBase": 1, "max": null, @@ -5599,11 +5598,11 @@ "show": true }, { - "format": "bytes", + "format": "percentunit", "label": null, "logBase": 1, - "max": null, - "min": null, + "max": "1.1", + "min": "0", "show": true } ], @@ -5618,29 +5617,34 @@ "dashLength": 10, "dashes": false, "datasource": "${DS_TEST-CLUSTER}", - "description": "The stall duration of write and delete range", + "decimals": 1, + "description": "The number of files of PageStorage instances in each TiFlash node", + "editable": true, + "error": false, "fieldConfig": { "defaults": {}, "overrides": [] }, - "fill": 1, + "fill": 0, "fillGradient": 0, + "grid": {}, "gridPos": { "h": 8, - "w": 24, + "w": 12, "x": 0, - "y": 15 + "y": 14 }, "hiddenSeries": false, - "id": 62, + "id": 129, "legend": { "alignAsTable": true, "avg": false, "current": true, - "max": true, + "max": false, "min": false, "rightSide": true, "show": true, + "sideWidth": null, "total": false, "values": true }, @@ -5656,40 +5660,32 @@ "pointradius": 5, "points": false, "renderer": "flot", - "seriesOverrides": [ - { - "alias": "99-delta_merge", - "yaxis": 2 - } - ], + "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { - "expr": "histogram_quantile(0.99, sum(rate(tiflash_storage_write_stall_duration_seconds_bucket{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m])) by (le, type, instance))", - "format": "time_series", - "hide": true, - "intervalFactor": 1, - "legendFormat": "99-{{type}}-{{instance}}", - "refId": "B" - }, - { - "expr": "histogram_quantile(1, sum(rate(tiflash_storage_write_stall_duration_seconds_bucket{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m])) by (le, type, instance))", + "exemplar": true, + "expr": "sum(tiflash_system_asynchronous_metric_BlobFileNums{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}) by (instance)", "format": "time_series", - "intervalFactor": 1, - "legendFormat": "max-{{type}}-{{instance}}", - "refId": "A" + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "num_file-{{instance}}", + "refId": "A", + "step": 10 } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, - "title": "Write Stall Duration", + "title": "PageStorage File Num", "tooltip": { + "msResolution": false, "shared": true, - "sort": 0, + "sort": 2, "value_type": "individual" }, "type": "graph", @@ -5702,7 +5698,7 @@ }, "yaxes": [ { - "format": "s", + "format": "short", "label": null, "logBase": 1, "max": null, @@ -5710,10 +5706,10 @@ "show": true }, { - "format": "s", + "format": "percentunit", "label": null, "logBase": 1, - "max": null, + "max": "1.1", "min": "0", "show": true } @@ -5730,29 +5726,29 @@ "dashes": false, "datasource": "${DS_TEST-CLUSTER}", "decimals": 1, - "description": "The throughput of write by instance", + "description": "The number of tables running under different mode in DeltaTree", + "editable": true, + "error": false, "fieldConfig": { "defaults": {}, "overrides": [] }, - "fill": 1, + "fill": 0, "fillGradient": 0, + "grid": {}, "gridPos": { - "h": 9, - "w": 24, - "x": 0, - "y": 23 + "h": 8, + "w": 12, + "x": 12, + "y": 14 }, - "height": "", "hiddenSeries": false, - "id": 89, + "id": 123, "legend": { "alignAsTable": true, "avg": false, "current": true, - "hideEmpty": false, - "hideZero": false, - "max": true, + "max": false, "min": false, "rightSide": true, "show": true, @@ -5765,7 +5761,7 @@ "lines": true, "linewidth": 1, "links": [], - "nullPointMode": "null", + "nullPointMode": "null as zero", "options": { "alertThreshold": true }, @@ -5774,43 +5770,45 @@ "pointradius": 5, "points": false, "renderer": "flot", - "repeatedByRow": true, - "seriesOverrides": [ - { - "alias": "/total/", - "yaxis": 2 - } - ], + "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "exemplar": true, - "expr": "sum(rate(tiflash_storage_throughput_bytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\", type=~\"write\"}[1m])) by (instance)", + "expr": "sum(tiflash_system_current_metric_StoragePoolV2Only{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}) by (instance)", "format": "time_series", - "hide": false, "interval": "", - "intervalFactor": 1, - "legendFormat": "throughput_write-{{instance}}", + "intervalFactor": 2, + "legendFormat": "{{instance}}-OnlyV2", "refId": "A", "step": 10 }, { "exemplar": true, - "expr": "sum(rate(tiflash_storage_throughput_bytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\", type=~\"ingest\"}[1m])) by (instance)", + "expr": "sum(tiflash_system_current_metric_StoragePoolV3Only{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}) by (instance)", "hide": false, "interval": "", - "legendFormat": "throughput_ingest-{{instance}}", + "legendFormat": "{{instance}}-OnlyV3", "refId": "B" + }, + { + "exemplar": true, + "expr": "sum(tiflash_system_current_metric_StoragePoolMixMode{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}) by (instance)", + "hide": false, + "interval": "", + "legendFormat": "{{instance}}-MixMode", + "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, - "title": "Write Throughput By Instance", + "title": "StoragePool Runmode", "tooltip": { + "msResolution": false, "shared": true, "sort": 0, "value_type": "individual" @@ -5825,7 +5823,7 @@ }, "yaxes": [ { - "format": "binBps", + "format": "short", "label": null, "logBase": 1, "max": null, @@ -5833,7 +5831,7 @@ "show": true }, { - "format": "bytes", + "format": "short", "label": null, "logBase": 1, "max": null, @@ -5845,43 +5843,65 @@ "align": false, "alignLevel": null } - }, + } + ], + "title": "StoragePool", + "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 64, + "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "${DS_TEST-CLUSTER}", - "description": "The total count of different kinds of commands received", + "decimals": 1, + "description": "The throughput of write and delta's background management", "fieldConfig": { "defaults": {}, "overrides": [] }, - "fill": 0, + "fill": 1, "fillGradient": 0, "gridPos": { "h": 9, "w": 24, "x": 0, - "y": 32 + "y": 71 }, + "height": "", "hiddenSeries": false, - "id": 90, + "id": 70, "legend": { "alignAsTable": true, "avg": false, "current": true, + "hideEmpty": false, + "hideZero": false, "max": true, "min": false, "rightSide": true, "show": true, + "sideWidth": null, + "sort": "current", + "sortDesc": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "links": [], - "nullPointMode": "null as zero", + "nullPointMode": "null", "options": { "alertThreshold": true }, @@ -5890,9 +5910,10 @@ "pointradius": 5, "points": false, "renderer": "flot", + "repeatedByRow": true, "seriesOverrides": [ { - "alias": "/delete_range|ingest/", + "alias": "/total/", "yaxis": 2 } ], @@ -5901,8 +5922,372 @@ "steppedLine": false, "targets": [ { - "expr": "sum(rate(tiflash_system_profile_event_DMWriteBlock{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m])) by (instance, type)", - "format": "time_series", + "exemplar": true, + "expr": "sum(rate(tiflash_storage_throughput_bytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\", type=~\"write|ingest\"}[1m]))", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "throughput_write+ingest", + "refId": "A", + "step": 10 + }, + { + "expr": "sum(rate(tiflash_storage_throughput_bytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\", type!~\"write|ingest\"}[1m]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "throughput_delta-management", + "refId": "B" + }, + { + "exemplar": true, + "expr": "sum(tiflash_storage_throughput_bytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\", type=~\"write|ingest\"})", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "total_write+ingest", + "refId": "C" + }, + { + "expr": "sum(tiflash_storage_throughput_bytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\", type!~\"write|ingest\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "total_delta-management", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Write & Delta Management Throughput", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "binBps", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_TEST-CLUSTER}", + "description": "The stall duration of write and delete range", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 80 + }, + "hiddenSeries": false, + "id": 62, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.11", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "99-delta_merge", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "histogram_quantile(0.99, sum(rate(tiflash_storage_write_stall_duration_seconds_bucket{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m])) by (le, type, instance))", + "format": "time_series", + "hide": true, + "intervalFactor": 1, + "legendFormat": "99-{{type}}-{{instance}}", + "refId": "B" + }, + { + "expr": "histogram_quantile(1, sum(rate(tiflash_storage_write_stall_duration_seconds_bucket{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m])) by (le, type, instance))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "max-{{type}}-{{instance}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Write Stall Duration", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_TEST-CLUSTER}", + "decimals": 1, + "description": "The throughput of write by instance", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 88 + }, + "height": "", + "hiddenSeries": false, + "id": 89, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": null, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.11", + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeatedByRow": true, + "seriesOverrides": [ + { + "alias": "/total/", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(tiflash_storage_throughput_bytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\", type=~\"write\"}[1m])) by (instance)", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "throughput_write-{{instance}}", + "refId": "A", + "step": 10 + }, + { + "exemplar": true, + "expr": "sum(rate(tiflash_storage_throughput_bytes{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\", type=~\"ingest\"}[1m])) by (instance)", + "hide": false, + "interval": "", + "legendFormat": "throughput_ingest-{{instance}}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Write Throughput By Instance", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "binBps", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_TEST-CLUSTER}", + "description": "The total count of different kinds of commands received", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 97 + }, + "hiddenSeries": false, + "id": 90, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.11", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "/delete_range|ingest/", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(tiflash_system_profile_event_DMWriteBlock{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}[1m])) by (instance, type)", + "format": "time_series", "hide": false, "intervalFactor": 1, "legendFormat": "write block-{{instance}}", @@ -5970,7 +6355,7 @@ "h": 1, "w": 24, "x": 0, - "y": 6 + "y": 7 }, "id": 34, "panels": [ @@ -5990,7 +6375,7 @@ "h": 7, "w": 12, "x": 0, - "y": 7 + "y": 8 }, "hiddenSeries": false, "id": 35, @@ -6088,7 +6473,7 @@ "h": 7, "w": 12, "x": 12, - "y": 7 + "y": 8 }, "hiddenSeries": false, "id": 36, @@ -6206,7 +6591,7 @@ "h": 7, "w": 12, "x": 0, - "y": 14 + "y": 15 }, "hiddenSeries": false, "id": 37, @@ -6340,7 +6725,7 @@ "h": 7, "w": 12, "x": 12, - "y": 14 + "y": 15 }, "hiddenSeries": false, "id": 75, @@ -6444,7 +6829,7 @@ "h": 7, "w": 24, "x": 0, - "y": 21 + "y": 22 }, "hiddenSeries": false, "id": 82, @@ -6597,7 +6982,7 @@ "h": 7, "w": 12, "x": 0, - "y": 28 + "y": 29 }, "heatmap": {}, "hideZeroBuckets": true, @@ -6667,7 +7052,7 @@ "h": 7, "w": 12, "x": 12, - "y": 28 + "y": 29 }, "heatmap": {}, "hideZeroBuckets": true, @@ -6737,7 +7122,7 @@ "h": 7, "w": 12, "x": 0, - "y": 35 + "y": 36 }, "heatmap": {}, "hideZeroBuckets": true, @@ -6807,7 +7192,7 @@ "h": 7, "w": 12, "x": 12, - "y": 35 + "y": 36 }, "heatmap": {}, "hideZeroBuckets": true, @@ -6871,7 +7256,7 @@ "h": 7, "w": 24, "x": 0, - "y": 42 + "y": 43 }, "height": "", "hiddenSeries": false, @@ -6985,7 +7370,7 @@ "h": 7, "w": 12, "x": 0, - "y": 49 + "y": 50 }, "heatmap": {}, "hideZeroBuckets": true, @@ -7054,7 +7439,7 @@ "h": 7, "w": 12, "x": 12, - "y": 49 + "y": 50 }, "heatmap": {}, "hideZeroBuckets": true, @@ -7124,7 +7509,7 @@ "h": 7, "w": 12, "x": 0, - "y": 56 + "y": 57 }, "heatmap": {}, "hideZeroBuckets": true, @@ -7194,7 +7579,7 @@ "h": 7, "w": 12, "x": 12, - "y": 56 + "y": 57 }, "heatmap": {}, "hideZeroBuckets": true, @@ -7264,7 +7649,7 @@ "h": 7, "w": 12, "x": 0, - "y": 63 + "y": 64 }, "heatmap": {}, "hideZeroBuckets": true, @@ -7330,7 +7715,7 @@ "h": 7, "w": 12, "x": 12, - "y": 63 + "y": 64 }, "hiddenSeries": false, "id": 91, @@ -7453,7 +7838,7 @@ "h": 1, "w": 24, "x": 0, - "y": 7 + "y": 8 }, "id": 95, "panels": [ @@ -7675,290 +8060,9 @@ ], "title": "Rough Set Filter Rate Histogram", "type": "row" - }, - { - "collapsed": true, - "datasource": null, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 8 - }, - "id": 119, - "panels": [ - { - "datasource": "${DS_TEST-CLUSTER}", - "description": "The Global StoragePool and KVStore Runmode", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "axisSoftMax": 5, - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "graph": false, - "legend": false, - "tooltip": false - }, - "lineInterpolation": "linear", - "lineStyle": { - "fill": "solid" - }, - "lineWidth": 1, - "pointSize": 11, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false - }, - "decimals": 0, - "mappings": [ - { - "from": "", - "id": 1, - "text": "ONLY_V2", - "to": "", - "type": 1, - "value": "1" - }, - { - "from": "", - "id": 2, - "text": "ONLY_V3", - "to": "", - "type": 1, - "value": "2" - }, - { - "from": "", - "id": 3, - "text": "MIX_MODE", - "to": "", - "type": 1, - "value": "3" - }, - { - "from": "", - "id": 4, - "text": " ", - "to": "", - "type": 1, - "value": "4" - }, - { - "from": "", - "id": 5, - "text": " ", - "to": "", - "type": 1, - "value": "5" - } - ], - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 9 - }, - "id": 126, - "links": [], - "options": { - "graph": {}, - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "right" - }, - "tooltipOptions": { - "mode": "multi" - } - }, - "pluginVersion": "7.5.11", - "targets": [ - { - "exemplar": true, - "expr": "tiflash_system_current_metric_GlobalStorageRunMode{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{instance}}-GlobalRunMode", - "refId": "A", - "step": 10 - }, - { - "exemplar": false, - "expr": "tiflash_system_current_metric_RegionPersisterRunMode{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}", - "hide": false, - "interval": "", - "legendFormat": "{{instance}}-KVStoreRunMode", - "refId": "B" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Global Runmode", - "type": "timeseries" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_TEST-CLUSTER}", - "decimals": 1, - "description": "The StoragePool Runmode in DeltaMerge Storage", - "editable": true, - "error": false, - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 0, - "fillGradient": 0, - "grid": {}, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 9 - }, - "hiddenSeries": false, - "id": 123, - "legend": { - "alignAsTable": true, - "avg": false, - "current": true, - "max": false, - "min": false, - "rightSide": true, - "show": true, - "sideWidth": null, - "sort": "current", - "sortDesc": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null as zero", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "7.5.11", - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "exemplar": true, - "expr": "sum(tiflash_system_current_metric_StoragePoolV2Only{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}) by (instance)", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{instance}}-OnlyV2", - "refId": "A", - "step": 10 - }, - { - "exemplar": true, - "expr": "sum(tiflash_system_current_metric_StoragePoolV3Only{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}) by (instance)", - "hide": false, - "interval": "", - "legendFormat": "{{instance}}-OnlyV3", - "refId": "B" - }, - { - "exemplar": true, - "expr": "sum(tiflash_system_current_metric_StoragePoolMixMode{k8s_cluster=\"$k8s_cluster\", tidb_cluster=\"$tidb_cluster\", instance=~\"$instance\"}) by (instance)", - "hide": false, - "interval": "", - "legendFormat": "{{instance}}-MixMode", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "StoragePool Runmode", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "title": "StoragePool", - "type": "row" } ], - "refresh": false, + "refresh": "30s", "schemaVersion": 27, "style": "dark", "tags": [], From 7dff46338e179a047127abb151d2db586a5b9cce Mon Sep 17 00:00:00 2001 From: hehechen Date: Fri, 27 May 2022 20:38:50 +0800 Subject: [PATCH 072/127] Fix unstable ut in the master branch (#5024) close pingcap/tiflash#5023 --- dbms/src/TestUtils/TiFlashTestEnv.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbms/src/TestUtils/TiFlashTestEnv.cpp b/dbms/src/TestUtils/TiFlashTestEnv.cpp index 264fd6009a3..bd05e5826db 100644 --- a/dbms/src/TestUtils/TiFlashTestEnv.cpp +++ b/dbms/src/TestUtils/TiFlashTestEnv.cpp @@ -88,7 +88,7 @@ Context TiFlashTestEnv::getContext(const DB::Settings & settings, Strings testda Context context = *global_context; context.setGlobalContext(*global_context); // Load `testdata_path` as path if it is set. - const String root_path = testdata_path.empty() ? getTemporaryPath() : testdata_path[0]; + const String root_path = testdata_path.empty() ? (DB::toString(getpid()) + "/" + getTemporaryPath()) : testdata_path[0]; if (testdata_path.empty()) testdata_path.push_back(root_path); context.setPath(root_path); From c8624a048783d6db172b000a9ec180848f6fb1c4 Mon Sep 17 00:00:00 2001 From: xufei Date: Mon, 30 May 2022 11:00:26 +0800 Subject: [PATCH 073/127] refine get type from decimal literal (#5014) close pingcap/tiflash#5007 --- dbms/src/Common/Decimal.h | 31 +++++- dbms/src/Core/Field.h | 15 ++- dbms/src/DataTypes/DataTypeDecimal.h | 2 +- dbms/src/DataTypes/FieldToDataType.h | 3 +- .../tests/gtest_decimal_literal_datatype.cpp | 100 ++++++++++++++++++ 5 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 dbms/src/DataTypes/tests/gtest_decimal_literal_datatype.cpp diff --git a/dbms/src/Common/Decimal.h b/dbms/src/Common/Decimal.h index 71dcf82ea39..d4a3f382208 100644 --- a/dbms/src/Common/Decimal.h +++ b/dbms/src/Common/Decimal.h @@ -306,10 +306,6 @@ using Decimal64 = Decimal; using Decimal128 = Decimal; using Decimal256 = Decimal; -static constexpr PrecType minDecimalPrecision() -{ - return 1; -} template static constexpr PrecType maxDecimalPrecision() { @@ -336,6 +332,33 @@ constexpr PrecType maxDecimalPrecision() return 65; } +template +constexpr PrecType minDecimalPrecision() +{ + /// return a invalid value + return maxDecimalPrecision() + 1; +} +template <> +constexpr PrecType minDecimalPrecision() +{ + return 1; +} +template <> +constexpr PrecType minDecimalPrecision() +{ + return maxDecimalPrecision() + 1; +} +template <> +constexpr PrecType minDecimalPrecision() +{ + return maxDecimalPrecision() + 1; +} +template <> +constexpr PrecType minDecimalPrecision() +{ + return maxDecimalPrecision() + 1; +} + template struct PromoteType { diff --git a/dbms/src/Core/Field.h b/dbms/src/Core/Field.h index 3d2673a3412..038a9c3fe90 100644 --- a/dbms/src/Core/Field.h +++ b/dbms/src/Core/Field.h @@ -122,7 +122,20 @@ class DecimalField } if (cnt == 0) cnt = 1; - return cnt; + return std::max(cnt, scale); + } + + /// In TiFlash there are 4 subtype of decimal: + /// Decimal32, Decimal64, Decimal128 and Decimal256 + /// they are not compatible with each other. So a DecimalField + /// can not be inserted into a decimal column with DecimalType + /// getPrecWithCurrentDecimalType will return the prec that fit + /// current decimal type, that is to say, current DecimalField can be + /// inserted into a decimal column with type `Decimal(getPrecWithCurrentDecimalType, getScale)` + UInt32 getPrecWithCurrentDecimalType() const + { + auto raw_prec = getPrec(); + return std::max(raw_prec, minDecimalPrecision()); } template diff --git a/dbms/src/DataTypes/DataTypeDecimal.h b/dbms/src/DataTypes/DataTypeDecimal.h index c8f32c03117..47f6602c9a3 100644 --- a/dbms/src/DataTypes/DataTypeDecimal.h +++ b/dbms/src/DataTypes/DataTypeDecimal.h @@ -192,7 +192,7 @@ using DataTypeDecimal256 = DataTypeDecimal; inline DataTypePtr createDecimal(UInt64 prec, UInt64 scale) { - if (prec < minDecimalPrecision() || prec > maxDecimalPrecision()) + if (prec < minDecimalPrecision() || prec > maxDecimalPrecision()) throw Exception("Wrong precision:" + DB::toString(prec), ErrorCodes::ARGUMENT_OUT_OF_BOUND); if (static_cast(scale) > prec) diff --git a/dbms/src/DataTypes/FieldToDataType.h b/dbms/src/DataTypes/FieldToDataType.h index 9903172f860..9f4b80b7324 100644 --- a/dbms/src/DataTypes/FieldToDataType.h +++ b/dbms/src/DataTypes/FieldToDataType.h @@ -40,8 +40,7 @@ class FieldToDataType : public StaticVisitor template DataTypePtr operator()(const DecimalField & x) const { - PrecType prec = maxDecimalPrecision(); - return std::make_shared>(prec, x.getScale()); + return std::make_shared>(x.getPrecWithCurrentDecimalType(), x.getScale()); } }; diff --git a/dbms/src/DataTypes/tests/gtest_decimal_literal_datatype.cpp b/dbms/src/DataTypes/tests/gtest_decimal_literal_datatype.cpp new file mode 100644 index 00000000000..0ae32502679 --- /dev/null +++ b/dbms/src/DataTypes/tests/gtest_decimal_literal_datatype.cpp @@ -0,0 +1,100 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include +#include + +namespace DB +{ +namespace tests +{ +TEST(DecimalLiteralDataTypeTest, getPrec) +try +{ + /// Decimal32 + ASSERT_TRUE(DecimalField(0, 0).getPrec() == 1); + ASSERT_TRUE(DecimalField(0, 1).getPrec() == 1); + ASSERT_TRUE(DecimalField(0, 2).getPrec() == 2); + ASSERT_TRUE(DecimalField(123, 0).getPrec() == 3); + ASSERT_TRUE(DecimalField(123, 2).getPrec() == 3); + ASSERT_TRUE(DecimalField(123, 4).getPrec() == 4); + + /// Decimal64 + ASSERT_TRUE(DecimalField(0, 0).getPrec() == 1); + ASSERT_TRUE(DecimalField(0, 1).getPrec() == 1); + ASSERT_TRUE(DecimalField(0, 2).getPrec() == 2); + ASSERT_TRUE(DecimalField(123, 0).getPrec() == 3); + ASSERT_TRUE(DecimalField(123, 2).getPrec() == 3); + ASSERT_TRUE(DecimalField(123, 4).getPrec() == 4); + ASSERT_TRUE(DecimalField(1234567891011ll, 4).getPrec() == 13); + + /// Decimal128 + ASSERT_TRUE(DecimalField(0, 0).getPrec() == 1); + ASSERT_TRUE(DecimalField(0, 1).getPrec() == 1); + ASSERT_TRUE(DecimalField(0, 2).getPrec() == 2); + ASSERT_TRUE(DecimalField(123, 0).getPrec() == 3); + ASSERT_TRUE(DecimalField(123, 2).getPrec() == 3); + ASSERT_TRUE(DecimalField(123, 4).getPrec() == 4); + ASSERT_TRUE(DecimalField(Int128(123123123123123ll) * 1000000, 4).getPrec() == 21); + + /// Decimal256 + ASSERT_TRUE(DecimalField(Int256(0), 0).getPrec() == 1); + ASSERT_TRUE(DecimalField(Int256(0), 1).getPrec() == 1); + ASSERT_TRUE(DecimalField(Int256(0), 2).getPrec() == 2); + ASSERT_TRUE(DecimalField(Int256(123), 0).getPrec() == 3); + ASSERT_TRUE(DecimalField(Int256(123), 2).getPrec() == 3); + ASSERT_TRUE(DecimalField(Int256(123), 4).getPrec() == 4); + ASSERT_TRUE(DecimalField(Int256(123123123123123123ll) * Int256(1000000000ll) * Int256(100000000000000ll), 4).getPrec() == 41); +} +CATCH + +TEST(DecimalLiteralDataTypeTest, fieldToDataType) +try +{ + /// Decimal32 + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(1,0)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(0, 0))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(1,1)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(0, 1))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(2,2)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(0, 2))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(3,0)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(123, 0))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(3,2)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(123, 2))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(4,4)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(123, 4))))); + + /// Decimal64 + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(10,0)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(0, 0))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(10,1)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(0, 1))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(10,2)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(0, 2))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(10,0)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(123, 0))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(10,2)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(123, 2))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(13,4)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(1234567891011ll, 4))))); + + /// Decimal128 + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(19,0)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(0, 0))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(19,1)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(0, 1))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(19,2)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(0, 2))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(19,0)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(123, 0))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(19,2)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(123, 2))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(21,4)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(Int128(123123123123123ll) * 1000000, 4))))); + + /// Decimal256 + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(39,0)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(Int256(0), 0))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(39,1)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(Int256(0), 1))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(39,2)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(Int256(0), 2))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(39,0)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(Int256(123), 0))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(39,2)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(Int256(123), 2))))); + ASSERT_TRUE(DataTypeFactory::instance().get("Decimal(41,4)")->equals(*applyVisitor(FieldToDataType(), Field(DecimalField(Int256(123123123123123123ll) * Int256(1000000000ll) * Int256(100000000000000ll), 4))))); +} +CATCH +} // namespace tests +} // namespace DB From 516fa158f887b81db8cdb5d3e0f8a016391ed6de Mon Sep 17 00:00:00 2001 From: hzh0425 <642256541@qq.com> Date: Mon, 30 May 2022 11:54:26 +0800 Subject: [PATCH 074/127] Refactor: add new module schema (#4932) ref pingcap/tiflash#4646 --- dbms/CMakeLists.txt | 1 + dbms/src/Databases/test/gtest_database.cpp | 2 +- dbms/src/Debug/MockSchemaGetter.h | 3 +- dbms/src/Debug/MockSchemaNameMapper.h | 2 +- dbms/src/Debug/MockTiDB.h | 4 +- dbms/src/Debug/dbgFuncCoprocessor.cpp | 20 ++-- dbms/src/Debug/dbgFuncMockTiDBTable.cpp | 2 +- dbms/src/Debug/dbgFuncSchema.cpp | 4 +- dbms/src/Debug/dbgFuncSchemaName.cpp | 6 +- dbms/src/Flash/BatchCoprocessorHandler.cpp | 2 +- .../Coprocessor/DAGStorageInterpreter.cpp | 2 +- dbms/src/Flash/CoprocessorHandler.cpp | 2 +- dbms/src/Interpreters/Context.cpp | 3 +- dbms/src/Interpreters/IDAsPathUpgrader.cpp | 20 ++-- dbms/src/Interpreters/IDAsPathUpgrader.h | 4 +- .../Interpreters/InterpreterSelectQuery.cpp | 16 ++-- dbms/src/Interpreters/loadMetadata.cpp | 4 +- dbms/src/Server/Server.cpp | 2 +- dbms/src/Storages/StorageDeltaMerge.cpp | 2 +- .../System/StorageSystemDTSegments.cpp | 21 +++-- .../Storages/System/StorageSystemDTTables.cpp | 2 +- .../System/StorageSystemDatabases.cpp | 17 ++-- .../Storages/System/StorageSystemTables.cpp | 25 ++--- .../Storages/Transaction/ApplySnapshot.cpp | 2 +- .../Storages/Transaction/PartitionStreams.cpp | 2 +- dbms/src/Storages/Transaction/RegionTable.cpp | 2 +- dbms/src/Storages/Transaction/TMTContext.cpp | 6 +- dbms/src/Storages/Transaction/TiDB.cpp | 20 ++-- .../tests/gtest_rename_resolver.cpp | 12 +-- .../Transaction/tests/gtest_table_info.cpp | 92 +++++++++---------- .../Storages/tests/gtest_filter_parser.cpp | 4 +- .../Schema}/SchemaBuilder-internal.h | 0 .../Schema}/SchemaBuilder.cpp | 8 +- .../Schema}/SchemaBuilder.h | 2 +- .../Schema}/SchemaGetter.cpp | 60 +++++++----- .../Schema}/SchemaGetter.h | 6 +- .../Schema}/SchemaNameMapper.h | 0 .../Schema}/SchemaSyncService.cpp | 6 +- .../Schema}/SchemaSyncService.h | 0 .../Schema}/SchemaSyncer.h | 0 .../Schema}/TiDBSchemaSyncer.h | 2 +- 41 files changed, 207 insertions(+), 183 deletions(-) rename dbms/src/{Storages/Transaction => TiDB/Schema}/SchemaBuilder-internal.h (100%) rename dbms/src/{Storages/Transaction => TiDB/Schema}/SchemaBuilder.cpp (99%) rename dbms/src/{Storages/Transaction => TiDB/Schema}/SchemaBuilder.h (98%) rename dbms/src/{Storages/Transaction => TiDB/Schema}/SchemaGetter.cpp (83%) rename dbms/src/{Storages/Transaction => TiDB/Schema}/SchemaGetter.h (96%) rename dbms/src/{Storages/Transaction => TiDB/Schema}/SchemaNameMapper.h (100%) rename dbms/src/{Storages/Transaction => TiDB/Schema}/SchemaSyncService.cpp (98%) rename dbms/src/{Storages/Transaction => TiDB/Schema}/SchemaSyncService.h (100%) rename dbms/src/{Storages/Transaction => TiDB/Schema}/SchemaSyncer.h (100%) rename dbms/src/{Storages/Transaction => TiDB/Schema}/TiDBSchemaSyncer.h (99%) diff --git a/dbms/CMakeLists.txt b/dbms/CMakeLists.txt index 718d18c4954..21cf06cbe31 100644 --- a/dbms/CMakeLists.txt +++ b/dbms/CMakeLists.txt @@ -90,6 +90,7 @@ add_headers_and_sources(dbms src/Storages/Page/V2/mvcc) add_headers_and_sources(dbms src/Storages/Page/V2/VersionSet) add_headers_and_sources(dbms src/Storages/Page/V2/gc) add_headers_and_sources(dbms src/WindowFunctions) +add_headers_and_sources(dbms src/TiDB/Schema) if (ENABLE_V3_PAGESTORAGE) add_headers_and_sources(dbms src/Storages/Page/V3) add_headers_and_sources(dbms src/Storages/Page/V3/LogFile) diff --git a/dbms/src/Databases/test/gtest_database.cpp b/dbms/src/Databases/test/gtest_database.cpp index 72915b8644f..6b8bbc17348 100644 --- a/dbms/src/Databases/test/gtest_database.cpp +++ b/dbms/src/Databases/test/gtest_database.cpp @@ -25,11 +25,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include diff --git a/dbms/src/Debug/MockSchemaGetter.h b/dbms/src/Debug/MockSchemaGetter.h index cdbaed97223..f02699866ce 100644 --- a/dbms/src/Debug/MockSchemaGetter.h +++ b/dbms/src/Debug/MockSchemaGetter.h @@ -15,14 +15,13 @@ #pragma once #include -#include +#include namespace DB { struct MockSchemaGetter { - TiDB::DBInfoPtr getDatabase(DatabaseID db_id) { return MockTiDB::instance().getDBInfoByID(db_id); } Int64 getVersion() { return MockTiDB::instance().getVersion(); } diff --git a/dbms/src/Debug/MockSchemaNameMapper.h b/dbms/src/Debug/MockSchemaNameMapper.h index b3fabab198a..003525aad89 100644 --- a/dbms/src/Debug/MockSchemaNameMapper.h +++ b/dbms/src/Debug/MockSchemaNameMapper.h @@ -14,7 +14,7 @@ #pragma once -#include +#include namespace DB { diff --git a/dbms/src/Debug/MockTiDB.h b/dbms/src/Debug/MockTiDB.h index cb09f9e305a..36d2af90859 100644 --- a/dbms/src/Debug/MockTiDB.h +++ b/dbms/src/Debug/MockTiDB.h @@ -15,10 +15,10 @@ #pragma once #include -#include -#include #include #include +#include +#include #include diff --git a/dbms/src/Debug/dbgFuncCoprocessor.cpp b/dbms/src/Debug/dbgFuncCoprocessor.cpp index a4a1f6730c9..e9335d1e2bd 100644 --- a/dbms/src/Debug/dbgFuncCoprocessor.cpp +++ b/dbms/src/Debug/dbgFuncCoprocessor.cpp @@ -336,7 +336,7 @@ void dbgFuncTiDBQueryFromNaturalDag(Context & context, const ASTs & args, DBGInv if (args.size() != 1) throw Exception("Args not matched, should be: json_dag_path", ErrorCodes::BAD_ARGUMENTS); - String json_dag_path = safeGet(typeid_cast(*args[0]).value); + auto json_dag_path = safeGet(typeid_cast(*args[0]).value); auto dag = NaturalDag(json_dag_path, &Poco::Logger::get("MockDAG")); dag.init(); dag.build(context); @@ -431,7 +431,7 @@ BlockInputStreamPtr dbgFuncTiDBQuery(Context & context, const ASTs & args) if (args.empty() || args.size() > 3) throw Exception("Args not matched, should be: query[, region-id, dag_prop_string]", ErrorCodes::BAD_ARGUMENTS); - String query = safeGet(typeid_cast(*args[0]).value); + auto query = safeGet(typeid_cast(*args[0]).value); RegionID region_id = InvalidRegionID; if (args.size() >= 2) region_id = safeGet(typeid_cast(*args[1]).value); @@ -464,8 +464,8 @@ BlockInputStreamPtr dbgFuncMockTiDBQuery(Context & context, const ASTs & args) if (args.size() < 2 || args.size() > 4) throw Exception("Args not matched, should be: query, region-id[, start-ts, dag_prop_string]", ErrorCodes::BAD_ARGUMENTS); - String query = safeGet(typeid_cast(*args[0]).value); - RegionID region_id = safeGet(typeid_cast(*args[1]).value); + auto query = safeGet(typeid_cast(*args[0]).value); + auto region_id = safeGet(typeid_cast(*args[1]).value); Timestamp start_ts = DEFAULT_MAX_READ_TSO; if (args.size() >= 3) start_ts = safeGet(typeid_cast(*args[2]).value); @@ -671,14 +671,14 @@ const ASTTablesInSelectQueryElement * getJoin(ASTSelectQuery & ast_query) if (!ast_query.tables) return nullptr; - const ASTTablesInSelectQuery & tables_in_select_query = static_cast(*ast_query.tables); + const auto & tables_in_select_query = static_cast(*ast_query.tables); if (tables_in_select_query.children.empty()) return nullptr; const ASTTablesInSelectQueryElement * joined_table = nullptr; for (const auto & child : tables_in_select_query.children) { - const ASTTablesInSelectQueryElement & tables_element = static_cast(*child); + const auto & tables_element = static_cast(*child); if (tables_element.table_join) { if (!joined_table) @@ -737,7 +737,7 @@ std::pair compileQueryBlock( bool append_pk_column = false; for (const auto & expr : ast_query.select_expression_list->children) { - if (ASTIdentifier * identifier = typeid_cast(expr.get())) + if (auto * identifier = typeid_cast(expr.get())) { if (identifier->getColumnName() == MutableSupport::tidb_pk_column_name) { @@ -756,7 +756,7 @@ std::pair compileQueryBlock( String right_table_alias; { String database_name, table_name; - const ASTTableExpression & table_to_join = static_cast(*joined_table->table_expression); + const auto & table_to_join = static_cast(*joined_table->table_expression); if (table_to_join.database_and_table_name) { auto identifier = static_cast(*table_to_join.database_and_table_name); @@ -788,7 +788,7 @@ std::pair compileQueryBlock( bool right_append_pk_column = false; for (const auto & expr : ast_query.select_expression_list->children) { - if (ASTIdentifier * identifier = typeid_cast(expr.get())) + if (auto * identifier = typeid_cast(expr.get())) { auto names = splitQualifiedName(identifier->getColumnName()); if (names.second == MutableSupport::tidb_pk_column_name) @@ -831,7 +831,7 @@ std::pair compileQueryBlock( bool has_agg_func = false; for (const auto & child : ast_query.select_expression_list->children) { - const ASTFunction * func = typeid_cast(child.get()); + const auto * func = typeid_cast(child.get()); if (func && AggregateFunctionFactory::instance().isAggregateFunctionName(func->name)) { has_agg_func = true; diff --git a/dbms/src/Debug/dbgFuncMockTiDBTable.cpp b/dbms/src/Debug/dbgFuncMockTiDBTable.cpp index 5b5cc004b58..65d0b2eadaa 100644 --- a/dbms/src/Debug/dbgFuncMockTiDBTable.cpp +++ b/dbms/src/Debug/dbgFuncMockTiDBTable.cpp @@ -24,8 +24,8 @@ #include #include #include -#include #include +#include #include namespace DB diff --git a/dbms/src/Debug/dbgFuncSchema.cpp b/dbms/src/Debug/dbgFuncSchema.cpp index 00ba5ab7335..8b73ddc23a3 100644 --- a/dbms/src/Debug/dbgFuncSchema.cpp +++ b/dbms/src/Debug/dbgFuncSchema.cpp @@ -22,10 +22,10 @@ #include #include #include -#include -#include #include #include +#include +#include #include #include diff --git a/dbms/src/Debug/dbgFuncSchemaName.cpp b/dbms/src/Debug/dbgFuncSchemaName.cpp index a4dac1ae050..4c2ad86bd62 100644 --- a/dbms/src/Debug/dbgFuncSchemaName.cpp +++ b/dbms/src/Debug/dbgFuncSchemaName.cpp @@ -20,9 +20,9 @@ #include #include #include -#include -#include #include +#include +#include #include #include @@ -97,7 +97,7 @@ BlockInputStreamPtr dbgFuncQueryMapped(Context & context, const ASTs & args) if (args.size() < 2 || args.size() > 3) throw Exception("Args not matched, should be: query, database-name[, table-name]", ErrorCodes::BAD_ARGUMENTS); - String query = safeGet(typeid_cast(*args[0]).value); + auto query = safeGet(typeid_cast(*args[0]).value); const String & database_name = typeid_cast(*args[1]).name; if (args.size() == 3) diff --git a/dbms/src/Flash/BatchCoprocessorHandler.cpp b/dbms/src/Flash/BatchCoprocessorHandler.cpp index 273ceec8f08..0fd41832711 100644 --- a/dbms/src/Flash/BatchCoprocessorHandler.cpp +++ b/dbms/src/Flash/BatchCoprocessorHandler.cpp @@ -18,8 +18,8 @@ #include #include #include -#include #include +#include #include diff --git a/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp b/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp index d91b18254f6..11a1b7e2d3e 100644 --- a/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp +++ b/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp @@ -33,7 +33,7 @@ #include #include #include -#include +#include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" diff --git a/dbms/src/Flash/CoprocessorHandler.cpp b/dbms/src/Flash/CoprocessorHandler.cpp index e432dd37083..3d653025b83 100644 --- a/dbms/src/Flash/CoprocessorHandler.cpp +++ b/dbms/src/Flash/CoprocessorHandler.cpp @@ -22,8 +22,8 @@ #include #include #include -#include #include +#include #include diff --git a/dbms/src/Interpreters/Context.cpp b/dbms/src/Interpreters/Context.cpp index 5d967b388d0..a0adef5b50d 100644 --- a/dbms/src/Interpreters/Context.cpp +++ b/dbms/src/Interpreters/Context.cpp @@ -59,9 +59,9 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -207,6 +207,7 @@ struct ContextShared explicit ContextShared(std::shared_ptr runtime_components_factory_) : runtime_components_factory(std::move(runtime_components_factory_)) + , storage_run_mode(PageStorageRunMode::ONLY_V3) { /// TODO: make it singleton (?) static std::atomic num_calls{0}; diff --git a/dbms/src/Interpreters/IDAsPathUpgrader.cpp b/dbms/src/Interpreters/IDAsPathUpgrader.cpp index ce72625fd46..8c807b537e9 100644 --- a/dbms/src/Interpreters/IDAsPathUpgrader.cpp +++ b/dbms/src/Interpreters/IDAsPathUpgrader.cpp @@ -33,10 +33,10 @@ #include #include #include -#include #include #include -#include +#include +#include #include #include @@ -71,7 +71,7 @@ std::shared_ptr getDatabaseEngine(const FileProviderPtr & file_prov ParserCreateQuery parser; ASTPtr ast = parseQuery(parser, query.data(), query.data() + query.size(), "in file " + filename, 0); ASTCreateQuery & ast_create_query = typeid_cast(*ast); - auto storage = ast_create_query.storage; + auto * storage = ast_create_query.storage; if (storage == nullptr || storage->engine == nullptr || storage->engine->name.empty()) { throw Exception("Can not get database engine for file: " + filename, ErrorCodes::LOGICAL_ERROR); @@ -97,7 +97,7 @@ std::pair getTableInfo(const FileProviderPtr & file_pro ParserCreateQuery parser; ASTPtr ast = parseQuery(parser, definition.data(), definition.data() + definition.size(), "in file " + table_metadata_file, 0); ASTCreateQuery & ast_create_query = typeid_cast(*ast); - auto storage = ast_create_query.storage; + auto * storage = ast_create_query.storage; if (storage == nullptr || storage->engine == nullptr || storage->engine->name.empty()) { throw Exception("Can not get table engine for file: " + table_metadata_file, ErrorCodes::LOGICAL_ERROR); @@ -105,7 +105,7 @@ std::pair getTableInfo(const FileProviderPtr & file_pro TiDB::TableInfo info; ASTFunction * engine = storage->engine; - auto * args = typeid_cast(engine->arguments.get()); + const auto * args = typeid_cast(engine->arguments.get()); if (args == nullptr) throw Exception("Can not cast table engine arguments", ErrorCodes::BAD_ARGUMENTS); @@ -399,12 +399,12 @@ String IDAsPathUpgrader::DatabaseDiskInfo::getNewMetaDirectory(const String & ro return root_path + (endsWith(root_path, "/") ? "" : "/") + "/metadata/" + escapeForFileName(newName()) + "/"; } // "data/" -String IDAsPathUpgrader::DatabaseDiskInfo::getNewDataDirectory(const String & root_path) const +String IDAsPathUpgrader::DatabaseDiskInfo::getNewDataDirectory(const String & root_path) { return root_path + "/data/"; } // "extra_data/" -String IDAsPathUpgrader::DatabaseDiskInfo::getNewExtraDirectory(const String & extra_root) const +String IDAsPathUpgrader::DatabaseDiskInfo::getNewExtraDirectory(const String & extra_root) { return extra_root + "/"; } @@ -457,11 +457,11 @@ IDAsPathUpgrader::IDAsPathUpgrader(Context & global_ctx_, bool is_mock_, std::un bool IDAsPathUpgrader::needUpgrade() { - const auto metadataPath = global_context.getPath() + "/metadata"; + const auto metadata_path = global_context.getPath() + "/metadata"; // For old version, we have database directories and its `.sql` file Poco::DirectoryIterator dir_end; - for (Poco::DirectoryIterator it(metadataPath); it != dir_end; ++it) + for (Poco::DirectoryIterator it(metadata_path); it != dir_end; ++it) { if (!it->isDirectory()) continue; @@ -893,7 +893,7 @@ void IDAsPathUpgrader::renameTable( args->children.emplace_back(literal); else if (args->children.size() >= 2) args->children.at(1) = literal; - } while (0); + } while (false); } const String new_tbl_meta_file = table.getNewMetaFilePath(root_path, db_info); diff --git a/dbms/src/Interpreters/IDAsPathUpgrader.h b/dbms/src/Interpreters/IDAsPathUpgrader.h index 8ef57f7f2cc..38dc37536aa 100644 --- a/dbms/src/Interpreters/IDAsPathUpgrader.h +++ b/dbms/src/Interpreters/IDAsPathUpgrader.h @@ -137,9 +137,9 @@ class IDAsPathUpgrader // "metadata/db_${id}/" String getNewMetaDirectory(const String & root_path) const; // "data/" - String getNewDataDirectory(const String & root_path) const; + static String getNewDataDirectory(const String & root_path); // "extra_data/" - String getNewExtraDirectory(const String & extra_root) const; + static String getNewExtraDirectory(const String & extra_root); private: // "metadata/${db_name}.sql" diff --git a/dbms/src/Interpreters/InterpreterSelectQuery.cpp b/dbms/src/Interpreters/InterpreterSelectQuery.cpp index a56fdb849e3..51b55f65bd4 100644 --- a/dbms/src/Interpreters/InterpreterSelectQuery.cpp +++ b/dbms/src/Interpreters/InterpreterSelectQuery.cpp @@ -55,12 +55,12 @@ #include #include #include -#include #include #include #include #include #include +#include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" @@ -496,7 +496,7 @@ void InterpreterSelectQuery::executeImpl(Pipeline & pipeline, const BlockInputSt { if (expressions.has_join) { - const ASTTableJoin & join = static_cast(*query.join()->table_join); + const auto & join = static_cast(*query.join()->table_join); if (join.kind == ASTTableJoin::Kind::Full || join.kind == ASTTableJoin::Kind::Right) pipeline.stream_with_non_joined_data = expressions.before_join->createStreamWithNonJoinedDataIfFullOrRightJoin( pipeline.firstStream()->getHeader(), @@ -816,7 +816,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(Pipeline for (size_t i = 0; i < arr->size(); i++) { - String str = arr->getElement(i); + auto str = arr->getElement(i); ::metapb::Region region; ::google::protobuf::TextFormat::ParseFromString(str, ®ion); @@ -839,7 +839,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(Pipeline } /// PARTITION SELECT only supports MergeTree family now. - if (const ASTSelectQuery * select_query = typeid_cast(query_info.query.get())) + if (const auto * select_query = typeid_cast(query_info.query.get())) { if (select_query->partition_expression_list) { @@ -860,7 +860,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(Pipeline if (auto managed_storage = std::dynamic_pointer_cast(storage); managed_storage && managed_storage->engineType() == TiDB::StorageEngine::DT) { - if (const ASTSelectQuery * select_query = typeid_cast(query_info.query.get())) + if (const auto * select_query = typeid_cast(query_info.query.get())) { // With `no_kvsotre` is true, we do not do learner read if (likely(!select_query->no_kvstore)) @@ -910,7 +910,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(Pipeline QuotaForIntervals & quota = context.getQuota(); pipeline.transform([&](auto & stream) { - if (IProfilingBlockInputStream * p_stream = dynamic_cast(stream.get())) + if (auto * p_stream = dynamic_cast(stream.get())) { p_stream->setLimits(limits); @@ -1275,7 +1275,7 @@ void InterpreterSelectQuery::executeLimitBy(Pipeline & pipeline) // NOLINT for (const auto & elem : query.limit_by_expression_list->children) columns.emplace_back(elem->getColumnName()); - size_t value = safeGet(typeid_cast(*query.limit_by_value).value); + auto value = safeGet(typeid_cast(*query.limit_by_value).value); pipeline.transform([&](auto & stream) { stream = std::make_shared(stream, value, columns); @@ -1347,7 +1347,7 @@ void InterpreterSelectQuery::executeExtremes(Pipeline & pipeline) return; pipeline.transform([&](auto & stream) { - if (IProfilingBlockInputStream * p_stream = dynamic_cast(stream.get())) + if (auto * p_stream = dynamic_cast(stream.get())) p_stream->enableExtremes(); }); } diff --git a/dbms/src/Interpreters/loadMetadata.cpp b/dbms/src/Interpreters/loadMetadata.cpp index 77c2bda6cda..54167f364e7 100644 --- a/dbms/src/Interpreters/loadMetadata.cpp +++ b/dbms/src/Interpreters/loadMetadata.cpp @@ -26,9 +26,9 @@ #include #include #include -#include -#include #include +#include +#include #include #include diff --git a/dbms/src/Server/Server.cpp b/dbms/src/Server/Server.cpp index fcf820eb958..04676ef969d 100644 --- a/dbms/src/Server/Server.cpp +++ b/dbms/src/Server/Server.cpp @@ -65,10 +65,10 @@ #include #include #include -#include #include #include #include +#include #include #include #include diff --git a/dbms/src/Storages/StorageDeltaMerge.cpp b/dbms/src/Storages/StorageDeltaMerge.cpp index 38a947a027f..fc73e28e23a 100644 --- a/dbms/src/Storages/StorageDeltaMerge.cpp +++ b/dbms/src/Storages/StorageDeltaMerge.cpp @@ -44,10 +44,10 @@ #include #include #include -#include #include #include #include +#include #include #include #include diff --git a/dbms/src/Storages/System/StorageSystemDTSegments.cpp b/dbms/src/Storages/System/StorageSystemDTSegments.cpp index 37f2dbe1b99..f84a19a005c 100644 --- a/dbms/src/Storages/System/StorageSystemDTSegments.cpp +++ b/dbms/src/Storages/System/StorageSystemDTSegments.cpp @@ -23,12 +23,13 @@ #include #include #include -#include #include +#include namespace DB { -StorageSystemDTSegments::StorageSystemDTSegments(const std::string & name_) : name(name_) +StorageSystemDTSegments::StorageSystemDTSegments(const std::string & name_) + : name(name_) { setColumns(ColumnsDescription({ {"database", std::make_shared()}, @@ -61,11 +62,11 @@ StorageSystemDTSegments::StorageSystemDTSegments(const std::string & name_) : na } BlockInputStreams StorageSystemDTSegments::read(const Names & column_names, - const SelectQueryInfo &, - const Context & context, - QueryProcessingStage::Enum & processed_stage, - const size_t /*max_block_size*/, - const unsigned /*num_streams*/) + const SelectQueryInfo &, + const Context & context, + QueryProcessingStage::Enum & processed_stage, + const size_t /*max_block_size*/, + const unsigned /*num_streams*/) { check(column_names); processed_stage = QueryProcessingStage::FetchColumns; @@ -78,19 +79,19 @@ BlockInputStreams StorageSystemDTSegments::read(const Names & column_names, for (const auto & d : databases) { String database_name = d.first; - auto & database = d.second; + const auto & database = d.second; const DatabaseTiFlash * db_tiflash = typeid_cast(database.get()); auto it = database->getIterator(context); for (; it->isValid(); it->next()) { - auto & table_name = it->name(); + const auto & table_name = it->name(); auto & storage = it->table(); if (storage->getName() != MutableSupport::delta_tree_storage_name) continue; auto dm_storage = std::dynamic_pointer_cast(storage); - auto & table_info = dm_storage->getTableInfo(); + const auto & table_info = dm_storage->getTableInfo(); auto table_id = table_info.id; auto segment_stats = dm_storage->getStore()->getSegmentStats(); for (auto & stat : segment_stats) diff --git a/dbms/src/Storages/System/StorageSystemDTTables.cpp b/dbms/src/Storages/System/StorageSystemDTTables.cpp index b3f9cf5b29e..b700cfb5324 100644 --- a/dbms/src/Storages/System/StorageSystemDTTables.cpp +++ b/dbms/src/Storages/System/StorageSystemDTTables.cpp @@ -23,7 +23,7 @@ #include #include #include -#include +#include namespace DB { diff --git a/dbms/src/Storages/System/StorageSystemDatabases.cpp b/dbms/src/Storages/System/StorageSystemDatabases.cpp index 6c665fec900..5ba249a0aba 100644 --- a/dbms/src/Storages/System/StorageSystemDatabases.cpp +++ b/dbms/src/Storages/System/StorageSystemDatabases.cpp @@ -21,16 +21,17 @@ #include #include #include -#include #include #include +#include namespace DB { -StorageSystemDatabases::StorageSystemDatabases(const std::string & name_) : name(name_) +StorageSystemDatabases::StorageSystemDatabases(const std::string & name_) + : name(name_) { setColumns(ColumnsDescription({ {"name", std::make_shared()}, @@ -45,11 +46,11 @@ StorageSystemDatabases::StorageSystemDatabases(const std::string & name_) : name BlockInputStreams StorageSystemDatabases::read(const Names & column_names, - const SelectQueryInfo &, - const Context & context, - QueryProcessingStage::Enum & processed_stage, - const size_t /*max_block_size*/, - const unsigned /*num_streams*/) + const SelectQueryInfo &, + const Context & context, + QueryProcessingStage::Enum & processed_stage, + const size_t /*max_block_size*/, + const unsigned /*num_streams*/) { check(column_names); processed_stage = QueryProcessingStage::FetchColumns; @@ -79,7 +80,7 @@ BlockInputStreams StorageSystemDatabases::read(const Names & column_names, res_columns[j++]->insert(Int64(database_id)); res_columns[j++]->insert(database.second->getEngineName()); - res_columns[j++]->insert((UInt64)tombstone); + res_columns[j++]->insert(static_cast(tombstone)); res_columns[j++]->insert(database.second->getDataPath()); res_columns[j++]->insert(database.second->getMetadataPath()); } diff --git a/dbms/src/Storages/System/StorageSystemTables.cpp b/dbms/src/Storages/System/StorageSystemTables.cpp index dd24d426e80..4abf0044c87 100644 --- a/dbms/src/Storages/System/StorageSystemTables.cpp +++ b/dbms/src/Storages/System/StorageSystemTables.cpp @@ -27,10 +27,10 @@ #include #include #include -#include #include #include #include +#include namespace DB { @@ -71,7 +71,8 @@ NameAndTypePair tryGetColumn(const ColumnsWithTypeAndName & columns, const Strin struct VirtualColumnsProcessor { explicit VirtualColumnsProcessor(const ColumnsWithTypeAndName & all_virtual_columns_) - : all_virtual_columns(all_virtual_columns_), virtual_columns_mask(all_virtual_columns_.size(), 0) + : all_virtual_columns(all_virtual_columns_) + , virtual_columns_mask(all_virtual_columns_.size(), 0) {} /// Separates real and virtual column names, returns real ones @@ -131,7 +132,8 @@ struct VirtualColumnsProcessor } // namespace -StorageSystemTables::StorageSystemTables(const std::string & name_) : name(name_) +StorageSystemTables::StorageSystemTables(const std::string & name_) + : name(name_) { setColumns(ColumnsDescription({ {"database", std::make_shared()}, @@ -147,7 +149,8 @@ StorageSystemTables::StorageSystemTables(const std::string & name_) : name(name_ })); virtual_columns = {{std::make_shared(), "metadata_modification_time"}, - {std::make_shared(), "create_table_query"}, {std::make_shared(), "engine_full"}}; + {std::make_shared(), "create_table_query"}, + {std::make_shared(), "engine_full"}}; } @@ -164,11 +167,11 @@ static ColumnPtr getFilteredDatabases(const ASTPtr & query, const Context & cont BlockInputStreams StorageSystemTables::read(const Names & column_names, - const SelectQueryInfo & query_info, - const Context & context, - QueryProcessingStage::Enum & processed_stage, - const size_t /*max_block_size*/, - const unsigned /*num_streams*/) + const SelectQueryInfo & query_info, + const Context & context, + QueryProcessingStage::Enum & processed_stage, + const size_t /*max_block_size*/, + const unsigned /*num_streams*/) { processed_stage = QueryProcessingStage::FetchColumns; @@ -226,7 +229,7 @@ BlockInputStreams StorageSystemTables::read(const Names & column_names, { if (db_tiflash) tidb_database_name = mapper.displayDatabaseName(db_tiflash->getDatabaseInfo()); - auto & table_info = managed_storage->getTableInfo(); + const auto & table_info = managed_storage->getTableInfo(); tidb_table_name = mapper.displayTableName(table_info); table_id = table_info.id; tombstone = managed_storage->getTombstone(); @@ -279,7 +282,7 @@ BlockInputStreams StorageSystemTables::read(const Names & column_names, { Tables external_tables = context.getSessionContext().getExternalTables(); - for (auto table : external_tables) + for (const auto & table : external_tables) { size_t j = 0; res_columns[j++]->insertDefault(); diff --git a/dbms/src/Storages/Transaction/ApplySnapshot.cpp b/dbms/src/Storages/Transaction/ApplySnapshot.cpp index 6106dda6f4b..3ed04d5ecbf 100644 --- a/dbms/src/Storages/Transaction/ApplySnapshot.cpp +++ b/dbms/src/Storages/Transaction/ApplySnapshot.cpp @@ -28,8 +28,8 @@ #include #include #include -#include #include +#include #include diff --git a/dbms/src/Storages/Transaction/PartitionStreams.cpp b/dbms/src/Storages/Transaction/PartitionStreams.cpp index 9142aad5358..13840159ebb 100644 --- a/dbms/src/Storages/Transaction/PartitionStreams.cpp +++ b/dbms/src/Storages/Transaction/PartitionStreams.cpp @@ -26,10 +26,10 @@ #include #include #include -#include #include #include #include +#include #include namespace DB diff --git a/dbms/src/Storages/Transaction/RegionTable.cpp b/dbms/src/Storages/Transaction/RegionTable.cpp index 8b5ca5746f0..c855d5b3226 100644 --- a/dbms/src/Storages/Transaction/RegionTable.cpp +++ b/dbms/src/Storages/Transaction/RegionTable.cpp @@ -20,9 +20,9 @@ #include #include #include -#include #include #include +#include namespace DB { diff --git a/dbms/src/Storages/Transaction/TMTContext.cpp b/dbms/src/Storages/Transaction/TMTContext.cpp index 006be9d7a92..719784edaf2 100644 --- a/dbms/src/Storages/Transaction/TMTContext.cpp +++ b/dbms/src/Storages/Transaction/TMTContext.cpp @@ -22,9 +22,9 @@ #include #include #include -#include #include -#include +#include +#include #include namespace DB @@ -55,6 +55,8 @@ TMTContext::TMTContext(Context & context_, const TiFlashRaftConfig & raft_config , engine(raft_config.engine) , replica_read_max_thread(1) , batch_read_index_timeout_ms(DEFAULT_BATCH_READ_INDEX_TIMEOUT_MS) + , wait_index_timeout_ms(DEFAULT_WAIT_INDEX_TIMEOUT_MS) + , read_index_worker_tick_ms(10) , wait_region_ready_timeout_sec(DEFAULT_WAIT_REGION_READY_TIMEOUT_SEC) {} diff --git a/dbms/src/Storages/Transaction/TiDB.cpp b/dbms/src/Storages/Transaction/TiDB.cpp index 3810e25372f..15bf2a3fb58 100644 --- a/dbms/src/Storages/Transaction/TiDB.cpp +++ b/dbms/src/Storages/Transaction/TiDB.cpp @@ -23,8 +23,8 @@ #include #include #include -#include #include +#include #include @@ -631,8 +631,8 @@ catch (const Poco::Exception & e) /////////////////////// IndexColumnInfo::IndexColumnInfo(Poco::JSON::Object::Ptr json) - : offset() - , length() + : offset(0) + , length(0) { deserialize(json); } @@ -682,13 +682,13 @@ catch (const Poco::Exception & e) /////////////////////// IndexInfo::IndexInfo(Poco::JSON::Object::Ptr json) - : id() - , state() - , index_type() - , is_unique() - , is_primary() - , is_invisible() - , is_global() + : id(0) + , state(TiDB::SchemaState::StateNone) + , index_type(0) + , is_unique(true) + , is_primary(true) + , is_invisible(true) + , is_global(true) { deserialize(json); } diff --git a/dbms/src/Storages/Transaction/tests/gtest_rename_resolver.cpp b/dbms/src/Storages/Transaction/tests/gtest_rename_resolver.cpp index f149fd40e3a..4d1afe2653c 100644 --- a/dbms/src/Storages/Transaction/tests/gtest_rename_resolver.cpp +++ b/dbms/src/Storages/Transaction/tests/gtest_rename_resolver.cpp @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include #include +#include +#include namespace DB::tests { @@ -138,10 +138,10 @@ inline ::testing::AssertionResult ColumnNameWithIDPairsCompare( // return ::testing::AssertionSuccess(); else return ::testing::internal::EqFailure(lhs_expr, - rhs_expr, - "<" + lhs.first.toString() + "," + lhs.second.toString() + ">", - "<" + rhs.first.toString() + "," + rhs.second.toString() + ">", - false); + rhs_expr, + "<" + lhs.first.toString() + "," + lhs.second.toString() + ">", + "<" + rhs.first.toString() + "," + rhs.second.toString() + ">", + false); } #define ASSERT_COLUMN_NAME_ID_PAIR_EQ(val1, val2) ASSERT_PRED_FORMAT2(::DB::tests::ColumnNameWithIDPairsCompare, val1, val2) diff --git a/dbms/src/Storages/Transaction/tests/gtest_table_info.cpp b/dbms/src/Storages/Transaction/tests/gtest_table_info.cpp index 0c9747cc24c..516a173b151 100644 --- a/dbms/src/Storages/Transaction/tests/gtest_table_info.cpp +++ b/dbms/src/Storages/Transaction/tests/gtest_table_info.cpp @@ -18,10 +18,10 @@ #include #include #include -#include #include #include #include +#include using TableInfo = TiDB::TableInfo; @@ -65,20 +65,20 @@ try R"json({"id":45,"name":{"O":"t","L":"t"},"charset":"utf8mb4","collate":"utf8mb4_bin","cols":[{"id":1,"name":{"O":"t","L":"t"},"offset":0,"origin_default":"\u0000\u00124","origin_default_bit":null,"default":null,"default_bit":null,"default_is_expr":false,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":254,"Flag":129,"Flen":4,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":"","hidden":false,"change_state_info":null,"version":2}],"index_info":null,"constraint_info":null,"fk_info":null,"state":5,"pk_is_handle":false,"is_common_handle":false,"comment":"","auto_inc_id":0,"auto_id_cache":0,"auto_rand_id":0,"max_col_id":1,"max_idx_id":0,"max_cst_id":0,"update_timestamp":418683341902184450,"ShardRowIDBits":0,"max_shard_row_id_bits":0,"auto_random_bits":0,"pre_split_regions":0,"partition":null,"compression":"","view":null,"sequence":null,"Lock":null,"version":3,"tiflash_replica":{"Count":1,"LocationLabels":[],"Available":false,"AvailablePartitionIDs":null}})json", [](const TableInfo & table_info) { ASSERT_EQ(table_info.columns[0].defaultValueToField().get(), - Field(String("\0\x12" - "4\0", - 4)) - .get()); + Field(String("\0\x12" + "4\0", + 4)) + .get()); }}, // Test binary default value with exact length having the full content. ParseCase{ R"json({"id":45,"name":{"O":"t","L":"t"},"charset":"utf8mb4","collate":"utf8mb4_bin","cols":[{"id":1,"name":{"O":"t","L":"t"},"offset":0,"origin_default":"\u0000\u00124","origin_default_bit":null,"default":null,"default_bit":null,"default_is_expr":false,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":254,"Flag":129,"Flen":3,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":"","hidden":false,"change_state_info":null,"version":2}],"index_info":null,"constraint_info":null,"fk_info":null,"state":5,"pk_is_handle":false,"is_common_handle":false,"comment":"","auto_inc_id":0,"auto_id_cache":0,"auto_rand_id":0,"max_col_id":1,"max_idx_id":0,"max_cst_id":0,"update_timestamp":418683341902184450,"ShardRowIDBits":0,"max_shard_row_id_bits":0,"auto_random_bits":0,"pre_split_regions":0,"partition":null,"compression":"","view":null,"sequence":null,"Lock":null,"version":3,"tiflash_replica":{"Count":1,"LocationLabels":[],"Available":false,"AvailablePartitionIDs":null}})json", [](const TableInfo & table_info) { ASSERT_EQ(table_info.columns[0].defaultValueToField().get(), - Field(String("\0\x12" - "4", - 3)) - .get()); + Field(String("\0\x12" + "4", + 3)) + .get()); }}, }; @@ -141,47 +141,47 @@ try { auto cases = // {StmtCase{ - 1145, // + 1145, // R"json({"id":1939,"db_name":{"O":"customer","L":"customer"},"charset":"utf8mb4","collate":"utf8mb4_bin","state":5})json", // R"json({"id":1145,"name":{"O":"customerdebt","L":"customerdebt"},"cols":[{"id":1,"name":{"O":"id","L":"id"},"offset":0,"origin_default":null,"default":null,"default_bit":null,"type":{"Tp":8,"Flag":515,"Flen":20,"Decimal":0},"state":5,"comment":"i\"d"}],"state":5,"pk_is_handle":true,"schema_version":-1,"comment":"负债信息","partition":null})json", // R"stmt(CREATE TABLE `customer`.`customerdebt`(`id` Int64) Engine = DeltaMerge((`id`), '{"cols":[{"comment":"i\\"d","default":null,"default_bit":null,"id":1,"name":{"L":"id","O":"id"},"offset":0,"origin_default":null,"state":5,"type":{"Charset":null,"Collate":null,"Decimal":0,"Elems":null,"Flag":515,"Flen":20,"Tp":8}}],"comment":"\\u8D1F\\u503A\\u4FE1\\u606F","id":1145,"index_info":[],"is_common_handle":false,"name":{"L":"customerdebt","O":"customerdebt"},"partition":null,"pk_is_handle":true,"schema_version":-1,"state":5,"tiflash_replica":{"Count":0},"update_timestamp":0}'))stmt", // }, - StmtCase{ - 2049, // - R"json({"id":1939,"db_name":{"O":"customer","L":"customer"},"charset":"utf8mb4","collate":"utf8mb4_bin","state":5})json", // - R"json({"id":2049,"name":{"O":"customerdebt","L":"customerdebt"},"cols":[{"id":1,"name":{"O":"id","L":"id"},"offset":0,"origin_default":null,"default":null,"default_bit":null,"type":{"Tp":8,"Flag":515,"Flen":20,"Decimal":0},"state":5,"comment":"i\"d"}],"state":5,"pk_is_handle":true,"schema_version":-1,"comment":"负债信息","update_timestamp":404545295996944390,"partition":null})json", // - R"stmt(CREATE TABLE `customer`.`customerdebt`(`id` Int64) Engine = DeltaMerge((`id`), '{"cols":[{"comment":"i\\"d","default":null,"default_bit":null,"id":1,"name":{"L":"id","O":"id"},"offset":0,"origin_default":null,"state":5,"type":{"Charset":null,"Collate":null,"Decimal":0,"Elems":null,"Flag":515,"Flen":20,"Tp":8}}],"comment":"\\u8D1F\\u503A\\u4FE1\\u606F","id":2049,"index_info":[],"is_common_handle":false,"name":{"L":"customerdebt","O":"customerdebt"},"partition":null,"pk_is_handle":true,"schema_version":-1,"state":5,"tiflash_replica":{"Count":0},"update_timestamp":404545295996944390}'))stmt", // - }, - StmtCase{ - 31, // - R"json({"id":1,"db_name":{"O":"db1","L":"db1"},"charset":"utf8mb4","collate":"utf8mb4_bin","state":5})json", // - R"json({"id":31,"name":{"O":"simple_t","L":"simple_t"},"charset":"","collate":"","cols":[{"id":1,"name":{"O":"i","L":"i"},"offset":0,"origin_default":null,"default":null,"default_bit":null,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":3,"Flag":0,"Flen":11,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":""}],"index_info":null,"fk_info":null,"state":5,"pk_is_handle":false,"schema_version":-1,"comment":"","auto_inc_id":0,"max_col_id":1,"max_idx_id":0,"update_timestamp":404545295996944390,"ShardRowIDBits":0,"partition":null})json", // - R"stmt(CREATE TABLE `db1`.`simple_t`(`i` Nullable(Int32), `_tidb_rowid` Int64) Engine = DeltaMerge((`_tidb_rowid`), '{"cols":[{"comment":"","default":null,"default_bit":null,"id":1,"name":{"L":"i","O":"i"},"offset":0,"origin_default":null,"state":5,"type":{"Charset":"binary","Collate":"binary","Decimal":0,"Elems":null,"Flag":0,"Flen":11,"Tp":3}}],"comment":"","id":31,"index_info":[],"is_common_handle":false,"name":{"L":"simple_t","O":"simple_t"},"partition":null,"pk_is_handle":false,"schema_version":-1,"state":5,"tiflash_replica":{"Count":0},"update_timestamp":404545295996944390}'))stmt", // - }, - StmtCase{ - 33, // - R"json({"id":2,"db_name":{"O":"db2","L":"db2"},"charset":"utf8mb4","collate":"utf8mb4_bin","state":5})json", // - R"json({"id":33,"name":{"O":"pk_t","L":"pk_t"},"charset":"","collate":"","cols":[{"id":1,"name":{"O":"i","L":"i"},"offset":0,"origin_default":null,"default":null,"default_bit":null,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":3,"Flag":3,"Flen":11,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":""}],"index_info":null,"fk_info":null,"state":5,"pk_is_handle":true,"schema_version":-1,"comment":"","auto_inc_id":0,"max_col_id":1,"max_idx_id":0,"update_timestamp":404545312978108418,"ShardRowIDBits":0,"partition":null})json", // - R"stmt(CREATE TABLE `db2`.`pk_t`(`i` Int32) Engine = DeltaMerge((`i`), '{"cols":[{"comment":"","default":null,"default_bit":null,"id":1,"name":{"L":"i","O":"i"},"offset":0,"origin_default":null,"state":5,"type":{"Charset":"binary","Collate":"binary","Decimal":0,"Elems":null,"Flag":3,"Flen":11,"Tp":3}}],"comment":"","id":33,"index_info":[],"is_common_handle":false,"name":{"L":"pk_t","O":"pk_t"},"partition":null,"pk_is_handle":true,"schema_version":-1,"state":5,"tiflash_replica":{"Count":0},"update_timestamp":404545312978108418}'))stmt", // - }, - StmtCase{ - 35, // - R"json({"id":1,"db_name":{"O":"db1","L":"db1"},"charset":"utf8mb4","collate":"utf8mb4_bin","state":5})json", // - R"json({"id":35,"name":{"O":"not_null_t","L":"not_null_t"},"charset":"","collate":"","cols":[{"id":1,"name":{"O":"i","L":"i"},"offset":0,"origin_default":null,"default":null,"default_bit":null,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":3,"Flag":4097,"Flen":11,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":""}],"index_info":null,"fk_info":null,"state":5,"pk_is_handle":false,"schema_version":-1,"comment":"","auto_inc_id":0,"max_col_id":1,"max_idx_id":0,"update_timestamp":404545324922961926,"ShardRowIDBits":0,"partition":null})json", // - R"stmt(CREATE TABLE `db1`.`not_null_t`(`i` Int32, `_tidb_rowid` Int64) Engine = DeltaMerge((`_tidb_rowid`), '{"cols":[{"comment":"","default":null,"default_bit":null,"id":1,"name":{"L":"i","O":"i"},"offset":0,"origin_default":null,"state":5,"type":{"Charset":"binary","Collate":"binary","Decimal":0,"Elems":null,"Flag":4097,"Flen":11,"Tp":3}}],"comment":"","id":35,"index_info":[],"is_common_handle":false,"name":{"L":"not_null_t","O":"not_null_t"},"partition":null,"pk_is_handle":false,"schema_version":-1,"state":5,"tiflash_replica":{"Count":0},"update_timestamp":404545324922961926}'))stmt", // - }, - StmtCase{ - 37, // - R"json({"id":2,"db_name":{"O":"db2","L":"db2"},"charset":"utf8mb4","collate":"utf8mb4_bin","state":5})json", - R"json({"id":37,"name":{"O":"mytable","L":"mytable"},"charset":"","collate":"","cols":[{"id":1,"name":{"O":"mycol","L":"mycol"},"offset":0,"origin_default":null,"default":null,"default_bit":null,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":15,"Flag":4099,"Flen":256,"Decimal":0,"Charset":"utf8","Collate":"utf8_bin","Elems":null},"state":5,"comment":""}],"index_info":[{"id":1,"idx_name":{"O":"PRIMARY","L":"primary"},"tbl_name":{"O":"","L":""},"idx_cols":[{"name":{"O":"mycol","L":"mycol"},"offset":0,"length":-1}],"is_unique":true,"is_primary":true,"state":5,"comment":"","index_type":1}],"fk_info":null,"state":5,"pk_is_handle":true,"schema_version":-1,"comment":"","auto_inc_id":0,"max_col_id":1,"max_idx_id":1,"update_timestamp":404566455285710853,"ShardRowIDBits":0,"partition":null})json", // - R"stmt(CREATE TABLE `db2`.`mytable`(`mycol` String) Engine = DeltaMerge((`mycol`), '{"cols":[{"comment":"","default":null,"default_bit":null,"id":1,"name":{"L":"mycol","O":"mycol"},"offset":0,"origin_default":null,"state":5,"type":{"Charset":"utf8","Collate":"utf8_bin","Decimal":0,"Elems":null,"Flag":4099,"Flen":256,"Tp":15}}],"comment":"","id":37,"index_info":[],"is_common_handle":false,"name":{"L":"mytable","O":"mytable"},"partition":null,"pk_is_handle":true,"schema_version":-1,"state":5,"tiflash_replica":{"Count":0},"update_timestamp":404566455285710853}'))stmt", // - }, - StmtCase{ - 32, // - R"json({"id":1,"db_name":{"O":"test","L":"test"},"charset":"utf8mb4","collate":"utf8mb4_bin","state":5})json", // - R"json({"id":31,"name":{"O":"range_part_t","L":"range_part_t"},"charset":"utf8mb4","collate":"utf8mb4_bin","cols":[{"id":1,"name":{"O":"i","L":"i"},"offset":0,"origin_default":null,"default":null,"default_bit":null,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":3,"Flag":0,"Flen":11,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":"","version":0}],"index_info":null,"fk_info":null,"state":5,"pk_is_handle":false,"schema_version":-1,"comment":"","auto_inc_id":0,"max_col_id":1,"max_idx_id":0,"update_timestamp":407445773801488390,"ShardRowIDBits":0,"partition":{"type":1,"expr":"`i`","columns":null,"enable":true,"definitions":[{"id":32,"name":{"O":"p0","L":"p0"},"less_than":["0"]},{"id":33,"name":{"O":"p1","L":"p1"},"less_than":["100"]}],"num":0},"compression":"","version":1})json", // - R"stmt(CREATE TABLE `test`.`range_part_t_32`(`i` Nullable(Int32), `_tidb_rowid` Int64) Engine = DeltaMerge((`_tidb_rowid`), '{"belonging_table_id":31,"cols":[{"comment":"","default":null,"default_bit":null,"id":1,"name":{"L":"i","O":"i"},"offset":0,"origin_default":null,"state":5,"type":{"Charset":"binary","Collate":"binary","Decimal":0,"Elems":null,"Flag":0,"Flen":11,"Tp":3}}],"comment":"","id":32,"index_info":[],"is_common_handle":false,"is_partition_sub_table":true,"name":{"L":"range_part_t_32","O":"range_part_t_32"},"partition":null,"pk_is_handle":false,"schema_version":-1,"state":5,"tiflash_replica":{"Count":0},"update_timestamp":407445773801488390}'))stmt", // - }}; + StmtCase{ + 2049, // + R"json({"id":1939,"db_name":{"O":"customer","L":"customer"},"charset":"utf8mb4","collate":"utf8mb4_bin","state":5})json", // + R"json({"id":2049,"name":{"O":"customerdebt","L":"customerdebt"},"cols":[{"id":1,"name":{"O":"id","L":"id"},"offset":0,"origin_default":null,"default":null,"default_bit":null,"type":{"Tp":8,"Flag":515,"Flen":20,"Decimal":0},"state":5,"comment":"i\"d"}],"state":5,"pk_is_handle":true,"schema_version":-1,"comment":"负债信息","update_timestamp":404545295996944390,"partition":null})json", // + R"stmt(CREATE TABLE `customer`.`customerdebt`(`id` Int64) Engine = DeltaMerge((`id`), '{"cols":[{"comment":"i\\"d","default":null,"default_bit":null,"id":1,"name":{"L":"id","O":"id"},"offset":0,"origin_default":null,"state":5,"type":{"Charset":null,"Collate":null,"Decimal":0,"Elems":null,"Flag":515,"Flen":20,"Tp":8}}],"comment":"\\u8D1F\\u503A\\u4FE1\\u606F","id":2049,"index_info":[],"is_common_handle":false,"name":{"L":"customerdebt","O":"customerdebt"},"partition":null,"pk_is_handle":true,"schema_version":-1,"state":5,"tiflash_replica":{"Count":0},"update_timestamp":404545295996944390}'))stmt", // + }, + StmtCase{ + 31, // + R"json({"id":1,"db_name":{"O":"db1","L":"db1"},"charset":"utf8mb4","collate":"utf8mb4_bin","state":5})json", // + R"json({"id":31,"name":{"O":"simple_t","L":"simple_t"},"charset":"","collate":"","cols":[{"id":1,"name":{"O":"i","L":"i"},"offset":0,"origin_default":null,"default":null,"default_bit":null,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":3,"Flag":0,"Flen":11,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":""}],"index_info":null,"fk_info":null,"state":5,"pk_is_handle":false,"schema_version":-1,"comment":"","auto_inc_id":0,"max_col_id":1,"max_idx_id":0,"update_timestamp":404545295996944390,"ShardRowIDBits":0,"partition":null})json", // + R"stmt(CREATE TABLE `db1`.`simple_t`(`i` Nullable(Int32), `_tidb_rowid` Int64) Engine = DeltaMerge((`_tidb_rowid`), '{"cols":[{"comment":"","default":null,"default_bit":null,"id":1,"name":{"L":"i","O":"i"},"offset":0,"origin_default":null,"state":5,"type":{"Charset":"binary","Collate":"binary","Decimal":0,"Elems":null,"Flag":0,"Flen":11,"Tp":3}}],"comment":"","id":31,"index_info":[],"is_common_handle":false,"name":{"L":"simple_t","O":"simple_t"},"partition":null,"pk_is_handle":false,"schema_version":-1,"state":5,"tiflash_replica":{"Count":0},"update_timestamp":404545295996944390}'))stmt", // + }, + StmtCase{ + 33, // + R"json({"id":2,"db_name":{"O":"db2","L":"db2"},"charset":"utf8mb4","collate":"utf8mb4_bin","state":5})json", // + R"json({"id":33,"name":{"O":"pk_t","L":"pk_t"},"charset":"","collate":"","cols":[{"id":1,"name":{"O":"i","L":"i"},"offset":0,"origin_default":null,"default":null,"default_bit":null,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":3,"Flag":3,"Flen":11,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":""}],"index_info":null,"fk_info":null,"state":5,"pk_is_handle":true,"schema_version":-1,"comment":"","auto_inc_id":0,"max_col_id":1,"max_idx_id":0,"update_timestamp":404545312978108418,"ShardRowIDBits":0,"partition":null})json", // + R"stmt(CREATE TABLE `db2`.`pk_t`(`i` Int32) Engine = DeltaMerge((`i`), '{"cols":[{"comment":"","default":null,"default_bit":null,"id":1,"name":{"L":"i","O":"i"},"offset":0,"origin_default":null,"state":5,"type":{"Charset":"binary","Collate":"binary","Decimal":0,"Elems":null,"Flag":3,"Flen":11,"Tp":3}}],"comment":"","id":33,"index_info":[],"is_common_handle":false,"name":{"L":"pk_t","O":"pk_t"},"partition":null,"pk_is_handle":true,"schema_version":-1,"state":5,"tiflash_replica":{"Count":0},"update_timestamp":404545312978108418}'))stmt", // + }, + StmtCase{ + 35, // + R"json({"id":1,"db_name":{"O":"db1","L":"db1"},"charset":"utf8mb4","collate":"utf8mb4_bin","state":5})json", // + R"json({"id":35,"name":{"O":"not_null_t","L":"not_null_t"},"charset":"","collate":"","cols":[{"id":1,"name":{"O":"i","L":"i"},"offset":0,"origin_default":null,"default":null,"default_bit":null,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":3,"Flag":4097,"Flen":11,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":""}],"index_info":null,"fk_info":null,"state":5,"pk_is_handle":false,"schema_version":-1,"comment":"","auto_inc_id":0,"max_col_id":1,"max_idx_id":0,"update_timestamp":404545324922961926,"ShardRowIDBits":0,"partition":null})json", // + R"stmt(CREATE TABLE `db1`.`not_null_t`(`i` Int32, `_tidb_rowid` Int64) Engine = DeltaMerge((`_tidb_rowid`), '{"cols":[{"comment":"","default":null,"default_bit":null,"id":1,"name":{"L":"i","O":"i"},"offset":0,"origin_default":null,"state":5,"type":{"Charset":"binary","Collate":"binary","Decimal":0,"Elems":null,"Flag":4097,"Flen":11,"Tp":3}}],"comment":"","id":35,"index_info":[],"is_common_handle":false,"name":{"L":"not_null_t","O":"not_null_t"},"partition":null,"pk_is_handle":false,"schema_version":-1,"state":5,"tiflash_replica":{"Count":0},"update_timestamp":404545324922961926}'))stmt", // + }, + StmtCase{ + 37, // + R"json({"id":2,"db_name":{"O":"db2","L":"db2"},"charset":"utf8mb4","collate":"utf8mb4_bin","state":5})json", + R"json({"id":37,"name":{"O":"mytable","L":"mytable"},"charset":"","collate":"","cols":[{"id":1,"name":{"O":"mycol","L":"mycol"},"offset":0,"origin_default":null,"default":null,"default_bit":null,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":15,"Flag":4099,"Flen":256,"Decimal":0,"Charset":"utf8","Collate":"utf8_bin","Elems":null},"state":5,"comment":""}],"index_info":[{"id":1,"idx_name":{"O":"PRIMARY","L":"primary"},"tbl_name":{"O":"","L":""},"idx_cols":[{"name":{"O":"mycol","L":"mycol"},"offset":0,"length":-1}],"is_unique":true,"is_primary":true,"state":5,"comment":"","index_type":1}],"fk_info":null,"state":5,"pk_is_handle":true,"schema_version":-1,"comment":"","auto_inc_id":0,"max_col_id":1,"max_idx_id":1,"update_timestamp":404566455285710853,"ShardRowIDBits":0,"partition":null})json", // + R"stmt(CREATE TABLE `db2`.`mytable`(`mycol` String) Engine = DeltaMerge((`mycol`), '{"cols":[{"comment":"","default":null,"default_bit":null,"id":1,"name":{"L":"mycol","O":"mycol"},"offset":0,"origin_default":null,"state":5,"type":{"Charset":"utf8","Collate":"utf8_bin","Decimal":0,"Elems":null,"Flag":4099,"Flen":256,"Tp":15}}],"comment":"","id":37,"index_info":[],"is_common_handle":false,"name":{"L":"mytable","O":"mytable"},"partition":null,"pk_is_handle":true,"schema_version":-1,"state":5,"tiflash_replica":{"Count":0},"update_timestamp":404566455285710853}'))stmt", // + }, + StmtCase{ + 32, // + R"json({"id":1,"db_name":{"O":"test","L":"test"},"charset":"utf8mb4","collate":"utf8mb4_bin","state":5})json", // + R"json({"id":31,"name":{"O":"range_part_t","L":"range_part_t"},"charset":"utf8mb4","collate":"utf8mb4_bin","cols":[{"id":1,"name":{"O":"i","L":"i"},"offset":0,"origin_default":null,"default":null,"default_bit":null,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":3,"Flag":0,"Flen":11,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":"","version":0}],"index_info":null,"fk_info":null,"state":5,"pk_is_handle":false,"schema_version":-1,"comment":"","auto_inc_id":0,"max_col_id":1,"max_idx_id":0,"update_timestamp":407445773801488390,"ShardRowIDBits":0,"partition":{"type":1,"expr":"`i`","columns":null,"enable":true,"definitions":[{"id":32,"name":{"O":"p0","L":"p0"},"less_than":["0"]},{"id":33,"name":{"O":"p1","L":"p1"},"less_than":["100"]}],"num":0},"compression":"","version":1})json", // + R"stmt(CREATE TABLE `test`.`range_part_t_32`(`i` Nullable(Int32), `_tidb_rowid` Int64) Engine = DeltaMerge((`_tidb_rowid`), '{"belonging_table_id":31,"cols":[{"comment":"","default":null,"default_bit":null,"id":1,"name":{"L":"i","O":"i"},"offset":0,"origin_default":null,"state":5,"type":{"Charset":"binary","Collate":"binary","Decimal":0,"Elems":null,"Flag":0,"Flen":11,"Tp":3}}],"comment":"","id":32,"index_info":[],"is_common_handle":false,"is_partition_sub_table":true,"name":{"L":"range_part_t_32","O":"range_part_t_32"},"partition":null,"pk_is_handle":false,"schema_version":-1,"state":5,"tiflash_replica":{"Count":0},"update_timestamp":407445773801488390}'))stmt", // + }}; for (auto & c : cases) { diff --git a/dbms/src/Storages/tests/gtest_filter_parser.cpp b/dbms/src/Storages/tests/gtest_filter_parser.cpp index a027ea71cfc..8820c05d2da 100644 --- a/dbms/src/Storages/tests/gtest_filter_parser.cpp +++ b/dbms/src/Storages/tests/gtest_filter_parser.cpp @@ -25,10 +25,10 @@ #include #include #include -#include -#include #include #include +#include +#include #include #include diff --git a/dbms/src/Storages/Transaction/SchemaBuilder-internal.h b/dbms/src/TiDB/Schema/SchemaBuilder-internal.h similarity index 100% rename from dbms/src/Storages/Transaction/SchemaBuilder-internal.h rename to dbms/src/TiDB/Schema/SchemaBuilder-internal.h diff --git a/dbms/src/Storages/Transaction/SchemaBuilder.cpp b/dbms/src/TiDB/Schema/SchemaBuilder.cpp similarity index 99% rename from dbms/src/Storages/Transaction/SchemaBuilder.cpp rename to dbms/src/TiDB/Schema/SchemaBuilder.cpp index 1ed8b33d415..64d118eec3e 100644 --- a/dbms/src/Storages/Transaction/SchemaBuilder.cpp +++ b/dbms/src/TiDB/Schema/SchemaBuilder.cpp @@ -35,11 +35,11 @@ #include #include #include -#include -#include -#include #include #include +#include +#include +#include #include #include @@ -1078,7 +1078,7 @@ void SchemaBuilder::applyCreatePhysicalTable(DBInfoPtr db_in ParserCreateQuery parser; ASTPtr ast = parseQuery(parser, stmt.data(), stmt.data() + stmt.size(), "from syncSchema " + table_info->name, 0); - ASTCreateQuery * ast_create_query = typeid_cast(ast.get()); + auto * ast_create_query = typeid_cast(ast.get()); ast_create_query->attach = true; ast_create_query->if_not_exists = true; ast_create_query->database = name_mapper.mapDatabaseName(*db_info); diff --git a/dbms/src/Storages/Transaction/SchemaBuilder.h b/dbms/src/TiDB/Schema/SchemaBuilder.h similarity index 98% rename from dbms/src/Storages/Transaction/SchemaBuilder.h rename to dbms/src/TiDB/Schema/SchemaBuilder.h index fcfba7db57b..8446765f74a 100644 --- a/dbms/src/Storages/Transaction/SchemaBuilder.h +++ b/dbms/src/TiDB/Schema/SchemaBuilder.h @@ -15,8 +15,8 @@ #pragma once #include -#include #include +#include namespace DB { diff --git a/dbms/src/Storages/Transaction/SchemaGetter.cpp b/dbms/src/TiDB/Schema/SchemaGetter.cpp similarity index 83% rename from dbms/src/Storages/Transaction/SchemaGetter.cpp rename to dbms/src/TiDB/Schema/SchemaGetter.cpp index a8dbf8befb7..7f52f9301b1 100644 --- a/dbms/src/Storages/Transaction/SchemaGetter.cpp +++ b/dbms/src/TiDB/Schema/SchemaGetter.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include namespace DB @@ -103,14 +103,14 @@ struct TxnStructure } public: - static String Get(pingcap::kv::Snapshot & snap, const String & key) + static String get(pingcap::kv::Snapshot & snap, const String & key) { String encode_key = encodeStringDataKey(key); String value = snap.Get(encode_key); return value; } - static String HGet(pingcap::kv::Snapshot & snap, const String & key, const String & field) + static String hGet(pingcap::kv::Snapshot & snap, const String & key, const String & field) { String encode_key = encodeHashDataKey(key, field); String value = snap.Get(encode_key); @@ -118,7 +118,7 @@ struct TxnStructure } // For convinient, we only return values. - static std::vector> HGetAll(pingcap::kv::Snapshot & snap, const String & key) + static std::vector> hGetAll(pingcap::kv::Snapshot & snap, const String & key) { auto tikv_key_prefix = hashDataKeyPrefix(key); String tikv_key_end = pingcap::kv::prefixNext(tikv_key_prefix); @@ -137,7 +137,14 @@ struct TxnStructure } }; -AffectedOption::AffectedOption(Poco::JSON::Object::Ptr json) { deserialize(json); } +AffectedOption::AffectedOption(Poco::JSON::Object::Ptr json) + : schema_id(0) + , table_id(0) + , old_table_id(0) + , old_schema_id(0) +{ + deserialize(json); +} void AffectedOption::deserialize(Poco::JSON::Object::Ptr json) { @@ -175,19 +182,22 @@ void SchemaDiff::deserialize(const String & data) Int64 SchemaGetter::getVersion() { - String ver = TxnStructure::Get(snap, schemaVersionKey); - if (ver == "") + String ver = TxnStructure::get(snap, schemaVersionKey); + if (ver.empty()) return 0; return std::stoll(ver); } -String SchemaGetter::getSchemaDiffKey(Int64 ver) { return std::string(schemaDiffPrefix) + ":" + std::to_string(ver); } +String SchemaGetter::getSchemaDiffKey(Int64 ver) +{ + return std::string(schemaDiffPrefix) + ":" + std::to_string(ver); +} SchemaDiff SchemaGetter::getSchemaDiff(Int64 ver) { String key = getSchemaDiffKey(ver); - String data = TxnStructure::Get(snap, key); - if (data == "") + String data = TxnStructure::get(snap, key); + if (data.empty()) { throw TiFlashException("cannot find schema diff for version: " + std::to_string(ver), Errors::Table::SyncError); } @@ -196,16 +206,22 @@ SchemaDiff SchemaGetter::getSchemaDiff(Int64 ver) return diff; } -String SchemaGetter::getDBKey(DatabaseID db_id) { return String(DBPrefix) + ":" + std::to_string(db_id); } +String SchemaGetter::getDBKey(DatabaseID db_id) +{ + return String(DBPrefix) + ":" + std::to_string(db_id); +} -String SchemaGetter::getTableKey(TableID table_id) { return String(TablePrefix) + ":" + std::to_string(table_id); } +String SchemaGetter::getTableKey(TableID table_id) +{ + return String(TablePrefix) + ":" + std::to_string(table_id); +} TiDB::DBInfoPtr SchemaGetter::getDatabase(DatabaseID db_id) { String key = getDBKey(db_id); - String json = TxnStructure::HGet(snap, DBs, key); + String json = TxnStructure::hGet(snap, DBs, key); - if (json == "") + if (json.empty()) return nullptr; LOG_DEBUG(log, "Get DB Info from TiKV : " + json); @@ -221,8 +237,8 @@ TiDB::TableInfoPtr SchemaGetter::getTableInfo(DatabaseID db_id, TableID table_id throw Exception(); } String table_key = getTableKey(table_id); - String table_info_json = TxnStructure::HGet(snap, db_key, table_key); - if (table_info_json == "") + String table_info_json = TxnStructure::hGet(snap, db_key, table_key); + if (table_info_json.empty()) return nullptr; LOG_DEBUG(log, "Get Table Info from TiKV : " + table_info_json); TiDB::TableInfoPtr table_info = std::make_shared(table_info_json); @@ -232,8 +248,8 @@ TiDB::TableInfoPtr SchemaGetter::getTableInfo(DatabaseID db_id, TableID table_id std::vector SchemaGetter::listDBs() { std::vector res; - auto pairs = TxnStructure::HGetAll(snap, DBs); - for (auto pair : pairs) + auto pairs = TxnStructure::hGetAll(snap, DBs); + for (const auto & pair : pairs) { auto db_info = std::make_shared(pair.second); res.push_back(db_info); @@ -243,8 +259,8 @@ std::vector SchemaGetter::listDBs() bool SchemaGetter::checkDBExists(const String & key) { - String value = TxnStructure::HGet(snap, DBs, key); - return value.size() > 0; + String value = TxnStructure::hGet(snap, DBs, key); + return !value.empty(); } std::vector SchemaGetter::listTables(DatabaseID db_id) @@ -257,9 +273,9 @@ std::vector SchemaGetter::listTables(DatabaseID db_id) std::vector res; - auto kv_pairs = TxnStructure::HGetAll(snap, db_key); + auto kv_pairs = TxnStructure::hGetAll(snap, db_key); - for (auto kv_pair : kv_pairs) + for (const auto & kv_pair : kv_pairs) { const String & key = kv_pair.first; if (key.rfind(TablePrefix, 0) != 0) diff --git a/dbms/src/Storages/Transaction/SchemaGetter.h b/dbms/src/TiDB/Schema/SchemaGetter.h similarity index 96% rename from dbms/src/Storages/Transaction/SchemaGetter.h rename to dbms/src/TiDB/Schema/SchemaGetter.h index efcac11f626..cfa5e1c6335 100644 --- a/dbms/src/Storages/Transaction/SchemaGetter.h +++ b/dbms/src/TiDB/Schema/SchemaGetter.h @@ -139,13 +139,13 @@ struct SchemaGetter SchemaDiff getSchemaDiff(Int64 ver); - String getSchemaDiffKey(Int64 ver); + static String getSchemaDiffKey(Int64 ver); bool checkDBExists(const String & key); - String getDBKey(DatabaseID db_id); + static String getDBKey(DatabaseID db_id); - String getTableKey(TableID table_id); + static String getTableKey(TableID table_id); TiDB::DBInfoPtr getDatabase(DatabaseID db_id); diff --git a/dbms/src/Storages/Transaction/SchemaNameMapper.h b/dbms/src/TiDB/Schema/SchemaNameMapper.h similarity index 100% rename from dbms/src/Storages/Transaction/SchemaNameMapper.h rename to dbms/src/TiDB/Schema/SchemaNameMapper.h diff --git a/dbms/src/Storages/Transaction/SchemaSyncService.cpp b/dbms/src/TiDB/Schema/SchemaSyncService.cpp similarity index 98% rename from dbms/src/Storages/Transaction/SchemaSyncService.cpp rename to dbms/src/TiDB/Schema/SchemaSyncService.cpp index a22578b51fc..92eb700766b 100644 --- a/dbms/src/Storages/Transaction/SchemaSyncService.cpp +++ b/dbms/src/TiDB/Schema/SchemaSyncService.cpp @@ -19,10 +19,10 @@ #include #include #include -#include -#include -#include #include +#include +#include +#include #include namespace DB diff --git a/dbms/src/Storages/Transaction/SchemaSyncService.h b/dbms/src/TiDB/Schema/SchemaSyncService.h similarity index 100% rename from dbms/src/Storages/Transaction/SchemaSyncService.h rename to dbms/src/TiDB/Schema/SchemaSyncService.h diff --git a/dbms/src/Storages/Transaction/SchemaSyncer.h b/dbms/src/TiDB/Schema/SchemaSyncer.h similarity index 100% rename from dbms/src/Storages/Transaction/SchemaSyncer.h rename to dbms/src/TiDB/Schema/SchemaSyncer.h diff --git a/dbms/src/Storages/Transaction/TiDBSchemaSyncer.h b/dbms/src/TiDB/Schema/TiDBSchemaSyncer.h similarity index 99% rename from dbms/src/Storages/Transaction/TiDBSchemaSyncer.h rename to dbms/src/TiDB/Schema/TiDBSchemaSyncer.h index c9b2f2aa567..b682abf1af4 100644 --- a/dbms/src/Storages/Transaction/TiDBSchemaSyncer.h +++ b/dbms/src/TiDB/Schema/TiDBSchemaSyncer.h @@ -18,9 +18,9 @@ #include #include #include -#include #include #include +#include #include #include From 335b124cab124828aadc318b7355f8390950f032 Mon Sep 17 00:00:00 2001 From: Wenxuan Date: Mon, 30 May 2022 14:54:26 +0800 Subject: [PATCH 075/127] docs: Add rfc for compact table via sql (#4929) ref pingcap/tiflash#4897 --- .../2022-05-19-compact-table-via-sql.md | 224 ++++++++++++++++++ .../2022-05-19-compact-table-via-sql-1.png | Bin 0 -> 96999 bytes .../2022-05-19-compact-table-via-sql-2.png | Bin 0 -> 75093 bytes .../2022-05-19-compact-table-via-sql-3.png | Bin 0 -> 124588 bytes .../2022-05-19-compact-table-via-sql-4.png | Bin 0 -> 52004 bytes .../2022-05-19-compact-table-via-sql-5.png | Bin 0 -> 63735 bytes .../2022-05-19-compact-table-via-sql-6.png | Bin 0 -> 101035 bytes .../2022-05-19-compact-table-via-sql-7.png | Bin 0 -> 122437 bytes 8 files changed, 224 insertions(+) create mode 100644 docs/design/2022-05-19-compact-table-via-sql.md create mode 100644 docs/design/images/2022-05-19-compact-table-via-sql-1.png create mode 100644 docs/design/images/2022-05-19-compact-table-via-sql-2.png create mode 100644 docs/design/images/2022-05-19-compact-table-via-sql-3.png create mode 100644 docs/design/images/2022-05-19-compact-table-via-sql-4.png create mode 100644 docs/design/images/2022-05-19-compact-table-via-sql-5.png create mode 100644 docs/design/images/2022-05-19-compact-table-via-sql-6.png create mode 100644 docs/design/images/2022-05-19-compact-table-via-sql-7.png diff --git a/docs/design/2022-05-19-compact-table-via-sql.md b/docs/design/2022-05-19-compact-table-via-sql.md new file mode 100644 index 00000000000..c2f9aeff22f --- /dev/null +++ b/docs/design/2022-05-19-compact-table-via-sql.md @@ -0,0 +1,224 @@ +# Compact Table via SQL + +- Author(s): [Wish](http://github.com/breezewish) + +## Introduction + +This RFC introduces a compaction SQL command in TiDB. The command triggers a +compaction on TiFlash replicas, which can be used to: + +1. Migrate from PageStorage v2 to PageStorage v3 +2. Optimize performance by better organizing the data + +## Motivation or Background + +Recently the PageStorage v3 engine was introduced to TiFlash. By allowing +users to manually trigger the compaction, the migration from PageStorage v2 to +PageStorage v3 can be done easily, as a compaction command will merge and clear +all delta layer data (which was stored in v2) into the stable layer, while new +delta layer data will be stored in PageStorage v3. + +As a bonus, even when the Delta layer is already stored in PageStorage v3, users +can also benefit from this manual compaction command, considering that +compaction will rewrite the stored data into a better organized state. + +## Product Behavior + +New SQL syntax: + +```sql +ALTER TABLE table_name COMPACT [engine_type REPLICA] +-- engine_type could be either TIKV or TIFLASH +``` + +Sample SQLs: + +```sql +ALTER TABLE `users` COMPACT; -- Implicit: Not recommended +ALTER TABLE `users` COMPACT TIFLASH REPLICA; -- Explicit: Recommended +``` + +- The compaction is triggered immediately and runs in the foreground. The SQL + won’t return until the compaction is done. +- When a table is already in compacting progress, the new compaction SQL command + involving this table will be exited immediately by producing a “canceled” + warning. +- The compact SQL commands can be executed on different tables simultaneously, + resulting in multiple tables being compacted simultaneously. +- When `engine_type` is specified as `TIKV`, “unsupported” error will be + returned. When `engine_type` is not specified, the SQL will run as compact + TiFlash replicas only. This behavior will change when we support TiKV compaction + in future. +- When the table contains multiple partitions, all partitions will be compacted. +- The compact command will exit in the following ways: + 1. User kill SQL via `KILL [TIDB] `: stop task immediately + 2. TiDB stopped: the compaction run on TiFlash should be stopped. There will + be no retries after TiDB is restarted. + 3. TiFlash stopped: the compaction command should stop and return error + immediately. There will be no retries after TiFlash is restarted. + 4. Compaction is finished. + +## Detailed Design + +### Protocol + +New APIs will be added to TiFlash: + +```protobuf +// Pseudo code + +message Error { + oneof { + ErrorCompactInProgress, + ErrorTooManyPendingTasks, + // More errors can be added in future + } +} + +message ErrorCompactInProgress {} + +message ErrorTooManyPendingTasks {} + +message CompactRequest { + bytes id + bytes start_key // Optional + bytes max_end_key // Optional + int64 physical_table_id // Used to locate the TiFlsh table +} + +message CompactResponse { + optional Error error + bool has_remaining + bytes compacted_start_key + bytes compacted_end_key +} +``` + +### General Flow + +![](./images/2022-05-19-compact-table-via-sql-1.png) + +TiDB sends `CompactRequest` to one TiFlash instance in series. Each request +compacts one or multiple Segments in the TiFlash instance. The segments may +change (e.g. split or merge) during the SQL execution process so that TiFlash +will respond with the end key for each request, which will be then used as the +`StartKey` for the next request sent from TiDB. + +When there are multiple TiFlash instances, TiDB talks with each TiFlash +concurrently. However, for each TiFlash connection, requests are still sent in +series. Newly added TiFlash instances during a compaction SQL execution are +discarded. + +### Interrupt Execution (Kill) + +![](./images/2022-05-19-compact-table-via-sql-2.png) + +When user executes `KILL [TIDB] ` to interrupt the compaction command, +TiDB simply stops the execution by not sending future `CompactRequest`. +There will be `CompactRequest` running in TiFlash instances at the moment when +user initiates the `KILL`. These running "small" compaction tasks will be +untouched and kept running until finished. There is no way to stop a running +`CompactRequest` in TiFlash. + +### On TiDB Restart + +Similar to Kill, TiDB does not need to do anything extra after the restart. +TiFlash will be returned to idle after currently running `CompactRequest` are +finished. + +### On Request Error + +The `CompactRequest` from TiDB to TiFlash may fail for several reasons, for +example, encountering network failures or receiving errors from TiFlash. +In such case, there will be no more future `CompactRequest` sent to this +failed TiFlash instance during the compaction command. Requests will +continue sending to other TiFlash instances. + +![](./images/2022-05-19-compact-table-via-sql-3.png) + +### Service Endpoint + +A new gRPC endpoint will be added to the tikv gRPC service: + +```protobuf +service Tikv { + // Existing endpoints + rpc KvGet(kvrpcpb.GetRequest) returns (kvrpcpb.GetResponse) {} + rpc KvScan(kvrpcpb.ScanRequest) returns (kvrpcpb.ScanResponse) {} + rpc KvPrewrite(kvrpcpb.PrewriteRequest) returns (kvrpcpb.PrewriteResponse) {} + ... + + // Newly added management endpoint + rpc Compact(managementpb.CompactRequest) returns (managementpb.CompactResponse) {} +} +``` + +### Handling CompactRequest in TiFlash + +![](./images/2022-05-19-compact-table-via-sql-4.png) + +The `CompactRequest` will be processed one by one in a new thread pool with 1 +worker thread. When one request is being processed, TiFlash locates the Segment +according to `start_key` and then performs a foreground Delta Merge. + +The number of worker thread can be configured in case of users want more table +compaction concurrency. Note that compaction is always stepped in serial for a +single table, even if there are more than 1 worker threads. + +### Compact Multiple Segments in One Request + +When Delta Merge takes a short time, TiFlash repeats itself to compact more +Segments, until either: + +- 1 minute (can be configured) has elapsed since receiving the request +- There are no more Segments in `[start_key, max_end_key)` + This can speed up the compaction process when there are many compacted + segments by reducing round trips. + +![](./images/2022-05-19-compact-table-via-sql-5.png) + +### Multiple Compaction Command from Same Table + +Compacting the same table concurrently doesn't make sense and leads to extra +resource costs, so that we would like to avoid concurrent compactions for the same +table, allowing only one to be executed. + +In order to detect such cases, an `ID` field is attached to the `CompactRequest`, +which will be set as the Table ID in TiDB. TiFlash adds ID to a map when the +request is received, and removes ID from the map when the response is going +to be returned. If ID exists in the map, `ErrorCompactInProgress` will be +returned immediately, without processing the request in the thread pool at all. + +![](./images/2022-05-19-compact-table-via-sql-6.png) + +### Multiple Compaction Command from Different Tables + +As there is only one worker thread, when user invokes multiple compaction +command concurrently, these compactions will be stepped evenly, instead of +following a FIFO order, as demostrated below: + +![](./images/2022-05-19-compact-table-via-sql-7.png) + +If there are too many queued requests (>N), new requests will be rejected +and `ErrorTooManyPendingTasks` will be returned. This effectively means, the +number of concurrent running compact commands will be limited to max N. + +# Investigation & Alternatives + +- The Compact API can be placed in TiKV Debug Service (`debugpb`). There is even + existing `debugpb.CompactRequest`. However, + - All TiKV Debug Service endpoints are only used by tikv-ctl, instead of + TiDB or any other clients now. + - `debugpb.CompactRequest` is not suitable to be used by TiFlash, containing + too many TiKV specific fields and lacking fields for TiFlash. It will also be + hard to modify it while keeping compatibility + clean. +- The Compact API can be provided via `DBGInvoker`, which means it available via + Clickhouse Client SQL or TiFlash HTTP Service. This is also the home of most + management or debug APIs of TiFlash. However, + - Currently TiDB does not talk to TiFlash in this way. + - We require this API to be stable for TiDB to use. All APIs in DBGInvoker + are human-facing, not producing machine readable and stable output now. + +# Unresolved Questions + +None. diff --git a/docs/design/images/2022-05-19-compact-table-via-sql-1.png b/docs/design/images/2022-05-19-compact-table-via-sql-1.png new file mode 100644 index 0000000000000000000000000000000000000000..7c07de7e433c5f36ce76659b7cdf080ab81c56d4 GIT binary patch literal 96999 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 ?8R383;$|nsXrQSBS8#Gcz(siYc=#Bl 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 z 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 Zt)eXw>W!AQk6i`-wAA(0?y6b^{0|f;vH<`9 literal 0 HcmV?d00001 diff --git a/docs/design/images/2022-05-19-compact-table-via-sql-3.png b/docs/design/images/2022-05-19-compact-table-via-sql-3.png new file mode 100644 index 0000000000000000000000000000000000000000..779cbedb93021b28d7338e8613596a3b73324057 GIT binary patch literal 124588 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⁢z8iaDc1DD^FrUeU{0X@beF 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

  • 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�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 z/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 LDHoo#4Ea9*@@fBd literal 0 HcmV?d00001 diff --git a/docs/design/images/2022-05-19-compact-table-via-sql-7.png b/docs/design/images/2022-05-19-compact-table-via-sql-7.png new file mode 100644 index 0000000000000000000000000000000000000000..64f8025c72596a88f7d6d30450b763166d0cbf56 GIT binary patch literal 122437 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≺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 Date: Tue, 31 May 2022 02:44:26 +0800 Subject: [PATCH 076/127] Fix broken unit test for `--gtest_filter='*StorageDeltaMergeTest*:*RegionKVStoreTest*'` (#4903) close pingcap/tiflash#4904 --- .../tests/gtest_dm_storage_delta_merge.cpp | 20 ++++-- .../Transaction/tests/gtest_kvstore.cpp | 23 ++++++- tests/docker/config/tics_dt.toml | 13 +--- .../docker/config/tiflash_dt_async_grpc.toml | 69 ------------------- .../tiflash_dt_disable_local_tunnel.toml | 69 ------------------- 5 files changed, 38 insertions(+), 156 deletions(-) diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp index a26471cfe01..1cb735a2b65 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -168,6 +169,8 @@ try } EXPECT_EQ(total_segment_rows, num_rows_read); storage->drop(); + // remove the storage from TiFlash context manually + storage->removeFromTMTContext(); } CATCH @@ -252,6 +255,8 @@ try ASSERT_EQ(storage->getDatabaseName(), new_db_name); storage->drop(); + // remove the storage from TiFlash context manually + storage->removeFromTMTContext(); } CATCH @@ -315,6 +320,8 @@ try ASSERT_EQ(sort_desc.front().nulls_direction, sort_desc2.front().nulls_direction); storage->drop(); + // remove the storage from TiFlash context manually + storage->removeFromTMTContext(); } CATCH @@ -609,6 +616,8 @@ try sample.insert(DB::tests::createColumn( Strings(100, "a"), "col2")); + constexpr TiDB::TableID table_id = 1; + const String table_name = fmt::format("t_{}", table_id); Context ctx = DMTestEnv::getContext(); std::shared_ptr storage; @@ -631,12 +640,11 @@ try path.remove(true); // primary_expr_ast - const String table_name = "t_1233"; ASTPtr astptr(new ASTIdentifier(table_name, ASTIdentifier::Kind::Table)); astptr->children.emplace_back(new ASTIdentifier("col1")); TiDB::TableInfo tidb_table_info; - tidb_table_info.id = 1; + tidb_table_info.id = table_id; storage = StorageDeltaMerge::create("TiFlash", /* db_name= */ "default", @@ -692,8 +700,8 @@ try { Field res; c->get(i, res); - ASSERT(!res.isNull()); - ASSERT(res.get() == 1); + ASSERT_TRUE(!res.isNull()); + ASSERT_EQ(res.get(), table_id); } } } @@ -701,6 +709,8 @@ try in->readSuffix(); ASSERT_EQ(num_rows_read, sample.rows()); storage->drop(); + // remove the storage from TiFlash context manually + storage->removeFromTMTContext(); } CATCH @@ -848,6 +858,8 @@ try ASSERT_LT(read_data(), num_rows_write); } storage->drop(); + // remove the storage from TiFlash context manually + storage->removeFromTMTContext(); } CATCH diff --git a/dbms/src/Storages/Transaction/tests/gtest_kvstore.cpp b/dbms/src/Storages/Transaction/tests/gtest_kvstore.cpp index e93a117cc1c..2378871f71f 100644 --- a/dbms/src/Storages/Transaction/tests/gtest_kvstore.cpp +++ b/dbms/src/Storages/Transaction/tests/gtest_kvstore.cpp @@ -47,8 +47,7 @@ RegionPtr makeRegion(UInt64 id, const std::string start_key, const std::string e class RegionKVStoreTest : public ::testing::Test { public: - RegionKVStoreTest() - = default; + RegionKVStoreTest() = default; static void SetUpTestCase() {} static void testBasic(); @@ -1372,12 +1371,30 @@ void RegionKVStoreTest::testBasic() } } -TEST_F(RegionKVStoreTest, run) +TEST_F(RegionKVStoreTest, Basic) try { testBasic(); +} +CATCH + +TEST_F(RegionKVStoreTest, KVStore) +try +{ testKVStore(); +} +CATCH + +TEST_F(RegionKVStoreTest, Region) +try +{ testRegion(); +} +CATCH + +TEST_F(RegionKVStoreTest, ReadIndex) +try +{ testReadIndex(); } CATCH diff --git a/tests/docker/config/tics_dt.toml b/tests/docker/config/tics_dt.toml index 89147f80c7d..56bef659cb7 100644 --- a/tests/docker/config/tics_dt.toml +++ b/tests/docker/config/tics_dt.toml @@ -13,25 +13,16 @@ # limitations under the License. tmp_path = "/tmp/tiflash/data/tmp" -display_name = "TiFlash" -# specify paths used for store data, multiple path should be seperated by comma path = "/tmp/tiflash/data/db" -capacity = "107374182400" -# multi-paths example -# path = "/tmp/tiflash/data/db,/tmp/tiflash1,/tmp/tiflash2" -# capacity = "0,0,0" +capacity = "107374182400" # 100GB mark_cache_size = 5368709120 minmax_index_cache_size = 5368709120 tcp_port = 9000 http_port = 8123 + [logger] count = 10 errorlog = "/tmp/tiflash/log/error.log" size = "1000M" log = "/tmp/tiflash/log/server.log" level = "trace" -[application] -runAsDaemon = true -[raft] -# specify which storage engine we use. tmt or dt -storage_engine = "dt" diff --git a/tests/docker/config/tiflash_dt_async_grpc.toml b/tests/docker/config/tiflash_dt_async_grpc.toml index 3c67c37db33..bf31c61cfa8 100644 --- a/tests/docker/config/tiflash_dt_async_grpc.toml +++ b/tests/docker/config/tiflash_dt_async_grpc.toml @@ -13,71 +13,15 @@ # limitations under the License. tmp_path = "/tmp/tiflash/data/tmp" -display_name = "TiFlash" -## Deprecated storage path setting style. Check [storage] section for new style. path = "/tmp/tiflash/data/db" capacity = "10737418240" -## Deprecated storage path setting style of multi-disks. Check [storage] section for new style. -# path = "/tmp/tiflash/data/db,/tmp/tiflash1,/tmp/tiflash2" -# capacity = "0" mark_cache_size = 5368709120 minmax_index_cache_size = 5368709120 tcp_port = 9000 http_port = 8123 -## Storage paths settings. -# [storage] -## The storage format version in storage engine. Valid values: 1, 2 (experimental). -## format_version = 1 - -## If there are multiple SSD disks on the machine, -## specify the path list on `storage.main.dir` can improve TiFlash performance. - -## If there are multiple disks with different IO metrics (e.g. one SSD and some HDDs) -## on the machine, -## set `storage.latest.dir` to store the latest data on SSD (disks with higher IOPS metrics) -## set `storage.main.dir` to store the main data on HDD (disks with lower IOPS metrics) -## can improve TiFlash performance. - -# [storage.main] -## The path to store main data. -# e.g. -# dir = [ "/data0/tiflash" ] -# or -# dir = [ "/data0/tiflash", "/data1/tiflash" ] - -## Store capacity of each path, i.e. max data size allowed. -## If it is not set, or is set to 0s, the actual disk capacity is used. -## Note that we don't support human-readable big numbers(like "10GB") yet. -## Please set in the specified number of bytes. -# e.g. -# capacity = [ 10737418240, 10737418240 ] - -# [storage.latest] -## The path(s) to store latest data. -## If not set, it will be the same with `storage.main.dir`. -# dir = [ ] - -## Store capacity of each path, i.e. max data size allowed. -## If it is not set, or is set to 0s, the actual disk capacity is used. -# e.g. -# capacity = [ 10737418240, 10737418240 ] - -# [storage.raft] -## The path(s) to store Raft data. -## If not set, it will be the paths in `storage.latest.dir` appended with "/kvstore". -# dir = [ ] - -# [storage.io_rate_limit] -## The max I/O bandwith. Default value is 0 and I/O rate limit is disabled. -# max_bytes_per_sec = 268435456 -## max_read_bytes_per_sec and max_write_bytes_per_sec are the same meaning as max_bytes_per_sec, -## but for disk that read bandwidth and write bandwith are calculated separatly, such as GCP's persistent disks. -# max_read_bytes_per_sec = 0 -# max_write_bytes_per_sec = 0 - [flash] tidb_status_addr = "tidb0:10080" service_addr = "0.0.0.0:3930" @@ -100,22 +44,9 @@ size = "1000M" log = "/tmp/tiflash/log/server.log" level = "trace" -[application] -runAsDaemon = true - [raft] pd_addr = "pd0:2379" ignore_databases = "system,default" -# specify which storage engine we use. tmt or dt -storage_engine = "dt" -# Deprecated Raft data storage path setting style. Check [storage.raft] section for new style. -# If it is not set, it will be the first path of "path" appended with "/kvstore". -# kvstore_path = "" - -[raft.snapshot] -# The way to apply snapshot data -# The value is one of "block" / "file1" / "file2". -# method = "file1" [profiles] [profiles.default] diff --git a/tests/docker/config/tiflash_dt_disable_local_tunnel.toml b/tests/docker/config/tiflash_dt_disable_local_tunnel.toml index 23b82909776..1fb166a9a19 100644 --- a/tests/docker/config/tiflash_dt_disable_local_tunnel.toml +++ b/tests/docker/config/tiflash_dt_disable_local_tunnel.toml @@ -13,71 +13,15 @@ # limitations under the License. tmp_path = "/tmp/tiflash/data/tmp" -display_name = "TiFlash" -## Deprecated storage path setting style. Check [storage] section for new style. path = "/tmp/tiflash/data/db" capacity = "10737418240" -## Deprecated storage path setting style of multi-disks. Check [storage] section for new style. -# path = "/tmp/tiflash/data/db,/tmp/tiflash1,/tmp/tiflash2" -# capacity = "0" mark_cache_size = 5368709120 minmax_index_cache_size = 5368709120 tcp_port = 9000 http_port = 8123 -## Storage paths settings. -# [storage] -## The storage format version in storage engine. Valid values: 1, 2 (experimental). -## format_version = 1 - -## If there are multiple SSD disks on the machine, -## specify the path list on `storage.main.dir` can improve TiFlash performance. - -## If there are multiple disks with different IO metrics (e.g. one SSD and some HDDs) -## on the machine, -## set `storage.latest.dir` to store the latest data on SSD (disks with higher IOPS metrics) -## set `storage.main.dir` to store the main data on HDD (disks with lower IOPS metrics) -## can improve TiFlash performance. - -# [storage.main] -## The path to store main data. -# e.g. -# dir = [ "/data0/tiflash" ] -# or -# dir = [ "/data0/tiflash", "/data1/tiflash" ] - -## Store capacity of each path, i.e. max data size allowed. -## If it is not set, or is set to 0s, the actual disk capacity is used. -## Note that we don't support human-readable big numbers(like "10GB") yet. -## Please set in the specified number of bytes. -# e.g. -# capacity = [ 10737418240, 10737418240 ] - -# [storage.latest] -## The path(s) to store latest data. -## If not set, it will be the same with `storage.main.dir`. -# dir = [ ] - -## Store capacity of each path, i.e. max data size allowed. -## If it is not set, or is set to 0s, the actual disk capacity is used. -# e.g. -# capacity = [ 10737418240, 10737418240 ] - -# [storage.raft] -## The path(s) to store Raft data. -## If not set, it will be the paths in `storage.latest.dir` appended with "/kvstore". -# dir = [ ] - -# [storage.io_rate_limit] -## The max I/O bandwith. Default value is 0 and I/O rate limit is disabled. -# max_bytes_per_sec = 268435456 -## max_read_bytes_per_sec and max_write_bytes_per_sec are the same meaning as max_bytes_per_sec, -## but for disk that read bandwidth and write bandwith are calculated separatly, such as GCP's persistent disks. -# max_read_bytes_per_sec = 0 -# max_write_bytes_per_sec = 0 - [flash] tidb_status_addr = "tidb0:10080" service_addr = "0.0.0.0:3930" @@ -100,22 +44,9 @@ size = "1000M" log = "/tmp/tiflash/log/server.log" level = "trace" -[application] -runAsDaemon = true - [raft] pd_addr = "pd0:2379" ignore_databases = "system,default" -# specify which storage engine we use. tmt or dt -storage_engine = "dt" -# Deprecated Raft data storage path setting style. Check [storage.raft] section for new style. -# If it is not set, it will be the first path of "path" appended with "/kvstore". -# kvstore_path = "" - -[raft.snapshot] -# The way to apply snapshot data -# The value is one of "block" / "file1" / "file2". -# method = "file1" [profiles] [profiles.default] From 6afdd7496c093e08c5ef3f92c166e2cb50641595 Mon Sep 17 00:00:00 2001 From: hongyunyan <649330952@qq.com> Date: Tue, 31 May 2022 16:40:27 +0800 Subject: [PATCH 077/127] Remove useless code (#5004) close pingcap/tiflash#4998 --- dbms/src/Debug/dbgFuncRegion.cpp | 12 +- dbms/src/Server/RaftConfigParser.cpp | 6 +- .../Storages/Transaction/ApplySnapshot.cpp | 92 +-------- dbms/src/Storages/Transaction/KVStore.h | 7 +- .../Storages/Transaction/PartitionStreams.cpp | 54 ----- dbms/src/Storages/Transaction/ProxyFFI.cpp | 40 +--- dbms/src/Storages/Transaction/ProxyFFI.h | 1 - dbms/src/Storages/Transaction/Region.cpp | 41 ---- dbms/src/Storages/Transaction/Region.h | 1 - .../Transaction/RegionBlockReader.cpp | 23 +-- .../Storages/Transaction/RegionBlockReader.h | 69 +------ dbms/src/Storages/Transaction/RegionTable.h | 15 -- .../Storages/Transaction/StorageEngineType.h | 16 +- dbms/src/Storages/Transaction/TMTContext.cpp | 12 +- dbms/src/Storages/Transaction/TMTContext.h | 1 - .../Transaction/tests/gtest_kvstore.cpp | 187 ++++++++++-------- etc/config-template.toml | 4 - 17 files changed, 135 insertions(+), 446 deletions(-) diff --git a/dbms/src/Debug/dbgFuncRegion.cpp b/dbms/src/Debug/dbgFuncRegion.cpp index 7924c086508..b2024eac1d8 100644 --- a/dbms/src/Debug/dbgFuncRegion.cpp +++ b/dbms/src/Debug/dbgFuncRegion.cpp @@ -40,7 +40,7 @@ extern const int UNKNOWN_TABLE; // put_region(region_id, start, end, database_name, table_name[, partition-name]) void dbgFuncPutRegion(Context & context, const ASTs & args, DBGInvoker::Printer output) { - RegionID region_id = static_cast(safeGet(typeid_cast(*args[0]).value)); + auto region_id = static_cast(safeGet(typeid_cast(*args[0]).value)); bool has_partition_id = false; size_t args_size = args.size(); if (dynamic_cast(args[args_size - 1].get()) != nullptr) @@ -81,8 +81,8 @@ void dbgFuncPutRegion(Context & context, const ASTs & args, DBGInvoker::Printer } else { - HandleID start = static_cast(safeGet(typeid_cast(*args[1]).value)); - HandleID end = static_cast(safeGet(typeid_cast(*args[2]).value)); + auto start = static_cast(safeGet(typeid_cast(*args[1]).value)); + auto end = static_cast(safeGet(typeid_cast(*args[2]).value)); TMTContext & tmt = context.getTMTContext(); RegionPtr region = RegionBench::createRegion(table_id, region_id, start, end); @@ -107,7 +107,7 @@ void dbgFuncTryFlushRegion(Context & context, const ASTs & args, DBGInvoker::Pri throw Exception("Args not matched, should be: region-id", ErrorCodes::BAD_ARGUMENTS); } - RegionID region_id = static_cast(safeGet(typeid_cast(*args[0]).value)); + auto region_id = static_cast(safeGet(typeid_cast(*args[0]).value)); TMTContext & tmt = context.getTMTContext(); tmt.getRegionTable().tryFlushRegion(region_id); @@ -160,7 +160,7 @@ void dbgFuncDumpAllRegion(Context & context, const ASTs & args, DBGInvoker::Prin if (args.empty()) throw Exception("Args not matched, should be: table_id", ErrorCodes::BAD_ARGUMENTS); - TableID table_id = static_cast(safeGet(typeid_cast(*args[0]).value)); + auto table_id = static_cast(safeGet(typeid_cast(*args[0]).value)); bool ignore_none = false; if (args.size() > 1) @@ -190,7 +190,7 @@ void dbgFuncRemoveRegion(Context & context, const ASTs & args, DBGInvoker::Print if (args.empty()) throw Exception("Args not matched, should be: region_id", ErrorCodes::BAD_ARGUMENTS); - RegionID region_id = static_cast(safeGet(typeid_cast(*args[0]).value)); + auto region_id = static_cast(safeGet(typeid_cast(*args[0]).value)); TMTContext & tmt = context.getTMTContext(); KVStorePtr & kvstore = tmt.getKVStore(); diff --git a/dbms/src/Server/RaftConfigParser.cpp b/dbms/src/Server/RaftConfigParser.cpp index 8e146dd842e..2f0a88855cd 100644 --- a/dbms/src/Server/RaftConfigParser.cpp +++ b/dbms/src/Server/RaftConfigParser.cpp @@ -92,11 +92,7 @@ TiFlashRaftConfig TiFlashRaftConfig::parseSettings(Poco::Util::LayeredConfigurat { String snapshot_method = config.getString("raft.snapshot.method"); std::transform(snapshot_method.begin(), snapshot_method.end(), snapshot_method.begin(), [](char ch) { return std::tolower(ch); }); - if (snapshot_method == "block") - { - res.snapshot_apply_method = TiDB::SnapshotApplyMethod::Block; - } - else if (snapshot_method == "file1") + if (snapshot_method == "file1") { res.snapshot_apply_method = TiDB::SnapshotApplyMethod::DTFile_Directory; } diff --git a/dbms/src/Storages/Transaction/ApplySnapshot.cpp b/dbms/src/Storages/Transaction/ApplySnapshot.cpp index 3ed04d5ecbf..2df95fead93 100644 --- a/dbms/src/Storages/Transaction/ApplySnapshot.cpp +++ b/dbms/src/Storages/Transaction/ApplySnapshot.cpp @@ -261,75 +261,6 @@ void KVStore::onSnapshot(const RegionPtrWrap & new_region_wrap, RegionPtr old_re extern RegionPtrWithBlock::CachePtr GenRegionPreDecodeBlockData(const RegionPtr &, Context &); -/// `preHandleSnapshotToBlock` read data from SSTFiles and predoced the data as a block -RegionPreDecodeBlockDataPtr KVStore::preHandleSnapshotToBlock( - RegionPtr new_region, - const SSTViewVec snaps, - uint64_t /*index*/, - uint64_t /*term*/, - TMTContext & tmt) -{ - RegionPreDecodeBlockDataPtr cache{nullptr}; - { - decltype(bg_gc_region_data)::value_type tmp; - std::lock_guard lock(bg_gc_region_data_mutex); - if (!bg_gc_region_data.empty()) - { - tmp.swap(bg_gc_region_data.back()); - bg_gc_region_data.pop_back(); - } - } - - Stopwatch watch; - auto & ctx = tmt.getContext(); - SCOPE_EXIT({ GET_METRIC(tiflash_raft_command_duration_seconds, type_apply_snapshot_predecode).Observe(watch.elapsedSeconds()); }); - - { - LOG_FMT_INFO(log, "Pre-handle snapshot {} with {} TiKV sst files", new_region->toString(false), snaps.len); - // Iterator over all SST files and insert key-values into `new_region` - for (UInt64 i = 0; i < snaps.len; ++i) - { - const auto & snapshot = snaps.views[i]; - auto sst_reader = SSTReader{proxy_helper, snapshot}; - - uint64_t kv_size = 0; - while (sst_reader.remained()) - { - auto key = sst_reader.key(); - auto value = sst_reader.value(); - new_region->insert(snaps.views[i].type, TiKVKey(key.data, key.len), TiKVValue(value.data, value.len)); - ++kv_size; - sst_reader.next(); - } - - LOG_FMT_INFO(log, - "Decode {} got [cf: {}, kv size: {}]", - std::string_view(snapshot.path.data, snapshot.path.len), - CFToName(snapshot.type), - kv_size); - // Note that number of keys in different cf will be aggregated into one metrics - GET_METRIC(tiflash_raft_process_keys, type_apply_snapshot).Increment(kv_size); - } - { - LOG_FMT_INFO(log, "Start to pre-decode {} into block", new_region->toString()); - auto block_cache = GenRegionPreDecodeBlockData(new_region, ctx); - if (block_cache) - { - std::stringstream ss; - block_cache->toString(ss); - LOG_FMT_INFO(log, "Got pre-decode block cache {}", ss.str()); - } - else - LOG_FMT_INFO(log, "Got empty pre-decode block cache"); - - cache = std::move(block_cache); - } - LOG_FMT_INFO(log, "Pre-handle snapshot {} cost {}ms", new_region->toString(false), watch.elapsedMilliseconds()); - } - - return cache; -} - std::vector KVStore::preHandleSnapshotToFiles( RegionPtr new_region, const SSTViewVec snaps, @@ -473,8 +404,8 @@ void KVStore::handlePreApplySnapshot(const RegionPtrWrap & new_region, TMTContex LOG_FMT_INFO(log, "{} apply snapshot success", new_region->toString(false)); } -template void KVStore::handlePreApplySnapshot(const RegionPtrWithBlock &, TMTContext &); template void KVStore::handlePreApplySnapshot(const RegionPtrWithSnapshotFiles &, TMTContext &); + template void KVStore::checkAndApplySnapshot(const RegionPtrWithBlock &, TMTContext &); template void KVStore::checkAndApplySnapshot(const RegionPtrWithSnapshotFiles &, TMTContext &); template void KVStore::onSnapshot(const RegionPtrWithBlock &, RegionPtr, UInt64, TMTContext &); @@ -521,10 +452,7 @@ void KVStore::handleApplySnapshot( TMTContext & tmt) { auto new_region = genRegionPtr(std::move(region), peer_id, index, term); - if (snapshot_apply_method == TiDB::SnapshotApplyMethod::Block) - handlePreApplySnapshot(RegionPtrWithBlock{new_region, preHandleSnapshotToBlock(new_region, snaps, index, term, tmt)}, tmt); - else - handlePreApplySnapshot(RegionPtrWithSnapshotFiles{new_region, preHandleSnapshotToFiles(new_region, snaps, index, term, tmt)}, tmt); + handlePreApplySnapshot(RegionPtrWithSnapshotFiles{new_region, preHandleSnapshotToFiles(new_region, snaps, index, term, tmt)}, tmt); } EngineStoreApplyRes KVStore::handleIngestSST(UInt64 region_id, const SSTViewVec snaps, UInt64 index, UInt64 term, TMTContext & tmt) @@ -543,15 +471,12 @@ EngineStoreApplyRes KVStore::handleIngestSST(UInt64 region_id, const SSTViewVec fiu_do_on(FailPoints::force_set_sst_decode_rand, { static int num_call = 0; - switch (num_call++ % 3) + switch (num_call++ % 2) { case 0: - snapshot_apply_method = TiDB::SnapshotApplyMethod::Block; - break; - case 1: snapshot_apply_method = TiDB::SnapshotApplyMethod::DTFile_Directory; break; - case 2: + case 1: snapshot_apply_method = TiDB::SnapshotApplyMethod::DTFile_Single; break; default: @@ -576,15 +501,6 @@ EngineStoreApplyRes KVStore::handleIngestSST(UInt64 region_id, const SSTViewVec } }; - if (snapshot_apply_method == TiDB::SnapshotApplyMethod::Block) - { - // try to flush remain data in memory. - func_try_flush(); - region->handleIngestSSTInMemory(snaps, index, term); - // after `handleIngestSSTInMemory`, all data are stored in `region`, try to flush committed data into storage - func_try_flush(); - } - else { // try to flush remain data in memory. func_try_flush(); diff --git a/dbms/src/Storages/Transaction/KVStore.h b/dbms/src/Storages/Transaction/KVStore.h index ef851d67958..8673cae3ff3 100644 --- a/dbms/src/Storages/Transaction/KVStore.h +++ b/dbms/src/Storages/Transaction/KVStore.h @@ -109,12 +109,7 @@ class KVStore final : private boost::noncopyable EngineStoreApplyRes handleWriteRaftCmd(const WriteCmdsView & cmds, UInt64 region_id, UInt64 index, UInt64 term, TMTContext & tmt); void handleApplySnapshot(metapb::Region && region, uint64_t peer_id, const SSTViewVec, uint64_t index, uint64_t term, TMTContext & tmt); - RegionPreDecodeBlockDataPtr preHandleSnapshotToBlock( - RegionPtr new_region, - const SSTViewVec, - uint64_t index, - uint64_t term, - TMTContext & tmt); + std::vector /* */ preHandleSnapshotToFiles( RegionPtr new_region, const SSTViewVec, diff --git a/dbms/src/Storages/Transaction/PartitionStreams.cpp b/dbms/src/Storages/Transaction/PartitionStreams.cpp index 13840159ebb..ada792c80f7 100644 --- a/dbms/src/Storages/Transaction/PartitionStreams.cpp +++ b/dbms/src/Storages/Transaction/PartitionStreams.cpp @@ -353,60 +353,6 @@ void RegionTable::writeBlockByRegion( data_list_to_remove = std::move(*data_list_read); } -RegionTable::ReadBlockByRegionRes RegionTable::readBlockByRegion(const TiDB::TableInfo & table_info, - const ColumnsDescription & columns [[maybe_unused]], - const Names & column_names_to_read, - const RegionPtr & region, - RegionVersion region_version, - RegionVersion conf_version, - bool resolve_locks, - Timestamp start_ts, - const std::unordered_set * bypass_lock_ts, - RegionScanFilterPtr scan_filter) -{ - if (!region) - throw Exception(std::string(__PRETTY_FUNCTION__) + ": region is null", ErrorCodes::LOGICAL_ERROR); - - // Tiny optimization for queries that need only handle, tso, delmark. - bool need_value = column_names_to_read.size() != 3; - auto region_data_lock = resolveLocksAndReadRegionData( - table_info.id, - region, - start_ts, - bypass_lock_ts, - region_version, - conf_version, - resolve_locks, - need_value); - - return std::visit(variant_op::overloaded{ - [&](RegionDataReadInfoList & data_list_read) -> ReadBlockByRegionRes { - /// Read region data as block. - Block block; - // FIXME: remove this deprecated function - assert(0); - { - auto reader = RegionBlockReader(nullptr); - bool ok = reader.setStartTs(start_ts) - .setFilter(scan_filter) - .read(block, data_list_read, /*force_decode*/ true); - if (!ok) - // TODO: Enrich exception message. - throw Exception("Read region " + std::to_string(region->id()) + " of table " - + std::to_string(table_info.id) + " failed", - ErrorCodes::LOGICAL_ERROR); - } - return block; - }, - [&](LockInfoPtr & lock_value) -> ReadBlockByRegionRes { - assert(lock_value); - throw LockException(region->id(), std::move(lock_value)); - }, - [](RegionException::RegionReadStatus & s) -> ReadBlockByRegionRes { return s; }, - }, - region_data_lock); -} - RegionTable::ResolveLocksAndWriteRegionRes RegionTable::resolveLocksAndWriteRegion(TMTContext & tmt, const TiDB::TableID table_id, const RegionPtr & region, diff --git a/dbms/src/Storages/Transaction/ProxyFFI.cpp b/dbms/src/Storages/Transaction/ProxyFFI.cpp index 58e7f5ad2e5..cc7d1e10a49 100644 --- a/dbms/src/Storages/Transaction/ProxyFFI.cpp +++ b/dbms/src/Storages/Transaction/ProxyFFI.cpp @@ -304,25 +304,13 @@ RawRustPtrWrap::~RawRustPtrWrap() RustGcHelper::instance().gcRustPtr(ptr, type); } RawRustPtrWrap::RawRustPtrWrap(RawRustPtrWrap && src) + : RawRustPtr() { RawRustPtr & tar = (*this); tar = src; src.ptr = nullptr; } -struct PreHandledSnapshotWithBlock -{ - ~PreHandledSnapshotWithBlock() { CurrentMetrics::sub(CurrentMetrics::RaftNumSnapshotsPendingApply); } - PreHandledSnapshotWithBlock(const RegionPtr & region_, RegionPtrWithBlock::CachePtr && cache_) - : region(region_) - , cache(std::move(cache_)) - { - CurrentMetrics::add(CurrentMetrics::RaftNumSnapshotsPendingApply); - } - RegionPtr region; - RegionPtrWithBlock::CachePtr cache; -}; - struct PreHandledSnapshotWithFiles { ~PreHandledSnapshotWithFiles() { CurrentMetrics::sub(CurrentMetrics::RaftNumSnapshotsPendingApply); } @@ -362,13 +350,6 @@ RawCppPtr PreHandleSnapshot( switch (kvstore->applyMethod()) { - case TiDB::SnapshotApplyMethod::Block: - { - // Pre-decode as a block - auto new_region_block_cache = kvstore->preHandleSnapshotToBlock(new_region, snaps, index, term, tmt); - auto * res = new PreHandledSnapshotWithBlock{new_region, std::move(new_region_block_cache)}; - return GenRawCppPtr(res, RawCppPtrTypeImpl::PreHandledSnapshotWithBlock); - } case TiDB::SnapshotApplyMethod::DTFile_Directory: case TiDB::SnapshotApplyMethod::DTFile_Single: { @@ -391,18 +372,12 @@ RawCppPtr PreHandleSnapshot( template void ApplyPreHandledSnapshot(EngineStoreServerWrap * server, PreHandledSnapshot * snap) { - static_assert( - std::is_same_v || std::is_same_v, - "Unknown pre-handled snapshot type"); + static_assert(std::is_same_v, "Unknown pre-handled snapshot type"); try { auto & kvstore = server->tmt->getKVStore(); - if constexpr (std::is_same_v) - { - kvstore->handlePreApplySnapshot(RegionPtrWithBlock{snap->region, std::move(snap->cache)}, *server->tmt); - } - else if constexpr (std::is_same_v) + if constexpr (std::is_same_v) { kvstore->handlePreApplySnapshot(RegionPtrWithSnapshotFiles{snap->region, std::move(snap->ingest_ids)}, *server->tmt); } @@ -418,12 +393,6 @@ void ApplyPreHandledSnapshot(EngineStoreServerWrap * server, RawVoidPtr res, Raw { switch (static_cast(type)) { - case RawCppPtrTypeImpl::PreHandledSnapshotWithBlock: - { - auto * snap = reinterpret_cast(res); - ApplyPreHandledSnapshot(server, snap); - break; - } case RawCppPtrTypeImpl::PreHandledSnapshotWithFiles: { auto * snap = reinterpret_cast(res); @@ -445,9 +414,6 @@ void GcRawCppPtr(RawVoidPtr ptr, RawCppPtrType type) case RawCppPtrTypeImpl::String: delete reinterpret_cast(ptr); break; - case RawCppPtrTypeImpl::PreHandledSnapshotWithBlock: - delete reinterpret_cast(ptr); - break; case RawCppPtrTypeImpl::PreHandledSnapshotWithFiles: delete reinterpret_cast(ptr); break; diff --git a/dbms/src/Storages/Transaction/ProxyFFI.h b/dbms/src/Storages/Transaction/ProxyFFI.h index 5d87af94f30..e1c01599275 100644 --- a/dbms/src/Storages/Transaction/ProxyFFI.h +++ b/dbms/src/Storages/Transaction/ProxyFFI.h @@ -56,7 +56,6 @@ enum class RawCppPtrTypeImpl : RawCppPtrType { None = 0, String, - PreHandledSnapshotWithBlock, PreHandledSnapshotWithFiles, WakerNotifier, }; diff --git a/dbms/src/Storages/Transaction/Region.cpp b/dbms/src/Storages/Transaction/Region.cpp index e021de3d978..aa75eabb4b9 100644 --- a/dbms/src/Storages/Transaction/Region.cpp +++ b/dbms/src/Storages/Transaction/Region.cpp @@ -720,47 +720,6 @@ EngineStoreApplyRes Region::handleWriteRaftCmd(const WriteCmdsView & cmds, UInt6 return EngineStoreApplyRes::None; } -void Region::handleIngestSSTInMemory(const SSTViewVec snaps, UInt64 index, UInt64 term) -{ - if (index <= appliedIndex()) - return; - - { - std::unique_lock lock(mutex); - - for (UInt64 i = 0; i < snaps.len; ++i) - { - const auto & snapshot = snaps.views[i]; - auto sst_reader = SSTReader{proxy_helper, snapshot}; - - LOG_FMT_INFO(log, - "{} begin to ingest sst of cf {} at [term: {}, index: {}]", - this->toString(false), - CFToName(snapshot.type), - term, - index); - - uint64_t kv_size = 0; - while (sst_reader.remained()) - { - auto key = sst_reader.key(); - auto value = sst_reader.value(); - doInsert(snaps.views[i].type, TiKVKey(key.data, key.len), TiKVValue(value.data, value.len)); - ++kv_size; - sst_reader.next(); - } - - LOG_FMT_INFO(log, - "{} finish to ingest sst of kv count {}", - this->toString(false), - kv_size); - GET_METRIC(tiflash_raft_process_keys, type_ingest_sst).Increment(kv_size); - } - meta.setApplied(index, term); - } - meta.notifyAll(); -} - void Region::finishIngestSSTByDTFile(RegionPtr && rhs, UInt64 index, UInt64 term) { if (index <= appliedIndex()) diff --git a/dbms/src/Storages/Transaction/Region.h b/dbms/src/Storages/Transaction/Region.h index b31ae0cdc49..06b18de379a 100644 --- a/dbms/src/Storages/Transaction/Region.h +++ b/dbms/src/Storages/Transaction/Region.h @@ -191,7 +191,6 @@ class Region : public std::enable_shared_from_this TableID getMappedTableID() const; EngineStoreApplyRes handleWriteRaftCmd(const WriteCmdsView & cmds, UInt64 index, UInt64 term, TMTContext & tmt); - void handleIngestSSTInMemory(const SSTViewVec snaps, UInt64 index, UInt64 term); void finishIngestSSTByDTFile(RegionPtr && rhs, UInt64 index, UInt64 term); UInt64 getSnapshotEventFlag() const { return snapshot_event_flag; } diff --git a/dbms/src/Storages/Transaction/RegionBlockReader.cpp b/dbms/src/Storages/Transaction/RegionBlockReader.cpp index 32be7302775..af351f4a6b0 100644 --- a/dbms/src/Storages/Transaction/RegionBlockReader.cpp +++ b/dbms/src/Storages/Transaction/RegionBlockReader.cpp @@ -58,7 +58,7 @@ bool RegionBlockReader::readImpl(Block & block, const RegionDataReadInfoList & d const auto & pk_column_ids = schema_snapshot->pk_column_ids; const auto & pk_pos_map = schema_snapshot->pk_pos_map; - SortedColumnIDWithPosConstIter column_ids_iter = read_column_ids.begin(); + auto column_ids_iter = read_column_ids.begin(); size_t next_column_pos = 0; /// every table in tiflash must have an extra handle column, it either @@ -112,25 +112,6 @@ bool RegionBlockReader::readImpl(Block & block, const RegionDataReadInfoList & d size_t index = 0; for (const auto & [pk, write_type, commit_ts, value_ptr] : data_list) { - // Ignore data after the start_ts. - if (commit_ts > start_ts) - continue; - - bool should_skip = false; - if constexpr (pk_type != TMTPKType::STRING) - { - if constexpr (pk_type == TMTPKType::UINT64) - { - should_skip = scan_filter != nullptr && scan_filter->filter(static_cast(pk)); - } - else - { - should_skip = scan_filter != nullptr && scan_filter->filter(static_cast(pk)); - } - } - if (should_skip) - continue; - /// set delmark and version column delmark_data.emplace_back(write_type == Region::DelFlag); version_data.emplace_back(commit_ts); @@ -186,7 +167,7 @@ bool RegionBlockReader::readImpl(Block & block, const RegionDataReadInfoList & d { // The pk_type must be Int32/Uint32 or more narrow type // so cannot tell its' exact type here, just use `insert(Field)` - HandleID handle_value(static_cast(pk)); + auto handle_value(static_cast(pk)); raw_pk_column->insert(Field(handle_value)); if (unlikely(raw_pk_column->getInt(index) != handle_value)) { diff --git a/dbms/src/Storages/Transaction/RegionBlockReader.h b/dbms/src/Storages/Transaction/RegionBlockReader.h index 860e0d149e6..ec633e805c0 100644 --- a/dbms/src/Storages/Transaction/RegionBlockReader.h +++ b/dbms/src/Storages/Transaction/RegionBlockReader.h @@ -37,79 +37,12 @@ using ManageableStoragePtr = std::shared_ptr; struct ColumnsDescription; class Block; -class RegionScanFilter -{ - bool is_full_range_scan; - std::vector> int64_ranges; - std::vector> uint64_ranges; - - bool isValidHandle(UInt64 handle) - { - for (const auto & range : uint64_ranges) - { - if (handle >= range.first && handle < range.second) - { - return true; - } - } - return false; - } - bool isValidHandle(Int64 handle) - { - for (const auto & range : int64_ranges) - { - if (handle >= range.first && handle < range.second) - { - return true; - } - } - return false; - } - -public: - RegionScanFilter( - bool is_full_range_scan_, - std::vector> int64_ranges_, - std::vector> uint64_ranges_) - : is_full_range_scan(is_full_range_scan_) - , int64_ranges(std::move(int64_ranges_)) - , uint64_ranges(std::move(uint64_ranges_)) - {} - bool filter(UInt64 handle) { return !is_full_range_scan && !isValidHandle(handle); } - bool filter(Int64 handle) { return !is_full_range_scan && !isValidHandle(handle); } - bool isFullRangeScan() { return is_full_range_scan; } - const std::vector> & getUInt64Ranges() { return uint64_ranges; } - const std::vector> & getInt64Ranges() { return int64_ranges; } -}; - -using RegionScanFilterPtr = std::shared_ptr; - /// The Reader to read the region data in `data_list` and decode based on the given table_info and columns, as a block. class RegionBlockReader : private boost::noncopyable { - RegionScanFilterPtr scan_filter; - Timestamp start_ts = std::numeric_limits::max(); - public: RegionBlockReader(DecodingStorageSchemaSnapshotConstPtr schema_snapshot_); - inline RegionBlockReader & setFilter(RegionScanFilterPtr filter) - { - scan_filter = std::move(filter); - return *this; - } - - /// Set the `start_ts` for reading data. The `start_ts` is `Timestamp::max` if not set. - /// - /// Data with commit_ts > start_ts will be ignored. This is for the sake of decode safety on read, - /// i.e. as data keeps being synced to region cache while the schema for a specific read is fixed, - /// we'll always have newer data than schema, only ignoring them can guarantee the decode safety. - inline RegionBlockReader & setStartTs(Timestamp tso) - { - start_ts = tso; - return *this; - } - /// Read `data_list` as a block. /// /// On decode error, i.e. column number/type mismatch, will do force apply schema, @@ -117,7 +50,7 @@ class RegionBlockReader : private boost::noncopyable /// Moreover, exception will be thrown if we see fatal decode error meanwhile `force_decode` is true. /// /// `RegionBlockReader::read` is the common routine used by both 'flush' and 'read' processes of TXN engine (Delta-Tree, TXN-MergeTree), - /// each of which will use carefully adjusted 'start_ts' and 'force_decode' with appropriate error handling/retry to get what they want. + /// each of which will use carefully adjusted 'force_decode' with appropriate error handling/retry to get what they want. bool read(Block & block, const RegionDataReadInfoList & data_list, bool force_decode); private: diff --git a/dbms/src/Storages/Transaction/RegionTable.h b/dbms/src/Storages/Transaction/RegionTable.h index b30a905541a..717b1cd568f 100644 --- a/dbms/src/Storages/Transaction/RegionTable.h +++ b/dbms/src/Storages/Transaction/RegionTable.h @@ -146,21 +146,6 @@ class RegionTable : private boost::noncopyable Poco::Logger * log, bool lock_region = true); - /// Read the data of the given region into block, take good care of learner read and locks. - /// Assuming that the schema has been properly synced by outer, i.e. being new enough to decode data before start_ts, - /// we directly ask RegionBlockReader::read to perform a read with the given start_ts and force_decode being true. - using ReadBlockByRegionRes = std::variant; - static ReadBlockByRegionRes readBlockByRegion(const TiDB::TableInfo & table_info, - const ColumnsDescription & columns, - const Names & column_names_to_read, - const RegionPtr & region, - RegionVersion region_version, - RegionVersion conf_version, - bool resolve_locks, - Timestamp start_ts, - const std::unordered_set * bypass_lock_ts, - RegionScanFilterPtr scan_filter = nullptr); - /// Check transaction locks in region, and write committed data in it into storage engine if check passed. Otherwise throw an LockException. /// The write logic is the same as #writeBlockByRegion, with some extra checks about region version and conf_version. using ResolveLocksAndWriteRegionRes = std::variant; diff --git a/dbms/src/Storages/Transaction/StorageEngineType.h b/dbms/src/Storages/Transaction/StorageEngineType.h index f202d15a769..3d103ca60c1 100644 --- a/dbms/src/Storages/Transaction/StorageEngineType.h +++ b/dbms/src/Storages/Transaction/StorageEngineType.h @@ -33,7 +33,7 @@ enum class StorageEngine enum class SnapshotApplyMethod : std::int32_t { - Block = 1, + DEPRECATED_Block = 1, // Invalid if the storage engine is not DeltaTree DTFile_Directory, DTFile_Single, @@ -43,14 +43,12 @@ inline const std::string applyMethodToString(SnapshotApplyMethod method) { switch (method) { - case SnapshotApplyMethod::Block: - return "block"; - case SnapshotApplyMethod::DTFile_Directory: - return "file1"; - case SnapshotApplyMethod::DTFile_Single: - return "file2"; - default: - return "unknown(" + std::to_string(static_cast(method)) + ")"; + case SnapshotApplyMethod::DTFile_Directory: + return "file1"; + case SnapshotApplyMethod::DTFile_Single: + return "file2"; + default: + return "unknown(" + std::to_string(static_cast(method)) + ")"; } return "unknown"; } diff --git a/dbms/src/Storages/Transaction/TMTContext.cpp b/dbms/src/Storages/Transaction/TMTContext.cpp index 719784edaf2..3c7468cbd64 100644 --- a/dbms/src/Storages/Transaction/TMTContext.cpp +++ b/dbms/src/Storages/Transaction/TMTContext.cpp @@ -36,6 +36,8 @@ extern const uint64_t DEFAULT_WAIT_INDEX_TIMEOUT_MS = 5 * 60 * 1000; const int64_t DEFAULT_WAIT_REGION_READY_TIMEOUT_SEC = 20 * 60; +const int64_t DEFAULT_READ_INDEX_WORKER_TICK_MS = 10; + TMTContext::TMTContext(Context & context_, const TiFlashRaftConfig & raft_config, const pingcap::ClusterConfig & cluster_config) : context(context_) , kvstore(std::make_shared(context, raft_config.snapshot_apply_method)) @@ -56,7 +58,7 @@ TMTContext::TMTContext(Context & context_, const TiFlashRaftConfig & raft_config , replica_read_max_thread(1) , batch_read_index_timeout_ms(DEFAULT_BATCH_READ_INDEX_TIMEOUT_MS) , wait_index_timeout_ms(DEFAULT_WAIT_INDEX_TIMEOUT_MS) - , read_index_worker_tick_ms(10) + , read_index_worker_tick_ms(DEFAULT_READ_INDEX_WORKER_TICK_MS) , wait_region_ready_timeout_sec(DEFAULT_WAIT_REGION_READY_TIMEOUT_SEC) {} @@ -149,12 +151,6 @@ SchemaSyncerPtr TMTContext::getSchemaSyncer() const return schema_syncer; } -void TMTContext::setSchemaSyncer(SchemaSyncerPtr rhs) -{ - std::lock_guard lock(mutex); - schema_syncer = rhs; -} - pingcap::pd::ClientPtr TMTContext::getPDClient() const { return cluster->pd_client; @@ -194,7 +190,7 @@ void TMTContext::reloadConfig(const Poco::Util::AbstractConfiguration & config) t = t >= 0 ? t : std::numeric_limits::max(); // set -1 to wait infinitely t; }); - read_index_worker_tick_ms = config.getUInt64(READ_INDEX_WORKER_TICK_MS, 10 /*10ms*/); + read_index_worker_tick_ms = config.getUInt64(READ_INDEX_WORKER_TICK_MS, DEFAULT_READ_INDEX_WORKER_TICK_MS); } { LOG_FMT_INFO( diff --git a/dbms/src/Storages/Transaction/TMTContext.h b/dbms/src/Storages/Transaction/TMTContext.h index 27e0482b787..bd592dad315 100644 --- a/dbms/src/Storages/Transaction/TMTContext.h +++ b/dbms/src/Storages/Transaction/TMTContext.h @@ -84,7 +84,6 @@ class TMTContext : private boost::noncopyable explicit TMTContext(Context & context_, const TiFlashRaftConfig & raft_config, const pingcap::ClusterConfig & cluster_config_); SchemaSyncerPtr getSchemaSyncer() const; - void setSchemaSyncer(SchemaSyncerPtr); pingcap::pd::ClientPtr getPDClient() const; diff --git a/dbms/src/Storages/Transaction/tests/gtest_kvstore.cpp b/dbms/src/Storages/Transaction/tests/gtest_kvstore.cpp index 2378871f71f..f0cafce3914 100644 --- a/dbms/src/Storages/Transaction/tests/gtest_kvstore.cpp +++ b/dbms/src/Storages/Transaction/tests/gtest_kvstore.cpp @@ -310,10 +310,13 @@ void RegionKVStoreTest::testRaftMergeRollback(KVStore & kvs, TMTContext & tmt) } } region->setStateApplying(); + try { - kvs.handleAdminRaftCmd(std::move(request), - std::move(response), + raft_cmdpb::AdminRequest first_request = request; + raft_cmdpb::AdminResponse first_response = response; + kvs.handleAdminRaftCmd(std::move(first_request), + std::move(first_response), region_id, 32, 6, @@ -925,12 +928,14 @@ void RegionKVStoreTest::testKVStore() TiKVValue lock_value = RecordKVFormat::encodeLockCfValue(Region::DelFlag, "pk", 77, 0); RegionBench::setupDelRequest(request.add_requests(), ColumnFamilyName::Lock, lock_key); } - ASSERT_EQ(kvs.handleWriteRaftCmd(std::move(request), 1, 7, 6, ctx.getTMTContext()), + raft_cmdpb::RaftCmdRequest first_request = request; + ASSERT_EQ(kvs.handleWriteRaftCmd(std::move(first_request), 1, 7, 6, ctx.getTMTContext()), EngineStoreApplyRes::None); RegionBench::setupDelRequest(request.add_requests(), ColumnFamilyName::Write, TiKVKey("illegal key")); // index <= appliedIndex(), ignore - ASSERT_EQ(kvs.handleWriteRaftCmd(std::move(request), 1, 7, 6, ctx.getTMTContext()), + raft_cmdpb::RaftCmdRequest second_request; + ASSERT_EQ(kvs.handleWriteRaftCmd(std::move(second_request), 1, 7, 6, ctx.getTMTContext()), EngineStoreApplyRes::None); try { @@ -972,13 +977,24 @@ void RegionKVStoreTest::testKVStore() request.mutable_compact_log(); request.set_cmd_type(::raft_cmdpb::AdminCmdType::CompactLog); - ASSERT_EQ(kvs.handleAdminRaftCmd(std::move(request), std::move(response), 7, 22, 6, ctx.getTMTContext()), EngineStoreApplyRes::Persist); - ASSERT_EQ(kvs.handleAdminRaftCmd(raft_cmdpb::AdminRequest{request}, std::move(response), 7, 23, 6, ctx.getTMTContext()), EngineStoreApplyRes::None); + raft_cmdpb::AdminRequest first_request = request; + raft_cmdpb::AdminResponse first_response = response; + + ASSERT_EQ(kvs.handleAdminRaftCmd(std::move(first_request), std::move(first_response), 7, 22, 6, ctx.getTMTContext()), EngineStoreApplyRes::Persist); + + raft_cmdpb::AdminResponse second_response = response; + ASSERT_EQ(kvs.handleAdminRaftCmd(raft_cmdpb::AdminRequest{request}, std::move(second_response), 7, 23, 6, ctx.getTMTContext()), EngineStoreApplyRes::None); request.set_cmd_type(::raft_cmdpb::AdminCmdType::ComputeHash); - ASSERT_EQ(kvs.handleAdminRaftCmd(raft_cmdpb::AdminRequest{request}, std::move(response), 7, 24, 6, ctx.getTMTContext()), EngineStoreApplyRes::None); + + raft_cmdpb::AdminResponse third_response = response; + ASSERT_EQ(kvs.handleAdminRaftCmd(raft_cmdpb::AdminRequest{request}, std::move(third_response), 7, 24, 6, ctx.getTMTContext()), EngineStoreApplyRes::None); request.set_cmd_type(::raft_cmdpb::AdminCmdType::VerifyHash); - ASSERT_EQ(kvs.handleAdminRaftCmd(raft_cmdpb::AdminRequest{request}, std::move(response), 7, 25, 6, ctx.getTMTContext()), EngineStoreApplyRes::None); - ASSERT_EQ(kvs.handleAdminRaftCmd(raft_cmdpb::AdminRequest{request}, std::move(response), 8192, 5, 6, ctx.getTMTContext()), EngineStoreApplyRes::NotFound); + + raft_cmdpb::AdminResponse fourth_response = response; + ASSERT_EQ(kvs.handleAdminRaftCmd(raft_cmdpb::AdminRequest{request}, std::move(fourth_response), 7, 25, 6, ctx.getTMTContext()), EngineStoreApplyRes::None); + + raft_cmdpb::AdminResponse fifth_response = response; + ASSERT_EQ(kvs.handleAdminRaftCmd(raft_cmdpb::AdminRequest{request}, std::move(fifth_response), 8192, 5, 6, ctx.getTMTContext()), EngineStoreApplyRes::NotFound); { kvs.setRegionCompactLogConfig(0, 0, 0); request.set_cmd_type(::raft_cmdpb::AdminCmdType::CompactLog); @@ -994,62 +1010,12 @@ void RegionKVStoreTest::testKVStore() } { auto ori_snapshot_apply_method = kvs.snapshot_apply_method; - kvs.snapshot_apply_method = TiDB::SnapshotApplyMethod::Block; + kvs.snapshot_apply_method = TiDB::SnapshotApplyMethod::DTFile_Single; SCOPE_EXIT({ kvs.snapshot_apply_method = ori_snapshot_apply_method; }); - { - { - auto region = makeRegion(22, RecordKVFormat::genKey(1, 55), RecordKVFormat::genKey(1, 65)); - kvs.checkAndApplySnapshot(region, ctx.getTMTContext()); - } - try - { - auto region = makeRegion(20, RecordKVFormat::genKey(1, 55), RecordKVFormat::genKey(1, 65)); - kvs.checkAndApplySnapshot(region, ctx.getTMTContext()); // overlap, but not tombstone - ASSERT_TRUE(false); - } - catch (Exception & e) - { - ASSERT_EQ(e.message(), "range of region 20 is overlapped with 22, state: region { id: 22 }"); - } - { - const auto * ori_ptr = proxy_helper.proxy_ptr.inner; - proxy_helper.proxy_ptr.inner = nullptr; - SCOPE_EXIT({ - proxy_helper.proxy_ptr.inner = ori_ptr; - }); - try - { - auto region = makeRegion(20, RecordKVFormat::genKey(1, 55), RecordKVFormat::genKey(1, 65)); - kvs.checkAndApplySnapshot(region, ctx.getTMTContext()); - ASSERT_TRUE(false); - } - catch (Exception & e) - { - ASSERT_EQ(e.message(), "getRegionLocalState meet internal error: RaftStoreProxyPtr is none"); - } - } - - { - proxy_instance.getRegion(22)->setSate(({ - raft_serverpb::RegionLocalState s; - s.set_state(::raft_serverpb::PeerState::Tombstone); - s; - })); - auto region = makeRegion(20, RecordKVFormat::genKey(1, 55), RecordKVFormat::genKey(1, 65)); - kvs.checkAndApplySnapshot(region, ctx.getTMTContext()); // overlap, tombstone, remove previous one - ASSERT_EQ(nullptr, kvs.getRegion(22)); - ASSERT_NE(nullptr, kvs.getRegion(20)); - - auto state = proxy_helper.getRegionLocalState(8192); - ASSERT_EQ(state.state(), raft_serverpb::PeerState::Tombstone); - } - - kvs.handleDestroy(20, ctx.getTMTContext()); - } auto region_id = 19; auto region = makeRegion(region_id, RecordKVFormat::genKey(1, 50), RecordKVFormat::genKey(1, 60)); auto region_id_str = std::to_string(19); @@ -1076,7 +1042,7 @@ void RegionKVStoreTest::testKVStore() 8, 5, ctx.getTMTContext()); - ASSERT_EQ(kvs.getRegion(19)->dataInfo(), "[default 2 ]"); + ASSERT_EQ(kvs.getRegion(19)->checkIndex(8), true); try { kvs.handleApplySnapshot( @@ -1091,22 +1057,85 @@ void RegionKVStoreTest::testKVStore() catch (Exception & e) { ASSERT_EQ(e.message(), "[region 19] already has newer apply-index 8 than 6, should not happen"); - ASSERT_EQ(kvs.getRegion(19)->dataInfo(), "[default 2 ]"); // apply-snapshot do not work } - kvs.handleApplySnapshot( - region->getMetaRegion(), - 2, - {}, // empty - 8, // same index - 5, - ctx.getTMTContext()); - ASSERT_EQ(kvs.getRegion(19)->dataInfo(), "[default 2 ]"); // apply-snapshot do not work - region = makeRegion(19, RecordKVFormat::genKey(1, 50), RecordKVFormat::genKey(1, 60)); - region->handleWriteRaftCmd({}, 10, 10, ctx.getTMTContext()); - kvs.checkAndApplySnapshot(region, ctx.getTMTContext()); - ASSERT_EQ(kvs.getRegion(19)->dataInfo(), "[]"); + } + + { + { + auto region = makeRegion(22, RecordKVFormat::genKey(55, 50), RecordKVFormat::genKey(55, 100)); + auto ingest_ids = kvs.preHandleSnapshotToFiles( + region, + {}, + 9, + 5, + ctx.getTMTContext()); + kvs.checkAndApplySnapshot(RegionPtrWithSnapshotFiles{region, std::move(ingest_ids)}, ctx.getTMTContext()); + } + try + { + auto region = makeRegion(20, RecordKVFormat::genKey(55, 50), RecordKVFormat::genKey(55, 100)); + auto ingest_ids = kvs.preHandleSnapshotToFiles( + region, + {}, + 9, + 5, + ctx.getTMTContext()); + kvs.checkAndApplySnapshot(RegionPtrWithSnapshotFiles{region, std::move(ingest_ids)}, ctx.getTMTContext()); // overlap, but not tombstone + ASSERT_TRUE(false); + } + catch (Exception & e) + { + ASSERT_EQ(e.message(), "range of region 20 is overlapped with 22, state: region { id: 22 }"); + } + + { + const auto * ori_ptr = proxy_helper.proxy_ptr.inner; + proxy_helper.proxy_ptr.inner = nullptr; + SCOPE_EXIT({ + proxy_helper.proxy_ptr.inner = ori_ptr; + }); + + try + { + auto region = makeRegion(20, RecordKVFormat::genKey(55, 50), RecordKVFormat::genKey(55, 100)); + auto ingest_ids = kvs.preHandleSnapshotToFiles( + region, + {}, + 10, + 5, + ctx.getTMTContext()); + kvs.checkAndApplySnapshot(RegionPtrWithSnapshotFiles{region, std::move(ingest_ids)}, ctx.getTMTContext()); + ASSERT_TRUE(false); + } + catch (Exception & e) + { + ASSERT_EQ(e.message(), "getRegionLocalState meet internal error: RaftStoreProxyPtr is none"); + } + } + + { + proxy_instance.getRegion(22)->setSate(({ + raft_serverpb::RegionLocalState s; + s.set_state(::raft_serverpb::PeerState::Tombstone); + s; + })); + auto region = makeRegion(20, RecordKVFormat::genKey(55, 50), RecordKVFormat::genKey(55, 100)); + auto ingest_ids = kvs.preHandleSnapshotToFiles( + region, + {}, + 10, + 5, + ctx.getTMTContext()); + kvs.checkAndApplySnapshot(RegionPtrWithSnapshotFiles{region, std::move(ingest_ids)}, ctx.getTMTContext()); // overlap, tombstone, remove previous one + + auto state = proxy_helper.getRegionLocalState(8192); + ASSERT_EQ(state.state(), raft_serverpb::PeerState::Tombstone); + } + + kvs.handleDestroy(20, ctx.getTMTContext()); } } + { auto region_id = 19; auto region_id_str = std::to_string(19); @@ -1125,11 +1154,6 @@ void RegionKVStoreTest::testKVStore() RegionMockTest mock_test(ctx.getTMTContext().getKVStore(), region); { - auto ori_snapshot_apply_method = kvs.snapshot_apply_method; - kvs.snapshot_apply_method = TiDB::SnapshotApplyMethod::Block; - SCOPE_EXIT({ - kvs.snapshot_apply_method = ori_snapshot_apply_method; - }); // Mocking ingest a SST for column family "Write" std::vector sst_views; sst_views.push_back(SSTView{ @@ -1142,9 +1166,10 @@ void RegionKVStoreTest::testKVStore() 100, 1, ctx.getTMTContext()); - ASSERT_EQ(kvs.getRegion(19)->dataInfo(), "[default 2 ]"); + ASSERT_EQ(kvs.getRegion(19)->checkIndex(100), true); } } + { raft_cmdpb::AdminRequest request; raft_cmdpb::AdminResponse response; @@ -1154,7 +1179,7 @@ void RegionKVStoreTest::testKVStore() try { - kvs.handleAdminRaftCmd(std::move(request), std::move(response), 19, 110, 6, ctx.getTMTContext()); + kvs.handleAdminRaftCmd(std::move(request), std::move(response), 1, 110, 6, ctx.getTMTContext()); ASSERT_TRUE(false); } catch (Exception & e) diff --git a/etc/config-template.toml b/etc/config-template.toml index cad45dc8105..f56a6a095d4 100644 --- a/etc/config-template.toml +++ b/etc/config-template.toml @@ -130,10 +130,6 @@ # pd_addr = "pd0:2379" # specify which storage engine we use. tmt or dt TODO: Remove deprecated tmt engine # storage_engine = "dt" -[raft.snapshot] -# The way to apply snapshot data -# The value is one of "block" / "file1" -# method = "file1" [status] # The port through which Prometheus pulls metrics information. From a0ed1a650cdc7cf600d37a59668aa59e05d3b7db Mon Sep 17 00:00:00 2001 From: hehechen Date: Tue, 31 May 2022 23:42:27 +0800 Subject: [PATCH 078/127] Add mix mode UT (#5012) close pingcap/tiflash#5028 --- .../Page/V3/tests/gtest_page_storage.cpp | 33 ++ .../V3/tests/gtest_page_storage_mix_mode.cpp | 312 ++++++++++++++++++ 2 files changed, 345 insertions(+) diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp index ce2ba0adaf4..f7ba33c46c8 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp @@ -1408,6 +1408,39 @@ try } CATCH +TEST_F(PageStorageTest, TruncateBlobFile) +try +{ + const size_t buf_sz = 1024; + char c_buff[buf_sz]; + + for (size_t i = 0; i < buf_sz; ++i) + { + c_buff[i] = i % 0xff; + } + + { + WriteBatch batch; + batch.putPage(1, 0, std::make_shared(c_buff, buf_sz), buf_sz, {}); + page_storage->write(std::move(batch)); + } + + auto blob_file = Poco::File(getTemporaryPath() + "/blobfile_1"); + + page_storage = reopenWithConfig(config); + EXPECT_GT(blob_file.getSize(), 0); + + { + WriteBatch batch; + batch.delPage(1); + page_storage->write(std::move(batch)); + } + page_storage = reopenWithConfig(config); + page_storage->gc(/*not_skip*/ false, nullptr, nullptr); + EXPECT_EQ(blob_file.getSize(), 0); +} +CATCH + } // namespace PS::V3::tests } // namespace DB diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp index 97eac841018..5517539b898 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp @@ -490,6 +490,318 @@ try } CATCH +// v2 put 1, v2 ref 2->1, get snapshot s1, v3 del 1, read s1 +TEST_F(PageStorageMixedTest, RefWithSnapshot) +try +{ + const size_t buf_sz = 1024; + char c_buff[buf_sz] = {0}; + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, 0, buff, buf_sz); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.putRefPage(2, 1); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + ASSERT_EQ(page_reader_mix->getNormalPageId(2), 1); + } + + auto snapshot_mix_mode = page_reader_mix->getSnapshot("ReadWithSnapshotAfterDelOrigin"); + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_mix_mode); + const auto & page1 = page_reader_mix_with_snap.read(1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 1); + } + + { + WriteBatch batch; + batch.delPage(1); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_mix_mode); + const auto & page1 = page_reader_mix_with_snap.read(1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 1); + } +} +CATCH + +// v2 put 1, v2 ref 2->1, get snapshot s1, v3 del 1, v3 del 2, read s1 +TEST_F(PageStorageMixedTest, RefWithDelSnapshot) +try +{ + const size_t buf_sz = 1024; + char c_buff[buf_sz] = {0}; + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, 0, buff, buf_sz); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.putRefPage(2, 1); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + ASSERT_EQ(page_reader_mix->getNormalPageId(2), 1); + } + + auto snapshot_mix_mode = page_reader_mix->getSnapshot("ReadWithSnapshotAfterDelOrigin"); + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_mix_mode); + const auto & page1 = page_reader_mix_with_snap.read(1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 1); + } + + { + WriteBatch batch; + batch.delPage(1); + batch.delPage(2); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_mix_mode); + const auto & page1 = page_reader_mix_with_snap.read(1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 1); + } +} +CATCH + +// v2 put 1, v2 ref 2->1, v3 del 1, get snapshot s1, v3 del 2, use s1 read 2 +TEST_F(PageStorageMixedTest, RefWithDelSnapshot2) +try +{ + const size_t buf_sz = 1024; + char c_buff[buf_sz] = {0}; + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, 0, buff, buf_sz); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.putRefPage(2, 1); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + ASSERT_EQ(page_reader_mix->getNormalPageId(2), 1); + } + + { + WriteBatch batch; + batch.delPage(1); + page_writer_mix->write(std::move(batch), nullptr); + } + + auto snapshot_mix_mode = page_reader_mix->getSnapshot("ReadWithSnapshotAfterDelOrigin"); + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_mix_mode); + const auto & page1 = page_reader_mix_with_snap.read(2); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 2); + } + + { + WriteBatch batch; + batch.delPage(2); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_mix_mode); + const auto & page1 = page_reader_mix_with_snap.read(2); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 2); + } +} +CATCH + +// v2 put 1, v2 del 1, v3 put 2, v3 del 2 +TEST_F(PageStorageMixedTest, GetMaxId) +try +{ + const size_t buf_sz = 1024; + char c_buff[buf_sz] = {0}; + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, 0, buff, buf_sz); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.delPage(1); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(2, 0, buff, buf_sz); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + ASSERT_EQ(storage_pool_mix->newLogPageId(), 3); + } + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(3, 0, buff, buf_sz); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.delPage(3); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + ASSERT_EQ(storage_pool_mix->newLogPageId(), 4); + } +} +CATCH + + +TEST_F(PageStorageMixedTest, ReuseV2ID) +try +{ + const size_t buf_sz = 1024; + char c_buff[buf_sz] = {0}; + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, 0, buff, buf_sz); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.delPage(1); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::ONLY_V3); + ASSERT_EQ(storage_pool_mix->newLogPageId(), 1); + } + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, 0, buff, buf_sz); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.delPage(1); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::ONLY_V3); + ASSERT_EQ(storage_pool_mix->newLogPageId(), 2); + } +} +CATCH + +// v2 put 1, v3 ref 2->1, reload, check max id, get snapshot s1, v3 del 1, get snapshot s2, v3 del 2, get snapshot s3, check snapshots +TEST_F(PageStorageMixedTest, V3RefV2WithSnapshot) +try +{ + const size_t buf_sz = 1024; + char c_buff[buf_sz] = {0}; + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, 0, buff, buf_sz); + page_writer_v2->write(std::move(batch), nullptr); + } + + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + + { + WriteBatch batch; + batch.putRefPage(2, 1); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + ASSERT_EQ(page_reader_mix->getNormalPageId(2), 1); + ASSERT_EQ(storage_pool_mix->newLogPageId(), 3); + } + + auto snapshot_before_del = page_reader_mix->getSnapshot("ReadWithSnapshotBeforeDelOrigin"); + + { + WriteBatch batch; + batch.delPage(1); + page_writer_mix->write(std::move(batch), nullptr); + } + + auto snapshot_after_del_origin = page_reader_mix->getSnapshot("ReadWithSnapshotAfterDelOrigin"); + + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_before_del); + const auto & page1 = page_reader_mix_with_snap.read(1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 1); + } + + { + WriteBatch batch; + batch.delPage(2); + page_writer_mix->write(std::move(batch), nullptr); + } + + auto snapshot_after_del_all = page_reader_mix->getSnapshot("ReadWithSnapshotAfterDelAll"); + + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_after_del_origin); + const auto & page1 = page_reader_mix_with_snap.read(2); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 2); + } + + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_after_del_origin); + const auto & page1 = page_reader_mix_with_snap.read(2); + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 2); + } + + { + auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_after_del_all); + ASSERT_ANY_THROW(page_reader_mix_with_snap.read(2)); + } +} +CATCH TEST_F(PageStorageMixedTest, MockDTIngest) try From 800715c798c6d56b6bc26e1ff83dff6a5eb6d0dd Mon Sep 17 00:00:00 2001 From: lidezhu <47731263+lidezhu@users.noreply.github.com> Date: Wed, 1 Jun 2022 11:52:26 +0800 Subject: [PATCH 079/127] support mix_mode in dt_workload (#5011) ref pingcap/tiflash#3594 --- .../DeltaMerge/tools/workload/DTWorkload.cpp | 2 +- .../DeltaMerge/tools/workload/MainEntry.cpp | 29 ++++++++++++++----- .../DeltaMerge/tools/workload/Options.cpp | 20 ++++++++++--- .../DeltaMerge/tools/workload/Options.h | 4 ++- dbms/src/TestUtils/TiFlashTestEnv.cpp | 4 +-- dbms/src/TestUtils/TiFlashTestEnv.h | 3 +- 6 files changed, 45 insertions(+), 17 deletions(-) diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/DTWorkload.cpp b/dbms/src/Storages/DeltaMerge/tools/workload/DTWorkload.cpp index e5c7fd30f40..a6113f91d91 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/DTWorkload.cpp +++ b/dbms/src/Storages/DeltaMerge/tools/workload/DTWorkload.cpp @@ -233,7 +233,7 @@ void DTWorkload::verifyHandle(uint64_t r) } for (size_t i = 0; i < handle_col->size(); i++) { - // Handle must be int64 or uint64. Currently, TableGenterator would ensure this limit. + // Handle must be int64 or uint64. Currently, TableGenerator would ensure this limit. uint64_t h = handle_col->getInt(i); uint64_t store_ts = ts_col->getInt(i); diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/MainEntry.cpp b/dbms/src/Storages/DeltaMerge/tools/workload/MainEntry.cpp index e18a6ef30a2..f79d414f20b 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/MainEntry.cpp +++ b/dbms/src/Storages/DeltaMerge/tools/workload/MainEntry.cpp @@ -124,13 +124,26 @@ void run(WorkloadOptions & opts) // Table Schema auto table_gen = TableGenerator::create(opts); auto table_info = table_gen->get(opts.table_id, opts.table_name); - // In this for loop, destory DeltaMergeStore gracefully and recreate it. - for (uint64_t i = 0; i < opts.verify_round; i++) + // In this for loop, destroy DeltaMergeStore gracefully and recreate it. + auto run_test = [&]() { + for (uint64_t i = 0; i < opts.verify_round; i++) + { + DTWorkload workload(opts, handle_table, table_info); + workload.run(i); + stats.push_back(workload.getStat()); + LOG_FMT_INFO(log, "No.{} Workload {} {}", i, opts.write_key_distribution, stats.back().toStrings()); + } + }; + run_test(); + + if (opts.ps_run_mode == DB::PageStorageRunMode::MIX_MODE) { - DTWorkload workload(opts, handle_table, table_info); - workload.run(i); - stats.push_back(workload.getStat()); - LOG_FMT_INFO(log, "No.{} Workload {} {}", i, opts.write_key_distribution, stats.back().toStrings()); + // clear statistic in DB::PageStorageRunMode::ONLY_V2 + stats.clear(); + auto & global_context = TiFlashTestEnv::getGlobalContext(); + global_context.setPageStorageRunMode(DB::PageStorageRunMode::MIX_MODE); + global_context.initializeGlobalStoragePoolIfNeed(global_context.getPathPool()); + run_test(); } } catch (...) @@ -254,8 +267,9 @@ int DTWorkload::mainEntry(int argc, char ** argv) // or the logging in global context won't be output to // the log file init(opts); - TiFlashTestEnv::initializeGlobalContext(opts.work_dirs, opts.enable_ps_v3); + // For mixed mode, we need to run the test in ONLY_V2 mode first. + TiFlashTestEnv::initializeGlobalContext(opts.work_dirs, opts.ps_run_mode == PageStorageRunMode::ONLY_V3 ? PageStorageRunMode::ONLY_V3 : PageStorageRunMode::ONLY_V2); if (opts.testing_type == "daily_perf") { dailyPerformanceTest(opts); @@ -277,7 +291,6 @@ int DTWorkload::mainEntry(int argc, char ** argv) runAndRandomKill(opts); } } - TiFlashTestEnv::shutdown(); return 0; } diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/Options.cpp b/dbms/src/Storages/DeltaMerge/tools/workload/Options.cpp index 0d2b14d916b..1c6409f3c53 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/Options.cpp +++ b/dbms/src/Storages/DeltaMerge/tools/workload/Options.cpp @@ -44,7 +44,7 @@ std::string WorkloadOptions::toString(std::string seperator) const fmt::format("read_stream_count {}{}", read_stream_count, seperator) + // fmt::format("testing_type {}{}", testing_type, seperator) + // fmt::format("log_write_request {}{}", log_write_request, seperator) + // - fmt::format("enable_ps_v3 {}{}", enable_ps_v3, seperator) + // + fmt::format("ps_run_mode {}{}", ps_run_mode, seperator) + // fmt::format("bg_thread_count {}{}", bg_thread_count, seperator) + // fmt::format("table_id {}{}", table_id, seperator) + // fmt::format("table_name {}{}", table_name, seperator); @@ -88,7 +88,7 @@ std::pair WorkloadOptions::parseOptions(int argc, char * argv // ("log_write_request", value()->default_value(false), "") // // - ("enable_ps_v3", value()->default_value(true), "") // + ("ps_run_mode", value()->default_value(2, "possible value: 1(only_v2), 2(only_v3), 3(mix_mode), and note that in mix_mode, the test will run twice, first round in only_v2 mode and second round in mix_mode")) // // ("bg_thread_count", value()->default_value(4), "") // // @@ -155,8 +155,20 @@ std::pair WorkloadOptions::parseOptions(int argc, char * argv testing_type = vm["testing_type"].as(); log_write_request = vm["log_write_request"].as(); - - enable_ps_v3 = vm["enable_ps_v3"].as(); + switch (vm["ps_run_mode"].as()) + { + case static_cast(PageStorageRunMode::ONLY_V2): + ps_run_mode = PageStorageRunMode::ONLY_V2; + break; + case static_cast(PageStorageRunMode::ONLY_V3): + ps_run_mode = PageStorageRunMode::ONLY_V3; + break; + case static_cast(PageStorageRunMode::MIX_MODE): + ps_run_mode = PageStorageRunMode::MIX_MODE; + break; + default: + return {false, fmt::format("unknown ps_run_mode {}.", vm["ps_run_mode"].as())}; + } bg_thread_count = vm["bg_thread_count"].as(); diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/Options.h b/dbms/src/Storages/DeltaMerge/tools/workload/Options.h index 17c7a5ba61f..f017daf2d8a 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/Options.h +++ b/dbms/src/Storages/DeltaMerge/tools/workload/Options.h @@ -14,6 +14,8 @@ #pragma once +#include + #include #include @@ -53,7 +55,7 @@ struct WorkloadOptions bool log_write_request; - bool enable_ps_v3; + PageStorageRunMode ps_run_mode; uint64_t bg_thread_count; diff --git a/dbms/src/TestUtils/TiFlashTestEnv.cpp b/dbms/src/TestUtils/TiFlashTestEnv.cpp index bd05e5826db..cbd42b57550 100644 --- a/dbms/src/TestUtils/TiFlashTestEnv.cpp +++ b/dbms/src/TestUtils/TiFlashTestEnv.cpp @@ -28,7 +28,7 @@ namespace DB::tests { std::unique_ptr TiFlashTestEnv::global_context = nullptr; -void TiFlashTestEnv::initializeGlobalContext(Strings testdata_path, bool enable_ps_v3) +void TiFlashTestEnv::initializeGlobalContext(Strings testdata_path, PageStorageRunMode ps_run_mode) { // set itself as global context global_context = std::make_unique(DB::Context::createGlobal()); @@ -68,7 +68,7 @@ void TiFlashTestEnv::initializeGlobalContext(Strings testdata_path, bool enable_ global_context->getPathCapacity(), global_context->getFileProvider()); - global_context->setPageStorageRunMode(enable_ps_v3 ? PageStorageRunMode::ONLY_V3 : PageStorageRunMode::ONLY_V2); + global_context->setPageStorageRunMode(ps_run_mode); global_context->initializeGlobalStoragePoolIfNeed(global_context->getPathPool()); LOG_FMT_INFO(Logger::get("TiFlashTestEnv"), "Storage mode : {}", static_cast(global_context->getPageStorageRunMode())); diff --git a/dbms/src/TestUtils/TiFlashTestEnv.h b/dbms/src/TestUtils/TiFlashTestEnv.h index 0264d87ef9f..dafecf6e1de 100644 --- a/dbms/src/TestUtils/TiFlashTestEnv.h +++ b/dbms/src/TestUtils/TiFlashTestEnv.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -88,7 +89,7 @@ class TiFlashTestEnv static Context getContext(const DB::Settings & settings = DB::Settings(), Strings testdata_path = {}); - static void initializeGlobalContext(Strings testdata_path = {}, bool enable_ps_v3 = true); + static void initializeGlobalContext(Strings testdata_path = {}, PageStorageRunMode ps_run_mode = PageStorageRunMode::ONLY_V3); static Context & getGlobalContext() { return *global_context; } static void shutdown(); From 380f6cd560247d2a53b39200a743a36c893ac91f Mon Sep 17 00:00:00 2001 From: xufei Date: Wed, 1 Jun 2022 19:50:27 +0800 Subject: [PATCH 080/127] enable new function test framework by default (#5029) ref pingcap/tiflash#4830 --- .../tests/gtest_arithmetic_functions.cpp | 30 ++-- dbms/src/Functions/tests/gtest_bitand.cpp | 148 +++++++++--------- dbms/src/Functions/tests/gtest_bitnot.cpp | 30 ++-- dbms/src/Functions/tests/gtest_bitor.cpp | 148 +++++++++--------- dbms/src/Functions/tests/gtest_bitxor.cpp | 148 +++++++++--------- dbms/src/Functions/tests/gtest_date_add.cpp | 32 ++-- .../gtest_date_or_datetime_to_something.cpp | 2 +- dbms/src/Functions/tests/gtest_date_sub.cpp | 32 ++-- .../tests/gtest_datetime_daymonthyear.cpp | 6 +- .../tests/gtest_duration_pushdown.cpp | 12 +- .../tests/gtest_functions_round_with_frac.cpp | 15 +- dbms/src/Functions/tests/gtest_ifnull.cpp | 23 +-- .../Functions/tests/gtest_inet_aton_ntoa.cpp | 2 +- .../Functions/tests/gtest_is_true_false.cpp | 8 +- .../Functions/tests/gtest_least_greatest.cpp | 4 +- dbms/src/Functions/tests/gtest_logical.cpp | 8 +- dbms/src/Functions/tests/gtest_regexp.cpp | 62 ++++---- .../src/Functions/tests/gtest_string_left.cpp | 13 +- .../Functions/tests/gtest_string_lrtrim.cpp | 20 +-- .../src/Functions/tests/gtest_strings_cmp.cpp | 18 +-- .../Functions/tests/gtest_strings_format.cpp | 12 +- .../Functions/tests/gtest_strings_right.cpp | 3 +- .../Functions/tests/gtest_strings_search.cpp | 97 +++++++----- .../tests/gtest_strings_tidb_concat.cpp | 5 +- .../Functions/tests/gtest_strings_trim.cpp | 14 +- dbms/src/Functions/tests/gtest_substring.cpp | 2 +- .../Functions/tests/gtest_substring_index.cpp | 4 +- .../Functions/tests/gtest_unix_timestamp.cpp | 8 +- dbms/src/TestUtils/FunctionTestUtils.h | 10 +- 29 files changed, 465 insertions(+), 451 deletions(-) diff --git a/dbms/src/Functions/tests/gtest_arithmetic_functions.cpp b/dbms/src/Functions/tests/gtest_arithmetic_functions.cpp index 1a542c908ee..1d548a4c2d2 100644 --- a/dbms/src/Functions/tests/gtest_arithmetic_functions.cpp +++ b/dbms/src/Functions/tests/gtest_arithmetic_functions.cpp @@ -128,6 +128,10 @@ try null_or_zero_field.push_back(Field(DecimalField(0, 0))); std::vector values{10, 2, 20, 8, 10, 0, 30, 8, 16, 4}; + /// The precision of a non-zero DecimalField must not less than minDecimalPrecision + /// the decimal_128_factor is used to make sure the precision is big enough + Int128 decimal_128_factor = 10000000000; + decimal_128_factor *= 1000000000; const size_t size = 10; @@ -172,7 +176,7 @@ try if (col2_type->onlyNull()) continue; auto c1 = nullable_decimal_type_1->createColumnConst(size, Null()); - auto c2 = col2_type->createColumnConst(size, Field(DecimalField(2, 0))); + auto c2 = col2_type->createColumnConst(size, Field(DecimalField(2 * decimal_128_factor, 0))); auto col1 = ColumnWithTypeAndName(std::move(c1), nullable_decimal_type_1, "col1"); auto col2 = ColumnWithTypeAndName(std::move(c2), col2_type, "col2"); auto result = executeFunction(func_name, {col1, col2}); @@ -193,7 +197,7 @@ try continue; if (!col2_value.isNull() && col2_type->onlyNull()) continue; - auto c1 = col1_type->createColumnConst(size, Field(DecimalField(100, 2))); + auto c1 = col1_type->createColumnConst(size, Field(DecimalField(100 * decimal_128_factor, 2))); auto c2 = col2_type->createColumnConst(size, col2_value); auto col1 = ColumnWithTypeAndName(std::move(c1), col1_type, "col1"); auto col2 = ColumnWithTypeAndName(std::move(c2), col2_type, "col2"); @@ -211,11 +215,11 @@ try { if (col1_type->onlyNull() || col2_type->onlyNull()) continue; - auto c1 = col1_type->createColumnConst(size, Field(DecimalField(1000, 2))); - auto c2 = col2_type->createColumnConst(size, Field(DecimalField(2, 0))); + auto c1 = col1_type->createColumnConst(size, Field(DecimalField(1000 * decimal_128_factor, 2))); + auto c2 = col2_type->createColumnConst(size, Field(DecimalField(2 * decimal_128_factor, 0))); auto col1 = ColumnWithTypeAndName(std::move(c1), col1_type, "col1"); auto col2 = ColumnWithTypeAndName(std::move(c2), col2_type, "col2"); - auto res_col = executeFunction(func_name, {col1, col2}).column; + auto res_col = executeFunction(func_name, {col1, col2}, nullptr, false).column; ASSERT_TRUE(size == res_col->size()); Field res_field; for (size_t i = 0; i < size; i++) @@ -246,7 +250,7 @@ try if (col1_type->isNullable() && col1_null_map[i]) c1_mutable->insert(Null()); else - c1_mutable->insert(Field(DecimalField(values[i], 2))); + c1_mutable->insert(Field(DecimalField(values[i] * decimal_128_factor, 2))); } auto c2 = col2_type->createColumnConst(values.size(), col2_value); @@ -271,9 +275,9 @@ try if (col1_type->isNullable() && col1_null_map[i]) c1_mutable->insert(Null()); else - c1_mutable->insert(Field(DecimalField(values[i], 2))); + c1_mutable->insert(Field(DecimalField(values[i] * decimal_128_factor, 2))); } - auto c2 = col2_type->createColumnConst(values.size(), Field(DecimalField(2, 0))); + auto c2 = col2_type->createColumnConst(values.size(), Field(DecimalField(2 * decimal_128_factor, 0))); auto col1 = ColumnWithTypeAndName(std::move(c1_mutable), col1_type, "col1"); auto col2 = ColumnWithTypeAndName(std::move(c2), col2_type, "col2"); @@ -312,7 +316,7 @@ try if (col2_type->isNullable() && col2_null_map[i]) c2->insert(Null()); else - c2->insert(Field(DecimalField(values[i], 2))); + c2->insert(Field(DecimalField(values[i] * decimal_128_factor, 2))); } auto col1 = ColumnWithTypeAndName(std::move(c1), col1_type, "col1"); auto col2 = ColumnWithTypeAndName(std::move(c2), col2_type, "col2"); @@ -334,14 +338,14 @@ try if (values[i] != 0) value *= values[i]; } - auto c1 = col1_type->createColumnConst(size, Field(DecimalField(value, 2))); + auto c1 = col1_type->createColumnConst(size, Field(DecimalField(value * decimal_128_factor, 2))); auto c2 = col2_type->createColumn(); for (size_t i = 0; i < values.size(); i++) { if (col2_type->isNullable() && col2_null_map[i]) c2->insert(Null()); else - c2->insert(Field(DecimalField(values[i], 0))); + c2->insert(Field(DecimalField(values[i] * decimal_128_factor, 0))); } auto col1 = ColumnWithTypeAndName(std::move(c1), col1_type, "col1"); auto col2 = ColumnWithTypeAndName(std::move(c2), col2_type, "col2"); @@ -377,11 +381,11 @@ try if (col1_type->isNullable() && col1_null_map[i]) c1->insert(Null()); else - c1->insert(Field(DecimalField(values[i], 2))); + c1->insert(Field(DecimalField(values[i] * decimal_128_factor, 2))); if (col2_type->isNullable() && col2_null_map[i]) c2->insert(Null()); else - c2->insert(Field(DecimalField(values[i], 0))); + c2->insert(Field(DecimalField(values[i] * decimal_128_factor, 0))); } auto col1 = ColumnWithTypeAndName(std::move(c1), col1_type, "col1"); auto col2 = ColumnWithTypeAndName(std::move(c2), col2_type, "col2"); diff --git a/dbms/src/Functions/tests/gtest_bitand.cpp b/dbms/src/Functions/tests/gtest_bitand.cpp index b77bc5e8547..88c70847d98 100644 --- a/dbms/src/Functions/tests/gtest_bitand.cpp +++ b/dbms/src/Functions/tests/gtest_bitand.cpp @@ -40,7 +40,7 @@ class TestFunctionBitAnd : public DB::tests::FunctionTest TEST_F(TestFunctionBitAnd, Simple) try { - ASSERT_BITAND(createColumn>({-1, 1}), createColumn>({0, 0}), createColumn>({0, 0})); + ASSERT_BITAND(createColumn>({-1, 1}), createColumn>({0, 0}), createColumn>({0, 0})); } CATCH @@ -49,21 +49,21 @@ TEST_F(TestFunctionBitAnd, TypePromotion) try { // Type Promotion - ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); // Type Promotion across signed/unsigned - ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn>({0}), createColumn>({0})); } CATCH @@ -71,51 +71,51 @@ TEST_F(TestFunctionBitAnd, Nullable) try { // Non Nullable - ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); - ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); - ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); - ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); + ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); + ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); + ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); + ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); - ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); - ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); + ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); + ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); - ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); - ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); - ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); - ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); + ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); + ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); + ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); + ASSERT_BITAND(createColumn({1}), createColumn({0}), createColumn({0})); // Across Nullable and non-Nullable - ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); + ASSERT_BITAND(createColumn({1}), createColumn>({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); - ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); + ASSERT_BITAND(createColumn>({1}), createColumn({0}), createColumn>({0})); } CATCH @@ -129,48 +129,48 @@ try /// 4. ColumnConst, value != null /// 5. ColumnConst, value = null - ASSERT_BITAND(createColumn({0, 0, 1, 1}), createColumn({0, 1, 0, 1}), createColumn({0, 0, 0, 1})); - ASSERT_BITAND(createColumn({0, 0, 1, 1}), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 0, std::nullopt, std::nullopt})); - ASSERT_BITAND(createColumn({0, 0, 1, 1}), createConstColumn(4, 0), createColumn({0, 0, 0, 0})); - ASSERT_BITAND(createColumn({0, 0, 1, 1}), createConstColumn>(4, 0), createColumn>({0, 0, 0, 0})); - ASSERT_BITAND(createColumn({0, 0, 1, 1}), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); // become const in wrapInNullable - - ASSERT_BITAND(createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn({0, 1, 0, 1}), createColumn>({0, 1, std::nullopt, std::nullopt})); - ASSERT_BITAND(createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); - ASSERT_BITAND(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn(4, 0), createColumn>({0, 0, std::nullopt, std::nullopt})); - ASSERT_BITAND(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, 0), createColumn>({0, 0, std::nullopt, std::nullopt})); - ASSERT_BITAND(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); - - ASSERT_BITAND(createConstColumn(4, 0), createColumn({0, 1, 0, 1}), createColumn({0, 0, 0, 0})); - ASSERT_BITAND(createConstColumn(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 0, std::nullopt, std::nullopt})); - ASSERT_BITAND(createConstColumn(4, 0), createConstColumn(4, 0), createConstColumn(4, 0)); - ASSERT_BITAND(createConstColumn(4, 0), createConstColumn>(4, 0), createConstColumn>(4, 0)); - ASSERT_BITAND(createConstColumn(4, 0), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); - - ASSERT_BITAND(createConstColumn>(4, 0), createColumn({0, 1, 0, 1}), createColumn>({0, 0, 0, 0})); - ASSERT_BITAND(createConstColumn>(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 0, std::nullopt, std::nullopt})); - ASSERT_BITAND(createConstColumn>(4, 0), createConstColumn(4, 0), createConstColumn>(4, 0)); - ASSERT_BITAND(createConstColumn>(4, 0), createConstColumn>(4, 0), createConstColumn>(4, 0)); - ASSERT_BITAND(createConstColumn>(4, 0), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); - - ASSERT_BITAND(createConstColumn>(4, std::nullopt), createColumn({0, 1, 0, 1}), createConstColumn>(4, std::nullopt)); - ASSERT_BITAND(createConstColumn>(4, std::nullopt), createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, std::nullopt)); - ASSERT_BITAND(createConstColumn>(4, std::nullopt), createConstColumn(4, 0), createConstColumn>(4, std::nullopt)); - ASSERT_BITAND(createConstColumn>(4, std::nullopt), createConstColumn>(4, 0), createConstColumn>(4, std::nullopt)); - ASSERT_BITAND(createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); + ASSERT_BITAND(createColumn({0, 0, 1, 1}), createColumn({0, 1, 0, 1}), createColumn({0, 0, 0, 1})); + ASSERT_BITAND(createColumn({0, 0, 1, 1}), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 0, std::nullopt, std::nullopt})); + ASSERT_BITAND(createColumn({0, 0, 1, 1}), createConstColumn(4, 0), createColumn({0, 0, 0, 0})); + ASSERT_BITAND(createColumn({0, 0, 1, 1}), createConstColumn>(4, 0), createColumn({0, 0, 0, 0})); + ASSERT_BITAND(createColumn({0, 0, 1, 1}), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); // become const in wrapInNullable + + ASSERT_BITAND(createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn({0, 1, 0, 1}), createColumn>({0, 1, std::nullopt, std::nullopt})); + ASSERT_BITAND(createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); + ASSERT_BITAND(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn(4, 0), createColumn>({0, 0, std::nullopt, std::nullopt})); + ASSERT_BITAND(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, 0), createColumn>({0, 0, std::nullopt, std::nullopt})); + ASSERT_BITAND(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); + + ASSERT_BITAND(createConstColumn(4, 0), createColumn({0, 1, 0, 1}), createColumn({0, 0, 0, 0})); + ASSERT_BITAND(createConstColumn(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 0, std::nullopt, std::nullopt})); + ASSERT_BITAND(createConstColumn(4, 0), createConstColumn(4, 0), createConstColumn(4, 0)); + ASSERT_BITAND(createConstColumn(4, 0), createConstColumn>(4, 0), createConstColumn(4, 0)); + ASSERT_BITAND(createConstColumn(4, 0), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); + + ASSERT_BITAND(createConstColumn>(4, 0), createColumn({0, 1, 0, 1}), createColumn({0, 0, 0, 0})); + ASSERT_BITAND(createConstColumn>(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 0, std::nullopt, std::nullopt})); + ASSERT_BITAND(createConstColumn>(4, 0), createConstColumn(4, 0), createConstColumn(4, 0)); + ASSERT_BITAND(createConstColumn>(4, 0), createConstColumn>(4, 0), createConstColumn(4, 0)); + ASSERT_BITAND(createConstColumn>(4, 0), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); + + ASSERT_BITAND(createConstColumn>(4, std::nullopt), createColumn({0, 1, 0, 1}), createConstColumn>(4, std::nullopt)); + ASSERT_BITAND(createConstColumn>(4, std::nullopt), createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, std::nullopt)); + ASSERT_BITAND(createConstColumn>(4, std::nullopt), createConstColumn(4, 0), createConstColumn>(4, std::nullopt)); + ASSERT_BITAND(createConstColumn>(4, std::nullopt), createConstColumn(4, 0), createConstColumn>(4, std::nullopt)); + ASSERT_BITAND(createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); } CATCH TEST_F(TestFunctionBitAnd, Boundary) try { - ASSERT_BITAND(createColumn({127, 127, -128, -128}), createColumn({0, 255, 0, 255}), createColumn({0, 127, 0, -128})); - ASSERT_BITAND(createColumn({127, 127, -128, -128}), createColumn({0, 65535, 0, 65535}), createColumn({0, 127, 0, -128})); - ASSERT_BITAND(createColumn({32767, 32767, -32768, -32768}), createColumn({0, 255, 0, 255}), createColumn({0, 255, 0, 0})); + ASSERT_BITAND(createColumn({127, 127, -128, -128}), createColumn({0, 255, 0, 255}), createColumn({0, 127, 0, 128})); + ASSERT_BITAND(createColumn({127, 127, -128, -128}), createColumn({0, 65535, 0, 65535}), createColumn({0, 127, 0, 65408})); + ASSERT_BITAND(createColumn({32767, 32767, -32768, -32768}), createColumn({0, 255, 0, 255}), createColumn({0, 255, 0, 0})); ASSERT_BITAND(createColumn({0, 0, 1, 1, -1, -1, INT64_MAX, INT64_MAX, INT64_MIN, INT64_MIN}), createColumn({0, UINT64_MAX, 0, UINT64_MAX, 0, UINT64_MAX, 0, UINT64_MAX, 0, UINT64_MAX}), - createColumn({0, 0, 0, 1, 0, -1, 0, INT64_MAX, 0, INT64_MIN})); + createColumn({0, 0, 0, 1, 0, UINT64_MAX, 0, INT64_MAX, 0, 9223372036854775808ull})); } CATCH diff --git a/dbms/src/Functions/tests/gtest_bitnot.cpp b/dbms/src/Functions/tests/gtest_bitnot.cpp index 6b96e59718e..0b542d87961 100644 --- a/dbms/src/Functions/tests/gtest_bitnot.cpp +++ b/dbms/src/Functions/tests/gtest_bitnot.cpp @@ -41,7 +41,7 @@ class TestFunctionBitNot : public DB::tests::FunctionTest TEST_F(TestFunctionBitNot, Simple) try { - ASSERT_BITNOT(createColumn>({-1, 1}), createColumn>({0, -2})); + ASSERT_BITNOT(createColumn>({-1, 1}), createColumn>({0, UINT64_MAX - 1})); } CATCH @@ -49,21 +49,21 @@ CATCH TEST_F(TestFunctionBitNot, TypeTest) try { - ASSERT_BITNOT(createColumn>({1}), createColumn>({-2})); - ASSERT_BITNOT(createColumn>({1}), createColumn>({-2})); - ASSERT_BITNOT(createColumn>({1}), createColumn>({-2})); - ASSERT_BITNOT(createColumn>({1}), createColumn>({-2})); - - ASSERT_BITNOT(createColumn>({1}), createColumn>({UINT8_MAX - 1})); - ASSERT_BITNOT(createColumn>({1}), createColumn>({UINT16_MAX - 1})); - ASSERT_BITNOT(createColumn>({1}), createColumn>({UINT32_MAX - 1})); + ASSERT_BITNOT(createColumn>({1}), createColumn>({UINT64_MAX - 1})); + ASSERT_BITNOT(createColumn>({1}), createColumn>({UINT64_MAX - 1})); + ASSERT_BITNOT(createColumn>({1}), createColumn>({UINT64_MAX - 1})); + ASSERT_BITNOT(createColumn>({1}), createColumn>({UINT64_MAX - 1})); + + ASSERT_BITNOT(createColumn>({1}), createColumn>({UINT64_MAX - 1})); + ASSERT_BITNOT(createColumn>({1}), createColumn>({UINT64_MAX - 1})); + ASSERT_BITNOT(createColumn>({1}), createColumn>({UINT64_MAX - 1})); ASSERT_BITNOT(createColumn>({1}), createColumn>({UINT64_MAX - 1})); - ASSERT_BITNOT(createColumn({0, 0, 1, 1}), createColumn({-1, -1, -2, -2})); - ASSERT_BITNOT(createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({-1, -2, std::nullopt, std::nullopt})); - ASSERT_BITNOT(createConstColumn(4, 0), createConstColumn(4, -1)); - ASSERT_BITNOT(createConstColumn>(4, 0), createConstColumn>(4, -1)); - ASSERT_BITNOT(createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); + ASSERT_BITNOT(createColumn({0, 0, 1, 1}), createColumn({UINT64_MAX, UINT64_MAX, UINT64_MAX - 1, UINT64_MAX - 1})); + ASSERT_BITNOT(createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({UINT64_MAX, UINT64_MAX - 1, std::nullopt, std::nullopt})); + ASSERT_BITNOT(createConstColumn(4, 0), createConstColumn(4, UINT64_MAX)); + ASSERT_BITNOT(createConstColumn>(4, 0), createConstColumn(4, UINT64_MAX)); + ASSERT_BITNOT(createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); } CATCH @@ -71,7 +71,7 @@ TEST_F(TestFunctionBitNot, Boundary) try { ASSERT_BITNOT(createColumn({0, 1, -1, INT64_MAX, INT64_MIN}), - createColumn({-1, -2, 0, INT64_MIN, INT64_MAX})); + createColumn({UINT64_MAX, UINT64_MAX - 1, 0, static_cast(INT64_MAX) + 1, INT64_MAX})); ASSERT_BITNOT(createColumn({0, 1, UINT64_MAX}), createColumn({UINT64_MAX, UINT64_MAX - 1, 0})); diff --git a/dbms/src/Functions/tests/gtest_bitor.cpp b/dbms/src/Functions/tests/gtest_bitor.cpp index a1611659913..fb4794c31b2 100644 --- a/dbms/src/Functions/tests/gtest_bitor.cpp +++ b/dbms/src/Functions/tests/gtest_bitor.cpp @@ -40,7 +40,7 @@ class TestFunctionBitOr : public DB::tests::FunctionTest TEST_F(TestFunctionBitOr, Simple) try { - ASSERT_BITOR(createColumn>({-1, 1}), createColumn>({0, 0}), createColumn>({-1, 1})); + ASSERT_BITOR(createColumn>({-1, 1}), createColumn>({0, 0}), createColumn>({UINT64_MAX, 1})); } CATCH @@ -49,21 +49,21 @@ TEST_F(TestFunctionBitOr, TypePromotion) try { // Type Promotion - ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); // Type Promotion across signed/unsigned - ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); } CATCH @@ -71,51 +71,51 @@ TEST_F(TestFunctionBitOr, Nullable) try { // Non Nullable - ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITOR(createColumn({1}), createColumn({0}), createColumn({1})); // Across Nullable and non-Nullable - ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITOR(createColumn>({1}), createColumn({0}), createColumn>({1})); } CATCH @@ -129,48 +129,48 @@ try /// 4. ColumnConst, value != null /// 5. ColumnConst, value = null - ASSERT_BITOR(createColumn({0, 0, 1, 1}), createColumn({0, 1, 0, 1}), createColumn({0, 1, 1, 1})); - ASSERT_BITOR(createColumn({0, 0, 1, 1}), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); - ASSERT_BITOR(createColumn({0, 0, 1, 1}), createConstColumn(4, 0), createColumn({0, 0, 1, 1})); - ASSERT_BITOR(createColumn({0, 0, 1, 1}), createConstColumn>(4, 0), createColumn>({0, 0, 1, 1})); - ASSERT_BITOR(createColumn({0, 0, 1, 1}), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); // become const in wrapInNullable - - ASSERT_BITOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn({0, 1, 0, 1}), createColumn>({0, 1, std::nullopt, std::nullopt})); - ASSERT_BITOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); - ASSERT_BITOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt})); - ASSERT_BITOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt})); - ASSERT_BITOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); - - ASSERT_BITOR(createConstColumn(4, 0), createColumn({0, 1, 0, 1}), createColumn({0, 1, 0, 1})); - ASSERT_BITOR(createConstColumn(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); - ASSERT_BITOR(createConstColumn(4, 0), createConstColumn(4, 0), createConstColumn(4, 0)); - ASSERT_BITOR(createConstColumn(4, 0), createConstColumn>(4, 0), createConstColumn>(4, 0)); - ASSERT_BITOR(createConstColumn(4, 0), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); - - ASSERT_BITOR(createConstColumn>(4, 0), createColumn({0, 1, 0, 1}), createColumn>({0, 1, 0, 1})); - ASSERT_BITOR(createConstColumn>(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); - ASSERT_BITOR(createConstColumn>(4, 0), createConstColumn(4, 0), createConstColumn>(4, 0)); - ASSERT_BITOR(createConstColumn>(4, 0), createConstColumn>(4, 0), createConstColumn>(4, 0)); - ASSERT_BITOR(createConstColumn>(4, 0), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); - - ASSERT_BITOR(createConstColumn>(4, std::nullopt), createColumn({0, 1, 0, 1}), createConstColumn>(4, std::nullopt)); - ASSERT_BITOR(createConstColumn>(4, std::nullopt), createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, std::nullopt)); - ASSERT_BITOR(createConstColumn>(4, std::nullopt), createConstColumn(4, 0), createConstColumn>(4, std::nullopt)); - ASSERT_BITOR(createConstColumn>(4, std::nullopt), createConstColumn>(4, 0), createConstColumn>(4, std::nullopt)); - ASSERT_BITOR(createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); + ASSERT_BITOR(createColumn({0, 0, 1, 1}), createColumn({0, 1, 0, 1}), createColumn({0, 1, 1, 1})); + ASSERT_BITOR(createColumn({0, 0, 1, 1}), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); + ASSERT_BITOR(createColumn({0, 0, 1, 1}), createConstColumn(4, 0), createColumn({0, 0, 1, 1})); + ASSERT_BITOR(createColumn({0, 0, 1, 1}), createConstColumn>(4, 0), createColumn({0, 0, 1, 1})); + ASSERT_BITOR(createColumn({0, 0, 1, 1}), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); // become const in wrapInNullable + + ASSERT_BITOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn({0, 1, 0, 1}), createColumn>({0, 1, std::nullopt, std::nullopt})); + ASSERT_BITOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); + ASSERT_BITOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt})); + ASSERT_BITOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt})); + ASSERT_BITOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); + + ASSERT_BITOR(createConstColumn(4, 0), createColumn({0, 1, 0, 1}), createColumn({0, 1, 0, 1})); + ASSERT_BITOR(createConstColumn(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); + ASSERT_BITOR(createConstColumn(4, 0), createConstColumn(4, 0), createConstColumn(4, 0)); + ASSERT_BITOR(createConstColumn(4, 0), createConstColumn>(4, 0), createConstColumn(4, 0)); + ASSERT_BITOR(createConstColumn(4, 0), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); + + ASSERT_BITOR(createConstColumn>(4, 0), createColumn({0, 1, 0, 1}), createColumn({0, 1, 0, 1})); + ASSERT_BITOR(createConstColumn>(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); + ASSERT_BITOR(createConstColumn>(4, 0), createConstColumn(4, 0), createConstColumn(4, 0)); + ASSERT_BITOR(createConstColumn>(4, 0), createConstColumn>(4, 0), createConstColumn(4, 0)); + ASSERT_BITOR(createConstColumn>(4, 0), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); + + ASSERT_BITOR(createConstColumn>(4, std::nullopt), createColumn({0, 1, 0, 1}), createConstColumn>(4, std::nullopt)); + ASSERT_BITOR(createConstColumn>(4, std::nullopt), createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, std::nullopt)); + ASSERT_BITOR(createConstColumn>(4, std::nullopt), createConstColumn(4, 0), createConstColumn>(4, std::nullopt)); + ASSERT_BITOR(createConstColumn>(4, std::nullopt), createConstColumn(4, 0), createConstColumn>(4, std::nullopt)); + ASSERT_BITOR(createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); } CATCH TEST_F(TestFunctionBitOr, Boundary) try { - ASSERT_BITOR(createColumn({127, 127, -128, -128}), createColumn({0, 255, 0, 255}), createColumn({127, -1, -128, -1})); - ASSERT_BITOR(createColumn({127, 127, -128, -128}), createColumn({0, 65535, 0, 65535}), createColumn({127, -1, -128, -1})); - ASSERT_BITOR(createColumn({32767, 32767, -32768, -32768}), createColumn({0, 255, 0, 255}), createColumn({32767, 32767, -32768, -32513})); + ASSERT_BITOR(createColumn({127, 127, -128, -128}), createColumn({0, 255, 0, 255}), createColumn({127, 255, 18446744073709551488ull, UINT64_MAX})); + ASSERT_BITOR(createColumn({127, 127, -128, -128}), createColumn({0, 65535, 0, 65535}), createColumn({127, 65535, 18446744073709551488ull, UINT64_MAX})); + ASSERT_BITOR(createColumn({32767, 32767, -32768, -32768}), createColumn({0, 255, 0, 255}), createColumn({32767, 32767, 18446744073709518848ull, 18446744073709519103ull})); ASSERT_BITOR(createColumn({0, 0, 1, 1, -1, -1, INT64_MAX, INT64_MAX, INT64_MIN, INT64_MIN}), createColumn({0, UINT64_MAX, 0, UINT64_MAX, 0, UINT64_MAX, 0, UINT64_MAX, 0, UINT64_MAX}), - createColumn({0, -1, 1, -1, -1, -1, INT64_MAX, -1, INT64_MIN, -1})); + createColumn({0, UINT64_MAX, 1, UINT64_MAX, UINT64_MAX, UINT64_MAX, INT64_MAX, UINT64_MAX, 9223372036854775808ull, UINT64_MAX})); } CATCH diff --git a/dbms/src/Functions/tests/gtest_bitxor.cpp b/dbms/src/Functions/tests/gtest_bitxor.cpp index 60f3027d2b5..85cc51a8a49 100644 --- a/dbms/src/Functions/tests/gtest_bitxor.cpp +++ b/dbms/src/Functions/tests/gtest_bitxor.cpp @@ -40,7 +40,7 @@ class TestFunctionBitXor : public DB::tests::FunctionTest TEST_F(TestFunctionBitXor, Simple) try { - ASSERT_BITXOR(createColumn>({-1, 1}), createColumn>({0, 0}), createColumn>({-1, 1})); + ASSERT_BITXOR(createColumn>({-1, 1}), createColumn>({0, 0}), createColumn>({UINT64_MAX, 1})); } CATCH @@ -49,21 +49,21 @@ TEST_F(TestFunctionBitXor, TypePromotion) try { // Type Promotion - ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); // Type Promotion across signed/unsigned - ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn>({0}), createColumn>({1})); } CATCH @@ -71,51 +71,51 @@ TEST_F(TestFunctionBitXor, Nullable) try { // Non Nullable - ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); - ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); + ASSERT_BITXOR(createColumn({1}), createColumn({0}), createColumn({1})); // Across Nullable and non-Nullable - ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn({1}), createColumn>({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); - ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); + ASSERT_BITXOR(createColumn>({1}), createColumn({0}), createColumn>({1})); } CATCH @@ -129,48 +129,48 @@ try /// 4. ColumnConst, value != null /// 5. ColumnConst, value = null - ASSERT_BITXOR(createColumn({0, 0, 1, 1}), createColumn({0, 1, 0, 1}), createColumn({0, 1, 1, 0})); - ASSERT_BITXOR(createColumn({0, 0, 1, 1}), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); - ASSERT_BITXOR(createColumn({0, 0, 1, 1}), createConstColumn(4, 0), createColumn({0, 0, 1, 1})); - ASSERT_BITXOR(createColumn({0, 0, 1, 1}), createConstColumn>(4, 0), createColumn>({0, 0, 1, 1})); - ASSERT_BITXOR(createColumn({0, 0, 1, 1}), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); // become const in wrapInNullable - - ASSERT_BITXOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn({0, 1, 0, 1}), createColumn>({0, 0, std::nullopt, std::nullopt})); - ASSERT_BITXOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 0, std::nullopt, std::nullopt})); - ASSERT_BITXOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt})); - ASSERT_BITXOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt})); - ASSERT_BITXOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); - - ASSERT_BITXOR(createConstColumn(4, 0), createColumn({0, 1, 0, 1}), createColumn({0, 1, 0, 1})); - ASSERT_BITXOR(createConstColumn(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); - ASSERT_BITXOR(createConstColumn(4, 0), createConstColumn(4, 0), createConstColumn(4, 0)); - ASSERT_BITXOR(createConstColumn(4, 0), createConstColumn>(4, 0), createConstColumn>(4, 0)); - ASSERT_BITXOR(createConstColumn(4, 0), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); - - ASSERT_BITXOR(createConstColumn>(4, 0), createColumn({0, 1, 0, 1}), createColumn>({0, 1, 0, 1})); - ASSERT_BITXOR(createConstColumn>(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); - ASSERT_BITXOR(createConstColumn>(4, 0), createConstColumn(4, 0), createConstColumn>(4, 0)); - ASSERT_BITXOR(createConstColumn>(4, 0), createConstColumn>(4, 0), createConstColumn>(4, 0)); - ASSERT_BITXOR(createConstColumn>(4, 0), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); - - ASSERT_BITXOR(createConstColumn>(4, std::nullopt), createColumn({0, 1, 0, 1}), createConstColumn>(4, std::nullopt)); - ASSERT_BITXOR(createConstColumn>(4, std::nullopt), createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, std::nullopt)); - ASSERT_BITXOR(createConstColumn>(4, std::nullopt), createConstColumn(4, 0), createConstColumn>(4, std::nullopt)); - ASSERT_BITXOR(createConstColumn>(4, std::nullopt), createConstColumn>(4, 0), createConstColumn>(4, std::nullopt)); - ASSERT_BITXOR(createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); + ASSERT_BITXOR(createColumn({0, 0, 1, 1}), createColumn({0, 1, 0, 1}), createColumn({0, 1, 1, 0})); + ASSERT_BITXOR(createColumn({0, 0, 1, 1}), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); + ASSERT_BITXOR(createColumn({0, 0, 1, 1}), createConstColumn(4, 0), createColumn({0, 0, 1, 1})); + ASSERT_BITXOR(createColumn({0, 0, 1, 1}), createConstColumn>(4, 0), createColumn({0, 0, 1, 1})); + ASSERT_BITXOR(createColumn({0, 0, 1, 1}), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); // become const in wrapInNullable + + ASSERT_BITXOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn({0, 1, 0, 1}), createColumn>({0, 0, std::nullopt, std::nullopt})); + ASSERT_BITXOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 0, std::nullopt, std::nullopt})); + ASSERT_BITXOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt})); + ASSERT_BITXOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt})); + ASSERT_BITXOR(createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); + + ASSERT_BITXOR(createConstColumn(4, 0), createColumn({0, 1, 0, 1}), createColumn({0, 1, 0, 1})); + ASSERT_BITXOR(createConstColumn(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); + ASSERT_BITXOR(createConstColumn(4, 0), createConstColumn(4, 0), createConstColumn(4, 0)); + ASSERT_BITXOR(createConstColumn(4, 0), createConstColumn>(4, 0), createConstColumn(4, 0)); + ASSERT_BITXOR(createConstColumn(4, 0), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); + + ASSERT_BITXOR(createConstColumn>(4, 0), createColumn({0, 1, 0, 1}), createColumn({0, 1, 0, 1})); + ASSERT_BITXOR(createConstColumn>(4, 0), createColumn>({0, 1, std::nullopt, std::nullopt}), createColumn>({0, 1, std::nullopt, std::nullopt})); + ASSERT_BITXOR(createConstColumn>(4, 0), createConstColumn(4, 0), createConstColumn(4, 0)); + ASSERT_BITXOR(createConstColumn>(4, 0), createConstColumn>(4, 0), createConstColumn(4, 0)); + ASSERT_BITXOR(createConstColumn>(4, 0), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); + + ASSERT_BITXOR(createConstColumn>(4, std::nullopt), createColumn({0, 1, 0, 1}), createConstColumn>(4, std::nullopt)); + ASSERT_BITXOR(createConstColumn>(4, std::nullopt), createColumn>({0, 1, std::nullopt, std::nullopt}), createConstColumn>(4, std::nullopt)); + ASSERT_BITXOR(createConstColumn>(4, std::nullopt), createConstColumn(4, 0), createConstColumn>(4, std::nullopt)); + ASSERT_BITXOR(createConstColumn>(4, std::nullopt), createConstColumn(4, 0), createConstColumn>(4, std::nullopt)); + ASSERT_BITXOR(createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt), createConstColumn>(4, std::nullopt)); } CATCH TEST_F(TestFunctionBitXor, Boundary) try { - ASSERT_BITXOR(createColumn({127, 127, -128, -128}), createColumn({0, 255, 0, 255}), createColumn({127, -128, -128, 127})); - ASSERT_BITXOR(createColumn({127, 127, -128, -128}), createColumn({0, 65535, 0, 65535}), createColumn({127, -128, -128, 127})); - ASSERT_BITXOR(createColumn({32767, 32767, -32768, -32768}), createColumn({0, 255, 0, 255}), createColumn({32767, 32512, -32768, -32513})); + ASSERT_BITXOR(createColumn({127, 127, -128, -128}), createColumn({0, 255, 0, 255}), createColumn({127, 128, 18446744073709551488ull, 18446744073709551487ull})); + ASSERT_BITXOR(createColumn({127, 127, -128, -128}), createColumn({0, 65535, 0, 65535}), createColumn({127, 65408, 18446744073709551488ull, 18446744073709486207ull})); + ASSERT_BITXOR(createColumn({32767, 32767, -32768, -32768}), createColumn({0, 255, 0, 255}), createColumn({32767, 32512, 18446744073709518848ull, 18446744073709519103ull})); ASSERT_BITXOR(createColumn({0, 0, 1, 1, -1, -1, INT64_MAX, INT64_MAX, INT64_MIN, INT64_MIN}), createColumn({0, UINT64_MAX, 0, UINT64_MAX, 0, UINT64_MAX, 0, UINT64_MAX, 0, UINT64_MAX}), - createColumn({0, -1, 1, -2, -1, 0, INT64_MAX, INT64_MIN, INT64_MIN, INT64_MAX})); + createColumn({0, UINT64_MAX, 1, UINT64_MAX - 1, UINT64_MAX, 0, INT64_MAX, 9223372036854775808ull, 9223372036854775808ull, INT64_MAX})); } CATCH diff --git a/dbms/src/Functions/tests/gtest_date_add.cpp b/dbms/src/Functions/tests/gtest_date_add.cpp index 19effb11fb6..d87209c0805 100644 --- a/dbms/src/Functions/tests/gtest_date_add.cpp +++ b/dbms/src/Functions/tests/gtest_date_add.cpp @@ -114,58 +114,58 @@ TEST_F(Dateadd, dateAddStringRealUnitTest) { ASSERT_COLUMN_EQ( toNullableVec({"2012-12-14", "2012-12-14 12:12:12", "2012-12-14", "2012-12-14 12:12:12"}), - executeFunction("addDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(1.6)))); + executeFunction("addDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(1.6)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-13", "2012-12-13 12:12:12", "2012-12-13", "2012-12-13 12:12:12"}), - executeFunction("addDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(1.4)))); + executeFunction("addDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(1.4)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-14", "2012-12-14 12:12:12", "2012-12-14", "2012-12-14 12:12:12"}), - executeFunction("addDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toFloatVec({1.6, 1.6, 1.6, 1.6})))); + executeFunction("addDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toFloatVec({1.6, 1.6, 1.6, 1.6})))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-13", "2012-12-13 12:12:12", "2012-12-13", "2012-12-13 12:12:12"}), - executeFunction("addDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toFloatVec({1.4, 1.4, 1.4, 1.4})))); + executeFunction("addDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toFloatVec({1.4, 1.4, 1.4, 1.4})))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-26", "2012-12-26 12:12:12", "2012-12-26", "2012-12-26 12:12:12"}), - executeFunction("addWeeks", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(1.6)))); + executeFunction("addWeeks", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(1.6)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-19", "2012-12-19 12:12:12", "2012-12-19", "2012-12-19 12:12:12"}), - executeFunction("addWeeks", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(1.4)))); + executeFunction("addWeeks", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(1.4)))); ASSERT_COLUMN_EQ( toNullableVec({"2013-02-12", "2013-02-12 12:12:12", "2013-02-12", "2013-02-12 12:12:12"}), - executeFunction("addMonths", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(1.6)))); + executeFunction("addMonths", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(1.6)))); ASSERT_COLUMN_EQ( toNullableVec({"2013-01-12", "2013-01-12 12:12:12", "2013-01-12", "2013-01-12 12:12:12"}), - executeFunction("addMonths", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(1.4)))); + executeFunction("addMonths", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(1.4)))); ASSERT_COLUMN_EQ( toNullableVec({"2014-12-12", "2014-12-12 12:12:12", "2014-12-12", "2014-12-12 12:12:12"}), - executeFunction("addYears", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(1.6)))); + executeFunction("addYears", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(1.6)))); ASSERT_COLUMN_EQ( toNullableVec({"2013-12-12", "2013-12-12 12:12:12", "2013-12-12", "2013-12-12 12:12:12"}), - executeFunction("addYears", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(1.4)))); + executeFunction("addYears", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(1.4)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-12 02:00:00", "2012-12-12 14:12:12", "2012-12-12 02:00:00", "2012-12-12 14:12:12"}), - executeFunction("addHours", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(1.6)))); + executeFunction("addHours", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(1.6)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-12 01:00:00", "2012-12-12 13:12:12", "2012-12-12 01:00:00", "2012-12-12 13:12:12"}), - executeFunction("addHours", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(1.4)))); + executeFunction("addHours", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(1.4)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-12 00:02:00", "2012-12-12 12:14:12", "2012-12-12 00:02:00", "2012-12-12 12:14:12"}), - executeFunction("addMinutes", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(1.6)))); + executeFunction("addMinutes", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(1.6)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-12 00:01:00", "2012-12-12 12:13:12", "2012-12-12 00:01:00", "2012-12-12 12:13:12"}), - executeFunction("addMinutes", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(1.4)))); + executeFunction("addMinutes", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(1.4)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-12 00:00:02", "2012-12-12 12:12:14", "2012-12-12 00:00:02", "2012-12-12 12:12:14"}), - executeFunction("addSeconds", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(1.6)))); + executeFunction("addSeconds", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(1.6)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-12 00:00:01", "2012-12-12 12:12:13", "2012-12-12 00:00:01", "2012-12-12 12:12:13"}), - executeFunction("addSeconds", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(1.4)))); + executeFunction("addSeconds", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(1.4)))); } } // namespace DB::tests diff --git a/dbms/src/Functions/tests/gtest_date_or_datetime_to_something.cpp b/dbms/src/Functions/tests/gtest_date_or_datetime_to_something.cpp index 115218e5550..206c13b1ef6 100644 --- a/dbms/src/Functions/tests/gtest_date_or_datetime_to_something.cpp +++ b/dbms/src/Functions/tests/gtest_date_or_datetime_to_something.cpp @@ -66,7 +66,7 @@ try MyDateTime(2020, 10, 10, 0, 0, 0, 0).toPackedUInt()) .column; input_col = ColumnWithTypeAndName(data_col_ptr, data_type_ptr, "input"); - output_col = createConstColumn>(4, 4); + output_col = createConstColumn(4, 4); ASSERT_COLUMN_EQ(output_col, executeFunction(func_name, input_col)); // ColumnConst(null) diff --git a/dbms/src/Functions/tests/gtest_date_sub.cpp b/dbms/src/Functions/tests/gtest_date_sub.cpp index ad6751bac09..ee45e4e212b 100644 --- a/dbms/src/Functions/tests/gtest_date_sub.cpp +++ b/dbms/src/Functions/tests/gtest_date_sub.cpp @@ -114,58 +114,58 @@ TEST_F(Datesub, dateSubStringRealUnitTest) { ASSERT_COLUMN_EQ( toNullableVec({"2012-12-14", "2012-12-14 12:12:12", "2012-12-14", "2012-12-14 12:12:12"}), - executeFunction("subtractDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(-1.6)))); + executeFunction("subtractDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(-1.6)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-13", "2012-12-13 12:12:12", "2012-12-13", "2012-12-13 12:12:12"}), - executeFunction("subtractDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(-1.4)))); + executeFunction("subtractDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(-1.4)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-14", "2012-12-14 12:12:12", "2012-12-14", "2012-12-14 12:12:12"}), - executeFunction("subtractDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toFloatVec({-1.6, -1.6, -1.6, -1.6})))); + executeFunction("subtractDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toFloatVec({-1.6, -1.6, -1.6, -1.6})))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-13", "2012-12-13 12:12:12", "2012-12-13", "2012-12-13 12:12:12"}), - executeFunction("subtractDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toFloatVec({-1.4, -1.4, -1.4, -1.4})))); + executeFunction("subtractDays", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toFloatVec({-1.4, -1.4, -1.4, -1.4})))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-26", "2012-12-26 12:12:12", "2012-12-26", "2012-12-26 12:12:12"}), - executeFunction("subtractWeeks", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(-1.6)))); + executeFunction("subtractWeeks", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(-1.6)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-19", "2012-12-19 12:12:12", "2012-12-19", "2012-12-19 12:12:12"}), - executeFunction("subtractWeeks", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(-1.4)))); + executeFunction("subtractWeeks", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(-1.4)))); ASSERT_COLUMN_EQ( toNullableVec({"2013-02-12", "2013-02-12 12:12:12", "2013-02-12", "2013-02-12 12:12:12"}), - executeFunction("subtractMonths", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(-1.6)))); + executeFunction("subtractMonths", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(-1.6)))); ASSERT_COLUMN_EQ( toNullableVec({"2013-01-12", "2013-01-12 12:12:12", "2013-01-12", "2013-01-12 12:12:12"}), - executeFunction("subtractMonths", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(-1.4)))); + executeFunction("subtractMonths", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(-1.4)))); ASSERT_COLUMN_EQ( toNullableVec({"2014-12-12", "2014-12-12 12:12:12", "2014-12-12", "2014-12-12 12:12:12"}), - executeFunction("subtractYears", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(-1.6)))); + executeFunction("subtractYears", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(-1.6)))); ASSERT_COLUMN_EQ( toNullableVec({"2013-12-12", "2013-12-12 12:12:12", "2013-12-12", "2013-12-12 12:12:12"}), - executeFunction("subtractYears", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(-1.4)))); + executeFunction("subtractYears", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(-1.4)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-12 02:00:00", "2012-12-12 14:12:12", "2012-12-12 02:00:00", "2012-12-12 14:12:12"}), - executeFunction("subtractHours", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(-1.6)))); + executeFunction("subtractHours", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(-1.6)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-12 01:00:00", "2012-12-12 13:12:12", "2012-12-12 01:00:00", "2012-12-12 13:12:12"}), - executeFunction("subtractHours", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(-1.4)))); + executeFunction("subtractHours", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(-1.4)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-12 00:02:00", "2012-12-12 12:14:12", "2012-12-12 00:02:00", "2012-12-12 12:14:12"}), - executeFunction("subtractMinutes", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(-1.6)))); + executeFunction("subtractMinutes", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(-1.6)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-12 00:01:00", "2012-12-12 12:13:12", "2012-12-12 00:01:00", "2012-12-12 12:13:12"}), - executeFunction("subtractMinutes", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(-1.4)))); + executeFunction("subtractMinutes", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(-1.4)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-12 00:00:02", "2012-12-12 12:12:14", "2012-12-12 00:00:02", "2012-12-12 12:12:14"}), - executeFunction("subtractSeconds", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(-1.6)))); + executeFunction("subtractSeconds", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(-1.6)))); ASSERT_COLUMN_EQ( toNullableVec({"2012-12-12 00:00:01", "2012-12-12 12:12:13", "2012-12-12 00:00:01", "2012-12-12 12:12:13"}), - executeFunction("subtractSeconds", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("round", toConst(-1.4)))); + executeFunction("subtractSeconds", toNullableVec({"20121212", "20121212121212", "2012-12-12", "2012-12-12 12:12:12"}), executeFunction("tidbRound", toConst(-1.4)))); } } // namespace DB::tests diff --git a/dbms/src/Functions/tests/gtest_datetime_daymonthyear.cpp b/dbms/src/Functions/tests/gtest_datetime_daymonthyear.cpp index 99254821062..1cc7d799014 100644 --- a/dbms/src/Functions/tests/gtest_datetime_daymonthyear.cpp +++ b/dbms/src/Functions/tests/gtest_datetime_daymonthyear.cpp @@ -82,9 +82,9 @@ class TestDateTimeDayMonthYear : public DB::tests::FunctionTest .column, makeNullable(std::make_shared
    ()), "result"); - result_day = createConstColumn>(1, {day}); - result_month = createConstColumn>(1, {month}); - result_year = createConstColumn>(1, {year}); + result_day = createConstColumn(1, {day}); + result_month = createConstColumn(1, {month}); + result_year = createConstColumn(1, {year}); } else { diff --git a/dbms/src/Functions/tests/gtest_duration_pushdown.cpp b/dbms/src/Functions/tests/gtest_duration_pushdown.cpp index bd6fddaeb84..4501a4c9fae 100644 --- a/dbms/src/Functions/tests/gtest_duration_pushdown.cpp +++ b/dbms/src/Functions/tests/gtest_duration_pushdown.cpp @@ -43,8 +43,10 @@ try result_col, executeFunction( "FunctionConvertDurationFromNanos", - createColumn>({-1, 0, 1, {}, INT64_MAX, INT64_MIN, (838 * 3600 + 59 * 60 + 59) * 1000000000L, -(838 * 3600 + 59 * 60 + 59) * 1000000000L}), - createConstColumn(8, 1))); + {createColumn>({-1, 0, 1, {}, INT64_MAX, INT64_MIN, (838 * 3600 + 59 * 60 + 59) * 1000000000L, -(838 * 3600 + 59 * 60 + 59) * 1000000000L}), + createConstColumn(8, 1)}, + nullptr, + true)); ColumnWithTypeAndName result_col2( createConstColumn(3, 3).column, @@ -54,8 +56,10 @@ try result_col2, executeFunction( "FunctionConvertDurationFromNanos", - createConstColumn(3, 3), - createConstColumn(3, 2))); + {createConstColumn(3, 3), + createConstColumn(3, 2)}, + nullptr, + true)); } CATCH diff --git a/dbms/src/Functions/tests/gtest_functions_round_with_frac.cpp b/dbms/src/Functions/tests/gtest_functions_round_with_frac.cpp index 0c85285029e..403b88259a4 100644 --- a/dbms/src/Functions/tests/gtest_functions_round_with_frac.cpp +++ b/dbms/src/Functions/tests/gtest_functions_round_with_frac.cpp @@ -312,9 +312,10 @@ TEST_F(TestFunctionsRoundWithFrac, IntConstInput) // const signed - const frac for (size_t i = 0; i < size; ++i) { - ASSERT_COLUMN_EQ(createConstColumn>(1, int32_result[i]), + bool frac_data_null = !frac_data[i].has_value(); + ASSERT_COLUMN_EQ(frac_data_null ? createConstColumn>(1, int32_result[i]) : createConstColumn(1, int32_result[i].value()), execute(createConstColumn(1, int32_input), createConstColumn>(1, frac_data[i]))); - ASSERT_COLUMN_EQ(createConstColumn>(1, uint32_result[i]), + ASSERT_COLUMN_EQ(frac_data_null ? createConstColumn>(1, uint32_result[i]) : createConstColumn(1, uint32_result[i].value()), execute(createConstColumn(1, uint32_input), createConstColumn>(1, frac_data[i]))); ASSERT_COLUMN_EQ(createConstColumn>(1, {}), execute(createConstColumn>(1, {}), createConstColumn>(1, frac_data[i]))); @@ -441,22 +442,22 @@ try { auto frac = createColumn>({3, 2, 1, 0, -1, -2, -3, -4, -5, -6, {}}); - ASSERT_COLUMN_EQ(column({max_prec, 3}, - {"98765.432", "98765.430", "98765.400", "98765.000", "98770.000", "98800.000", "99000.000", "100000.000", "100000.000", "0.000", {}}), + ASSERT_COLUMN_EQ(createColumn>(std::make_tuple(9, 3), + {"98765.432", "98765.430", "98765.400", "98765.000", "98770.000", "98800.000", "99000.000", "100000.000", "100000.000", "0.000", {}}), this->execute(constColumn({max_prec - 1, 3}, 11, "98765.432"), frac)); ASSERT_COLUMN_EQ(constColumn({max_prec, 3}, 11, {}), this->execute(constColumn({max_prec - 1, 3}, 11, {}), frac)); } // const input & frac - ASSERT_COLUMN_EQ(constColumn({max_prec - 1, 2}, 1, "0.03"), + ASSERT_COLUMN_EQ(createConstColumn(std::make_tuple(3, 2), 1, "0.03"), this->execute(constColumn({max_prec - 1, 3}, 1, "0.025"), createConstColumn(1, 2))); ASSERT_COLUMN_EQ( constColumn({max_prec - 1, 2}, 1, {}), this->execute(constColumn({max_prec - 1, 3}, 1, {}), createConstColumn(1, 2))); - ASSERT_COLUMN_EQ(constColumn({max_prec - 3, 0}, 1, {}), + ASSERT_COLUMN_EQ(createConstColumn>(std::make_tuple(1, 0), 1, {}, "", 0), this->execute(constColumn({max_prec - 1, 3}, 1, "0.025"), createConstColumn>(1, {}))); - ASSERT_COLUMN_EQ(createConstColumn(std::make_tuple(max_prec, 5), 100, "1." + String(5, '0')), + ASSERT_COLUMN_EQ(createConstColumn(std::make_tuple(6, 5), 100, "1." + String(5, '0')), this->execute(createConstColumn(std::make_tuple(max_prec - 5, 0), 100, "1"), createConstColumn(100, 5))); } CATCH diff --git a/dbms/src/Functions/tests/gtest_ifnull.cpp b/dbms/src/Functions/tests/gtest_ifnull.cpp index 6d4bd17089a..bd319b59d92 100644 --- a/dbms/src/Functions/tests/gtest_ifnull.cpp +++ b/dbms/src/Functions/tests/gtest_ifnull.cpp @@ -34,19 +34,15 @@ class TestIfNull : public DB::tests::FunctionTest protected: ColumnWithTypeAndName executeIfNull(const ColumnWithTypeAndName & first_column, const ColumnWithTypeAndName & second_column) { - auto is_null_column = executeFunction("isNull", first_column); - auto not_null_column = executeFunction("assumeNotNull", first_column); - return executeFunction("multiIf", is_null_column, second_column, not_null_column); + return executeFunction("ifNull", first_column, second_column); } DataTypePtr getReturnTypeForIfNull(const DataTypePtr & type_1, const DataTypePtr & type_2) { - const static auto cond_type = std::make_shared(); ColumnsWithTypeAndName input_columns{ - {nullptr, cond_type, ""}, - {nullptr, removeNullable(type_1), ""}, + {nullptr, type_1, ""}, {nullptr, type_2, ""}, }; - return getReturnTypeForFunction(context, "multiIf", input_columns); + return getReturnTypeForFunction(context, "ifNull", input_columns); } template ColumnWithTypeAndName createIntegerColumnInternal(const std::vector & signed_input, const std::vector unsigned_input, const std::vector & null_map) @@ -150,12 +146,12 @@ try } else { - ASSERT_COLUMN_EQ(col_2.type->isNullable() ? expr_data_2_nullable_vector : expr_data_2_vector, executeIfNull(col_1, col_2)); + ASSERT_COLUMN_EQ(expr_data_2_vector, executeIfNull(col_1, col_2)); } } else { - if (col_2.type->isNullable()) + if (col_2.column->isNullAt(0)) { ASSERT_COLUMN_EQ(expr_data_1_nullable_vector, executeIfNull(col_1, col_2)); } @@ -185,14 +181,7 @@ try } else { - if (col_2.type->isNullable()) - { - ASSERT_COLUMN_EQ(createNullableColumn(vector_const_result, {0, 0, 0, 0, 0}), executeIfNull(col_1, col_2)); - } - else - { - ASSERT_COLUMN_EQ(createColumn(vector_const_result), executeIfNull(col_1, col_2)); - } + ASSERT_COLUMN_EQ(createColumn(vector_const_result), executeIfNull(col_1, col_2)); } } } diff --git a/dbms/src/Functions/tests/gtest_inet_aton_ntoa.cpp b/dbms/src/Functions/tests/gtest_inet_aton_ntoa.cpp index 756dc7e610a..cb37252d17e 100644 --- a/dbms/src/Functions/tests/gtest_inet_aton_ntoa.cpp +++ b/dbms/src/Functions/tests/gtest_inet_aton_ntoa.cpp @@ -142,7 +142,7 @@ try // const non-null column ASSERT_COLUMN_EQ( - createConstColumn>(1, "0.0.0.1"), + createConstColumn(1, "0.0.0.1"), executeFunction(func_name, createConstColumn>(1, 1))); // normal cases diff --git a/dbms/src/Functions/tests/gtest_is_true_false.cpp b/dbms/src/Functions/tests/gtest_is_true_false.cpp index 520728b4380..400166c685e 100644 --- a/dbms/src/Functions/tests/gtest_is_true_false.cpp +++ b/dbms/src/Functions/tests/gtest_is_true_false.cpp @@ -124,10 +124,10 @@ CATCH createColumn>({0, 1, 1, std::nullopt}), \ executeFunction("isTrueWithNull", createColumn>({0, 1, static_cast(-1), std::nullopt}))); \ ASSERT_COLUMN_EQ( \ - createConstColumn>(5, 0), \ + createConstColumn(5, 0), \ executeFunction("isTrueWithNull", createConstColumn>(5, 0))); \ ASSERT_COLUMN_EQ( \ - createConstColumn>(5, 1), \ + createConstColumn(5, 1), \ executeFunction("isTrueWithNull", createConstColumn>(5, 2))); \ ASSERT_COLUMN_EQ( \ createConstColumn>(5, std::nullopt), \ @@ -194,10 +194,10 @@ CATCH createColumn>({1, 0, 0, std::nullopt}), \ executeFunction("isFalseWithNull", createColumn>({0, 1, static_cast(-1), std::nullopt}))); \ ASSERT_COLUMN_EQ( \ - createConstColumn>(5, 1), \ + createConstColumn(5, 1), \ executeFunction("isFalseWithNull", createConstColumn>(5, 0))); \ ASSERT_COLUMN_EQ( \ - createConstColumn>(5, 0), \ + createConstColumn(5, 0), \ executeFunction("isFalseWithNull", createConstColumn>(5, 2))); \ ASSERT_COLUMN_EQ( \ createConstColumn>(5, std::nullopt), \ diff --git a/dbms/src/Functions/tests/gtest_least_greatest.cpp b/dbms/src/Functions/tests/gtest_least_greatest.cpp index bc57cc531a1..cbf7552fdc1 100644 --- a/dbms/src/Functions/tests/gtest_least_greatest.cpp +++ b/dbms/src/Functions/tests/gtest_least_greatest.cpp @@ -154,7 +154,7 @@ try // const-const least ASSERT_COLUMN_EQ( - createConstColumn>(1, -3), + createConstColumn(1, -3), executeFunction( func_name, createConstColumn>(1, 5), @@ -323,7 +323,7 @@ try // const-const greatest ASSERT_COLUMN_EQ( - createConstColumn>(1, 5), + createConstColumn(1, 5), executeFunction( func_name, createConstColumn>(1, 5), diff --git a/dbms/src/Functions/tests/gtest_logical.cpp b/dbms/src/Functions/tests/gtest_logical.cpp index 7988989cc88..29fd4d6e133 100644 --- a/dbms/src/Functions/tests/gtest_logical.cpp +++ b/dbms/src/Functions/tests/gtest_logical.cpp @@ -46,7 +46,7 @@ try createColumn>({1, 0}))); // const, const ASSERT_COLUMN_EQ( - createConstColumn>(1, 1), + createConstColumn(1, 1), executeFunction( func_name, createConstColumn>(1, 1), @@ -82,7 +82,7 @@ try createColumn>({1, 0}))); // const, const ASSERT_COLUMN_EQ( - createConstColumn>(1, 1), + createConstColumn(1, 1), executeFunction( func_name, createConstColumn>(1, 1), @@ -118,7 +118,7 @@ try createColumn>({1, 0}))); // const, const ASSERT_COLUMN_EQ( - createConstColumn>(1, 0), + createConstColumn(1, 0), executeFunction( func_name, createConstColumn>(1, 1), @@ -146,7 +146,7 @@ try createColumn>({0, 1, {}}))); // const ASSERT_COLUMN_EQ( - createConstColumn>(1, 0), + createConstColumn(1, 0), executeFunction( func_name, createConstColumn>(1, 1))); diff --git a/dbms/src/Functions/tests/gtest_regexp.cpp b/dbms/src/Functions/tests/gtest_regexp.cpp index 25314e9f5e7..d3eb93a0790 100644 --- a/dbms/src/Functions/tests/gtest_regexp.cpp +++ b/dbms/src/Functions/tests/gtest_regexp.cpp @@ -1828,15 +1828,15 @@ TEST_F(Regexp, testRegexp) for (size_t i = 0; i < row_size; i++) { /// test regexp(const, const) - ASSERT_COLUMN_EQ(input_string_nulls[i] || pattern_nulls[i] ? const_uint8_null_column : createConstColumn>(row_size, results[i]), + ASSERT_COLUMN_EQ(input_string_nulls[i] || pattern_nulls[i] ? const_uint8_null_column : createConstColumn(row_size, results[i]), executeFunction("regexp", input_string_nulls[i] ? const_string_null_column : createConstColumn>(row_size, input_strings[i]), pattern_nulls[i] ? const_string_null_column : createConstColumn>(row_size, patterns[i]))); /// test regexp(const, const, const) - ASSERT_COLUMN_EQ(input_string_nulls[i] || pattern_nulls[i] || match_type_nulls[i] ? const_uint8_null_column : createConstColumn>(row_size, results_with_match_type[i]), + ASSERT_COLUMN_EQ(input_string_nulls[i] || pattern_nulls[i] || match_type_nulls[i] ? const_uint8_null_column : createConstColumn(row_size, results_with_match_type[i]), executeFunction("regexp", input_string_nulls[i] ? const_string_null_column : createConstColumn>(row_size, input_strings[i]), pattern_nulls[i] ? const_string_null_column : createConstColumn>(row_size, patterns[i]), match_type_nulls[i] ? const_string_null_column : createConstColumn>(row_size, match_types[i]))); /// test regexp(const, const, const) with binary collator - ASSERT_COLUMN_EQ(input_string_nulls[i] || pattern_nulls[i] || match_type_nulls[i] ? const_uint8_null_column : createConstColumn>(row_size, results_with_match_type_collator[i]), + ASSERT_COLUMN_EQ(input_string_nulls[i] || pattern_nulls[i] || match_type_nulls[i] ? const_uint8_null_column : createConstColumn(row_size, results_with_match_type_collator[i]), executeFunction("regexp", {input_string_nulls[i] ? const_string_null_column : createConstColumn>(row_size, input_strings[i]), pattern_nulls[i] ? const_string_null_column : createConstColumn>(row_size, patterns[i]), match_type_nulls[i] ? const_string_null_column : createConstColumn>(row_size, match_types[i])}, binary_collator)); } /// case 3 regexp(vector, const[, const]) @@ -1920,20 +1920,13 @@ TEST_F(Regexp, testRegexpCustomerCases) } else if (isColumnConstNotNull(input_column) && isColumnConstNotNull(pattern_column)) { - if (input_column.type->isNullable() || pattern_column.type->isNullable()) - { - ASSERT_COLUMN_EQ(createConstColumn>(5, 1), - executeFunction("regexp", input_column, pattern_column)); - } - else - { - ASSERT_COLUMN_EQ(createConstColumn(5, 1), - executeFunction("regexp", input_column, pattern_column)); - } + ASSERT_COLUMN_EQ(createConstColumn(5, 1), + executeFunction("regexp", input_column, pattern_column)); } else { - bool result_nullable = input_column.type->isNullable() || pattern_column.type->isNullable(); + bool result_nullable = (input_column.type->isNullable() && !isColumnConstNotNull(input_column)) + || (pattern_column.type->isNullable() && !isColumnConstNotNull(pattern_column)); if (!result_nullable) { ASSERT_COLUMN_EQ(createColumn({1, 1, 1, 1, 1}), @@ -2067,28 +2060,29 @@ TEST_F(Regexp, testRegexpReplace) auto const_string_null_column = createConstColumn>(row_size, {}); auto const_int64_null_column = createConstColumn>(row_size, {}); + /// regexp_replace is not supported in TiDB yet, so use raw function test /// case 1. regexp_replace(const, const, const [, const, const ,const]) for (size_t i = 0; i < match_types.size(); i++) { /// test regexp_replace(str, pattern, replacement) ASSERT_COLUMN_EQ(createConstColumn(row_size, results[i]), - executeFunction("replaceRegexpAll", createConstColumn(row_size, input_strings[i]), createConstColumn(row_size, patterns[i]), createConstColumn(row_size, replacements[i]))); + executeFunction("replaceRegexpAll", {createConstColumn(row_size, input_strings[i]), createConstColumn(row_size, patterns[i]), createConstColumn(row_size, replacements[i])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos) ASSERT_COLUMN_EQ(createConstColumn(row_size, results_with_pos[i]), - executeFunction("replaceRegexpAll", createConstColumn(row_size, input_strings[i]), createConstColumn(row_size, patterns[i]), createConstColumn(row_size, replacements[i]), createConstColumn(row_size, pos[i]))); + executeFunction("replaceRegexpAll", {createConstColumn(row_size, input_strings[i]), createConstColumn(row_size, patterns[i]), createConstColumn(row_size, replacements[i]), createConstColumn(row_size, pos[i])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos, occ) ASSERT_COLUMN_EQ(createConstColumn(row_size, results_with_pos_occ[i]), - executeFunction("replaceRegexpAll", createConstColumn(row_size, input_strings[i]), createConstColumn(row_size, patterns[i]), createConstColumn(row_size, replacements[i]), createConstColumn(row_size, pos[i]), createConstColumn(row_size, occ[i]))); + executeFunction("replaceRegexpAll", {createConstColumn(row_size, input_strings[i]), createConstColumn(row_size, patterns[i]), createConstColumn(row_size, replacements[i]), createConstColumn(row_size, pos[i]), createConstColumn(row_size, occ[i])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos, occ, match_type) ASSERT_COLUMN_EQ(createConstColumn(row_size, results_with_pos_occ_match_type[i]), - executeFunction("replaceRegexpAll", createConstColumn(row_size, input_strings[i]), createConstColumn(row_size, patterns[i]), createConstColumn(row_size, replacements[i]), createConstColumn(row_size, pos[i]), createConstColumn(row_size, occ[i]), createConstColumn(row_size, match_types[i]))); + executeFunction("replaceRegexpAll", {createConstColumn(row_size, input_strings[i]), createConstColumn(row_size, patterns[i]), createConstColumn(row_size, replacements[i]), createConstColumn(row_size, pos[i]), createConstColumn(row_size, occ[i]), createConstColumn(row_size, match_types[i])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos, occ, match_type) with binary collator ASSERT_COLUMN_EQ(createConstColumn(row_size, results_with_pos_occ_match_type_binary[i]), - executeFunction("replaceRegexpAll", {createConstColumn(row_size, input_strings[i]), createConstColumn(row_size, patterns[i]), createConstColumn(row_size, replacements[i]), createConstColumn(row_size, pos[i]), createConstColumn(row_size, occ[i]), createConstColumn(row_size, match_types[i])}, binary_collator)); + executeFunction("replaceRegexpAll", {createConstColumn(row_size, input_strings[i]), createConstColumn(row_size, patterns[i]), createConstColumn(row_size, replacements[i]), createConstColumn(row_size, pos[i]), createConstColumn(row_size, occ[i]), createConstColumn(row_size, match_types[i])}, binary_collator, true)); } /// case 2. regexp_replace(const, const, const [, const, const ,const]) with null value @@ -2097,74 +2091,74 @@ TEST_F(Regexp, testRegexpReplace) /// test regexp_replace(str, pattern, replacement) bool null_result = input_string_nulls[i] || pattern_nulls[i] || replacement_nulls[i]; ASSERT_COLUMN_EQ(null_result ? const_string_null_column : createConstColumn>(row_size, results[i]), - executeFunction("replaceRegexpAll", input_string_nulls[i] ? const_string_null_column : createConstColumn>(row_size, input_strings[i]), pattern_nulls[i] ? const_string_null_column : createConstColumn>(row_size, patterns[i]), replacement_nulls[i] ? const_string_null_column : createConstColumn>(row_size, replacements[i]))); + executeFunction("replaceRegexpAll", {input_string_nulls[i] ? const_string_null_column : createConstColumn>(row_size, input_strings[i]), pattern_nulls[i] ? const_string_null_column : createConstColumn>(row_size, patterns[i]), replacement_nulls[i] ? const_string_null_column : createConstColumn>(row_size, replacements[i])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos) null_result = null_result || pos_nulls[i]; ASSERT_COLUMN_EQ(null_result ? const_string_null_column : createConstColumn>(row_size, results_with_pos[i]), - executeFunction("replaceRegexpAll", input_string_nulls[i] ? const_string_null_column : createConstColumn>(row_size, input_strings[i]), pattern_nulls[i] ? const_string_null_column : createConstColumn>(row_size, patterns[i]), replacement_nulls[i] ? const_string_null_column : createConstColumn>(row_size, replacements[i]), pos_nulls[i] ? const_int64_null_column : createConstColumn>(row_size, pos[i]))); + executeFunction("replaceRegexpAll", {input_string_nulls[i] ? const_string_null_column : createConstColumn>(row_size, input_strings[i]), pattern_nulls[i] ? const_string_null_column : createConstColumn>(row_size, patterns[i]), replacement_nulls[i] ? const_string_null_column : createConstColumn>(row_size, replacements[i]), pos_nulls[i] ? const_int64_null_column : createConstColumn>(row_size, pos[i])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos, occ) null_result = null_result || occ_nulls[i]; ASSERT_COLUMN_EQ(null_result ? const_string_null_column : createConstColumn>(row_size, results_with_pos_occ[i]), - executeFunction("replaceRegexpAll", input_string_nulls[i] ? const_string_null_column : createConstColumn>(row_size, input_strings[i]), pattern_nulls[i] ? const_string_null_column : createConstColumn>(row_size, patterns[i]), replacement_nulls[i] ? const_string_null_column : createConstColumn>(row_size, replacements[i]), pos_nulls[i] ? const_int64_null_column : createConstColumn>(row_size, pos[i]), occ_nulls[i] ? const_int64_null_column : createConstColumn>(row_size, occ[i]))); + executeFunction("replaceRegexpAll", {input_string_nulls[i] ? const_string_null_column : createConstColumn>(row_size, input_strings[i]), pattern_nulls[i] ? const_string_null_column : createConstColumn>(row_size, patterns[i]), replacement_nulls[i] ? const_string_null_column : createConstColumn>(row_size, replacements[i]), pos_nulls[i] ? const_int64_null_column : createConstColumn>(row_size, pos[i]), occ_nulls[i] ? const_int64_null_column : createConstColumn>(row_size, occ[i])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos, occ, match_type) null_result = null_result || match_type_nulls[i]; ASSERT_COLUMN_EQ(null_result ? const_string_null_column : createConstColumn>(row_size, results_with_pos_occ_match_type[i]), - executeFunction("replaceRegexpAll", input_string_nulls[i] ? const_string_null_column : createConstColumn>(row_size, input_strings[i]), pattern_nulls[i] ? const_string_null_column : createConstColumn>(row_size, patterns[i]), replacement_nulls[i] ? const_string_null_column : createConstColumn>(row_size, replacements[i]), pos_nulls[i] ? const_int64_null_column : createConstColumn>(row_size, pos[i]), occ_nulls[i] ? const_int64_null_column : createConstColumn>(row_size, occ[i]), match_type_nulls[i] ? const_string_null_column : createConstColumn>(row_size, match_types[i]))); + executeFunction("replaceRegexpAll", {input_string_nulls[i] ? const_string_null_column : createConstColumn>(row_size, input_strings[i]), pattern_nulls[i] ? const_string_null_column : createConstColumn>(row_size, patterns[i]), replacement_nulls[i] ? const_string_null_column : createConstColumn>(row_size, replacements[i]), pos_nulls[i] ? const_int64_null_column : createConstColumn>(row_size, pos[i]), occ_nulls[i] ? const_int64_null_column : createConstColumn>(row_size, occ[i]), match_type_nulls[i] ? const_string_null_column : createConstColumn>(row_size, match_types[i])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos, occ, match_type) with binary collator ASSERT_COLUMN_EQ(null_result ? const_string_null_column : createConstColumn>(row_size, results_with_pos_occ_match_type_binary[i]), - executeFunction("replaceRegexpAll", {input_string_nulls[i] ? const_string_null_column : createConstColumn>(row_size, input_strings[i]), pattern_nulls[i] ? const_string_null_column : createConstColumn>(row_size, patterns[i]), replacement_nulls[i] ? const_string_null_column : createConstColumn>(row_size, replacements[i]), pos_nulls[i] ? const_int64_null_column : createConstColumn>(row_size, pos[i]), occ_nulls[i] ? const_int64_null_column : createConstColumn>(row_size, occ[i]), match_type_nulls[i] ? const_string_null_column : createConstColumn>(row_size, match_types[i])}, binary_collator)); + executeFunction("replaceRegexpAll", {input_string_nulls[i] ? const_string_null_column : createConstColumn>(row_size, input_strings[i]), pattern_nulls[i] ? const_string_null_column : createConstColumn>(row_size, patterns[i]), replacement_nulls[i] ? const_string_null_column : createConstColumn>(row_size, replacements[i]), pos_nulls[i] ? const_int64_null_column : createConstColumn>(row_size, pos[i]), occ_nulls[i] ? const_int64_null_column : createConstColumn>(row_size, occ[i]), match_type_nulls[i] ? const_string_null_column : createConstColumn>(row_size, match_types[i])}, binary_collator, true)); } /// case 3 regexp_replace(vector, const, const[, const, const, const]) { /// test regexp_replace(str, pattern, replacement) ASSERT_COLUMN_EQ(createColumn(vec_results), - executeFunction("replaceRegexpAll", createColumn(input_strings), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]))); + executeFunction("replaceRegexpAll", {createColumn(input_strings), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos) ASSERT_COLUMN_EQ(createColumn(vec_results_with_pos), - executeFunction("replaceRegexpAll", createColumn(input_strings), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0]))); + executeFunction("replaceRegexpAll", {createColumn(input_strings), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos, occ) ASSERT_COLUMN_EQ(createColumn(vec_results_with_pos_occ), - executeFunction("replaceRegexpAll", createColumn(input_strings), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0]), createConstColumn(row_size, occ[0]))); + executeFunction("replaceRegexpAll", {createColumn(input_strings), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0]), createConstColumn(row_size, occ[0])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos, occ, match_type) ASSERT_COLUMN_EQ(createColumn(vec_results_with_pos_occ_match_type), - executeFunction("replaceRegexpAll", createColumn(input_strings), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0]), createConstColumn(row_size, occ[0]), createConstColumn(row_size, match_types[0]))); + executeFunction("replaceRegexpAll", {createColumn(input_strings), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0]), createConstColumn(row_size, occ[0]), createConstColumn(row_size, match_types[0])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos, occ, match_type) with binary collator ASSERT_COLUMN_EQ(createColumn(vec_results_with_pos_occ_match_type_binary), - executeFunction("replaceRegexpAll", {createColumn(input_strings), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0]), createConstColumn(row_size, occ[0]), createConstColumn(row_size, match_types[0])}, binary_collator)); + executeFunction("replaceRegexpAll", {createColumn(input_strings), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0]), createConstColumn(row_size, occ[0]), createConstColumn(row_size, match_types[0])}, binary_collator, true)); } /// case 4 regexp_replace(vector, const, const[, const, const, const]) with null value { /// test regexp_replace(str, pattern, replacement) ASSERT_COLUMN_EQ(createNullableVectorColumn(vec_results, input_string_nulls), - executeFunction("replaceRegexpAll", createNullableVectorColumn(input_strings, input_string_nulls), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]))); + executeFunction("replaceRegexpAll", {createNullableVectorColumn(input_strings, input_string_nulls), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos) ASSERT_COLUMN_EQ(createNullableVectorColumn(vec_results_with_pos, input_string_nulls), - executeFunction("replaceRegexpAll", createNullableVectorColumn(input_strings, input_string_nulls), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0]))); + executeFunction("replaceRegexpAll", {createNullableVectorColumn(input_strings, input_string_nulls), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos, occ) ASSERT_COLUMN_EQ(createNullableVectorColumn(vec_results_with_pos_occ, input_string_nulls), - executeFunction("replaceRegexpAll", createNullableVectorColumn(input_strings, input_string_nulls), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0]), createConstColumn(row_size, occ[0]))); + executeFunction("replaceRegexpAll", {createNullableVectorColumn(input_strings, input_string_nulls), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0]), createConstColumn(row_size, occ[0])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos, occ, match_type) ASSERT_COLUMN_EQ(createNullableVectorColumn(vec_results_with_pos_occ_match_type, input_string_nulls), - executeFunction("replaceRegexpAll", createNullableVectorColumn(input_strings, input_string_nulls), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0]), createConstColumn(row_size, occ[0]), createConstColumn(row_size, match_types[0]))); + executeFunction("replaceRegexpAll", {createNullableVectorColumn(input_strings, input_string_nulls), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0]), createConstColumn(row_size, occ[0]), createConstColumn(row_size, match_types[0])}, nullptr, true)); /// test regexp_replace(str, pattern, replacement, pos, occ, match_type) with binary collator ASSERT_COLUMN_EQ(createNullableVectorColumn(vec_results_with_pos_occ_match_type_binary, input_string_nulls), - executeFunction("replaceRegexpAll", {createNullableVectorColumn(input_strings, input_string_nulls), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0]), createConstColumn(row_size, occ[0]), createConstColumn(row_size, match_types[0])}, binary_collator)); + executeFunction("replaceRegexpAll", {createNullableVectorColumn(input_strings, input_string_nulls), createConstColumn(row_size, patterns[0]), createConstColumn(row_size, replacements[0]), createConstColumn(row_size, pos[0]), createConstColumn(row_size, occ[0]), createConstColumn(row_size, match_types[0])}, binary_collator, true)); } } } // namespace tests diff --git a/dbms/src/Functions/tests/gtest_string_left.cpp b/dbms/src/Functions/tests/gtest_string_left.cpp index ca491bfe6fa..42a16ee6fff 100644 --- a/dbms/src/Functions/tests/gtest_string_left.cpp +++ b/dbms/src/Functions/tests/gtest_string_left.cpp @@ -27,8 +27,7 @@ namespace tests class StringLeftTest : public DB::tests::FunctionTest { public: - // leftUTF8(str,len) = substrUTF8(str,const 1,len) - static constexpr auto func_name = "substringUTF8"; + static constexpr auto func_name = "leftUTF8"; template void testBoundary() @@ -53,14 +52,14 @@ class StringLeftTest : public DB::tests::FunctionTest template void test(const std::optional & str, const std::optional & length, const std::optional & result) { - const auto start_column = createConstColumn(1, 1); auto inner_test = [&](bool is_str_const, bool is_length_const) { bool is_one_of_args_null_const = (is_str_const && !str.has_value()) || (is_length_const && !length.has_value()); bool is_result_const = (is_str_const && is_length_const) || is_one_of_args_null_const; - auto expected_res_column = is_result_const ? createConstColumn>(1, result) : createColumn>({result}); + auto expected_res_column = is_result_const ? (is_one_of_args_null_const ? createConstColumn>(1, result) : createConstColumn(1, result.value())) + : createColumn>({result}); auto str_column = is_str_const ? createConstColumn>(1, str) : createColumn>({str}); auto length_column = is_length_const ? createConstColumn>(1, length) : createColumn>({length}); - auto actual_res_column = executeFunction(func_name, str_column, start_column, length_column); + auto actual_res_column = executeFunction(func_name, str_column, length_column); ASSERT_COLUMN_EQ(expected_res_column, actual_res_column); }; std::vector is_consts = {true, false}; @@ -78,7 +77,6 @@ class StringLeftTest : public DB::tests::FunctionTest executeFunction( func_name, is_str_const ? createConstColumn>(1, "") : createColumn>({""}), - createConstColumn(1, 1), is_length_const ? createConstColumn>(1, 0) : createColumn>({0})), Exception); }; @@ -132,7 +130,6 @@ try executeFunction( func_name, createColumn>({big_string, origin_str, origin_str, mixed_language_str}), - createConstColumn(8, 1), createColumn>({22, 12, 22, english_str.size()}))); // case 2 String second_case_string = "abc"; @@ -141,14 +138,12 @@ try executeFunction( func_name, createColumn>({second_case_string, second_case_string, second_case_string, second_case_string, second_case_string, second_case_string, second_case_string, second_case_string}), - createConstColumn(8, 1), createColumn>({0, 1, 0, 1, 0, 0, 1, 1}))); ASSERT_COLUMN_EQ( createColumn>({"", "a", "", "a", "", "", "a", "a"}), executeFunction( func_name, createConstColumn>(8, second_case_string), - createConstColumn(8, 1), createColumn>({0, 1, 0, 1, 0, 0, 1, 1}))); } CATCH diff --git a/dbms/src/Functions/tests/gtest_string_lrtrim.cpp b/dbms/src/Functions/tests/gtest_string_lrtrim.cpp index 409c8ed715b..df52257613d 100644 --- a/dbms/src/Functions/tests/gtest_string_lrtrim.cpp +++ b/dbms/src/Functions/tests/gtest_string_lrtrim.cpp @@ -39,16 +39,16 @@ try { // ltrim(const) ASSERT_COLUMN_EQ( - createConstColumn>(5, "x "), + createConstColumn(5, "x "), executeFunction("tidbLTrim", createConstColumn>(5, " x "))); ASSERT_COLUMN_EQ( - createConstColumn>(5, "测试 "), + createConstColumn(5, "测试 "), executeFunction("tidbLTrim", createConstColumn>(5, " 测试 "))); ASSERT_COLUMN_EQ( - createConstColumn>(5, "x x x"), + createConstColumn(5, "x x x"), executeFunction("tidbLTrim", createConstColumn>(5, "x x x"))); ASSERT_COLUMN_EQ( - createConstColumn>(5, "测 试 "), + createConstColumn(5, "测 试 "), executeFunction("tidbLTrim", createConstColumn>(5, "测 试 "))); ASSERT_COLUMN_EQ( createConstColumn(5, "x "), @@ -83,16 +83,16 @@ try // rtrim(const) ASSERT_COLUMN_EQ( - createConstColumn>(5, " x"), + createConstColumn(5, " x"), executeFunction("tidbRTrim", createConstColumn>(5, " x "))); ASSERT_COLUMN_EQ( - createConstColumn>(5, " 测试"), + createConstColumn(5, " 测试"), executeFunction("tidbRTrim", createConstColumn>(5, " 测试 "))); ASSERT_COLUMN_EQ( - createConstColumn>(5, "x x x"), + createConstColumn(5, "x x x"), executeFunction("tidbRTrim", createConstColumn>(5, "x x x"))); ASSERT_COLUMN_EQ( - createConstColumn>(5, "测 试"), + createConstColumn(5, "测 试"), executeFunction("tidbRTrim", createConstColumn>(5, "测 试 "))); ASSERT_COLUMN_EQ( createConstColumn(5, " x"), @@ -225,10 +225,10 @@ try input_iter++, lres_iter++, rres_iter++) { ASSERT_COLUMN_EQ( - createConstColumn>(5, *lres_iter), + createConstColumn(5, *lres_iter), executeFunction("tidbLTrim", createConstColumn>(5, *input_iter))); ASSERT_COLUMN_EQ( - createConstColumn>(5, *rres_iter), + createConstColumn(5, *rres_iter), executeFunction("tidbRTrim", createConstColumn>(5, *input_iter))); ASSERT_COLUMN_EQ( createConstColumn(5, *lres_iter), diff --git a/dbms/src/Functions/tests/gtest_strings_cmp.cpp b/dbms/src/Functions/tests/gtest_strings_cmp.cpp index ca9ae6562c7..7e94a7ed30e 100644 --- a/dbms/src/Functions/tests/gtest_strings_cmp.cpp +++ b/dbms/src/Functions/tests/gtest_strings_cmp.cpp @@ -43,7 +43,7 @@ try ASSERT_COLUMN_EQ(createColumn>({1, 0, -1, std::nullopt}), executeFunction("strcmp", {createConstColumn>(4, "b"), createColumn>({"a", "b", "c", std::nullopt})})); // constant with constant - ASSERT_COLUMN_EQ(createConstColumn>(1, -1), executeFunction("strcmp", {createConstColumn>(1, "a"), createConstColumn>(1, "b")})); + ASSERT_COLUMN_EQ(createConstColumn(1, -1), executeFunction("strcmp", {createConstColumn>(1, "a"), createConstColumn>(1, "b")})); // constant with nullable ASSERT_COLUMN_EQ(createColumn>({-1}), executeFunction("strcmp", {createColumn({"a"}), createColumn>({"b"})})); @@ -65,10 +65,10 @@ try ASSERT_COLUMN_EQ(createColumn>({-1, 1, 0, std::nullopt, std::nullopt}), executeFunction("strcmp", {createColumn>({"", "123", "", "", std::nullopt}), createColumn>({"123", "", "", std::nullopt, ""})}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); // column with constant - ASSERT_COLUMN_EQ(createColumn>({-1}), executeFunction("strcmp", {createColumn({"a"}), createConstColumn>(1, "b")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); - ASSERT_COLUMN_EQ(createColumn>({-1}), executeFunction("strcmp", {createColumn({"A"}), createConstColumn>(1, "b")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); - ASSERT_COLUMN_EQ(createColumn>({-1}), executeFunction("strcmp", {createColumn({"A"}), createConstColumn>(1, "B")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); - ASSERT_COLUMN_EQ(createColumn>({-1, 0, 1, 1}), executeFunction("strcmp", {createColumn({"A", "B", "C", "D"}), createConstColumn>(4, "B")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); + ASSERT_COLUMN_EQ(createColumn({-1}), executeFunction("strcmp", {createColumn({"a"}), createConstColumn>(1, "b")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); + ASSERT_COLUMN_EQ(createColumn({-1}), executeFunction("strcmp", {createColumn({"A"}), createConstColumn>(1, "b")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); + ASSERT_COLUMN_EQ(createColumn({-1}), executeFunction("strcmp", {createColumn({"A"}), createConstColumn>(1, "B")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); + ASSERT_COLUMN_EQ(createColumn({-1, 0, 1, 1}), executeFunction("strcmp", {createColumn({"A", "B", "C", "D"}), createConstColumn>(4, "B")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); // constant with column ASSERT_COLUMN_EQ(createColumn>({1, 0, -1, std::nullopt}), executeFunction("strcmp", {createConstColumn>(4, "b"), createColumn>({"a", "b", "c", std::nullopt})}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); @@ -76,10 +76,10 @@ try ASSERT_COLUMN_EQ(createColumn>({1, 0, -1, std::nullopt}), executeFunction("strcmp", {createConstColumn>(4, "b"), createColumn>({"A", "B", "C", std::nullopt})}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); // constant with constant - ASSERT_COLUMN_EQ(createConstColumn>(1, -1), executeFunction("strcmp", {createConstColumn>(1, "a"), createConstColumn>(1, "b")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); - ASSERT_COLUMN_EQ(createConstColumn>(1, -1), executeFunction("strcmp", {createConstColumn>(1, "A"), createConstColumn>(1, "b")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); - ASSERT_COLUMN_EQ(createConstColumn>(1, -1), executeFunction("strcmp", {createConstColumn>(1, "a"), createConstColumn>(1, "B")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); - ASSERT_COLUMN_EQ(createConstColumn>(1, -1), executeFunction("strcmp", {createConstColumn>(1, "A"), createConstColumn>(1, "B")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); + ASSERT_COLUMN_EQ(createConstColumn(1, -1), executeFunction("strcmp", {createConstColumn>(1, "a"), createConstColumn>(1, "b")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); + ASSERT_COLUMN_EQ(createConstColumn(1, -1), executeFunction("strcmp", {createConstColumn>(1, "A"), createConstColumn>(1, "b")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); + ASSERT_COLUMN_EQ(createConstColumn(1, -1), executeFunction("strcmp", {createConstColumn>(1, "a"), createConstColumn>(1, "B")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); + ASSERT_COLUMN_EQ(createConstColumn(1, -1), executeFunction("strcmp", {createConstColumn>(1, "A"), createConstColumn>(1, "B")}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); // constant with nullable ASSERT_COLUMN_EQ(createColumn>({-1}), executeFunction("strcmp", {createColumn({"a"}), createColumn>({"b"})}, TiDB::ITiDBCollator::getCollator(TiDB::ITiDBCollator::UTF8MB4_GENERAL_CI))); diff --git a/dbms/src/Functions/tests/gtest_strings_format.cpp b/dbms/src/Functions/tests/gtest_strings_format.cpp index 2adc17afb93..2d571a9bb1b 100644 --- a/dbms/src/Functions/tests/gtest_strings_format.cpp +++ b/dbms/src/Functions/tests/gtest_strings_format.cpp @@ -62,6 +62,8 @@ class StringFormat : public DB::tests::FunctionTest FieldType(static_cast(-9999999), 4), FieldType(static_cast(-3333330), 4)}), createConstColumn>(4, 3))); + /// known issue https://github.com/pingcap/tiflash/issues/4891 + /* ASSERT_COLUMN_EQ( createColumn>({"-999.9999", "-1,000", "-1,000", "-999.999900000000000000000000000000", "-999.99990", "-1,000.0", "-1,000.00"}), executeFunction( @@ -80,6 +82,7 @@ class StringFormat : public DB::tests::FunctionTest 1, FieldType(static_cast(-9999999), 4)), createConstColumn>(1, 3))); + */ ASSERT_COLUMN_EQ( createColumn>({"12,332.1000", "12,332", "12,332.300000000000000000000000000000", "-12,332.30000", "-1,000.0", "-333.33", {}}), executeFunction( @@ -105,6 +108,8 @@ class StringFormat : public DB::tests::FunctionTest FieldType(static_cast(-9999999), 4), FieldType(static_cast(-3333330), 4)}), createConstColumn>(4, 3))); + /// known issue https://github.com/pingcap/tiflash/issues/4891 + /* ASSERT_COLUMN_EQ( createColumn>({"-999.9999", "-1,000", "-999.999900000000000000000000000000", "-999.99990", "-1,000.0", "-1,000.00"}), executeFunction( @@ -123,6 +128,7 @@ class StringFormat : public DB::tests::FunctionTest 1, FieldType(static_cast(-9999999), 4)), createConstColumn>(1, 3))); + */ } template @@ -226,7 +232,7 @@ try createColumn>({4, 0, -1, 31, 5, 1, 2}))); /// const, const ASSERT_COLUMN_EQ( - createConstColumn>(1, "-1,000.000"), + createConstColumn(1, "-1,000.000"), executeFunction( func_name, createConstColumn>(1, -999.9999), @@ -256,7 +262,7 @@ try createColumn>({4, 0, 31, 5, 1, 2}))); /// const, const ASSERT_COLUMN_EQ( - createConstColumn>(1, "-1,000.000"), + createConstColumn(1, "-1,000.000"), executeFunction( func_name, createConstColumn>(1, -999.9999), @@ -265,7 +271,7 @@ try /// float32, int /// const, const ASSERT_COLUMN_EQ( - createConstColumn>(1, "12.123"), + createConstColumn(1, "12.123"), executeFunction( func_name, createConstColumn>(1, 12.1235), diff --git a/dbms/src/Functions/tests/gtest_strings_right.cpp b/dbms/src/Functions/tests/gtest_strings_right.cpp index 1dbe8c7d5e3..210cb651ec6 100644 --- a/dbms/src/Functions/tests/gtest_strings_right.cpp +++ b/dbms/src/Functions/tests/gtest_strings_right.cpp @@ -54,7 +54,8 @@ class StringRightTest : public DB::tests::FunctionTest auto inner_test = [&](bool is_str_const, bool is_length_const) { bool is_one_of_args_null_const = (is_str_const && !str.has_value()) || (is_length_const && !length.has_value()); bool is_result_const = (is_str_const && is_length_const) || is_one_of_args_null_const; - auto expected_res_column = is_result_const ? createConstColumn>(1, result) : createColumn>({result}); + auto expected_res_column = is_result_const ? (is_one_of_args_null_const ? createConstColumn>(1, result) : createConstColumn(1, result.value())) + : createColumn>({result}); auto str_column = is_str_const ? createConstColumn>(1, str) : createColumn>({str}); auto length_column = is_length_const ? createConstColumn>(1, length) : createColumn>({length}); auto actual_res_column = executeFunction(func_name, str_column, length_column); diff --git a/dbms/src/Functions/tests/gtest_strings_search.cpp b/dbms/src/Functions/tests/gtest_strings_search.cpp index 58bf1b34487..544ebc34df6 100644 --- a/dbms/src/Functions/tests/gtest_strings_search.cpp +++ b/dbms/src/Functions/tests/gtest_strings_search.cpp @@ -24,6 +24,7 @@ namespace tests class StringMatch : public FunctionTest { protected: + const String func_name = "like3Args"; const String long_str = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab" "cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef" "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl" @@ -33,6 +34,8 @@ class StringMatch : public FunctionTest const String long_pattern = "abcdefghijklmnopqrstuvwxyz_bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz%abcdefghijklmnopqrstuvwxyz"; + ColumnWithTypeAndName escape = createConstColumn(1, static_cast('\\')); + static ColumnWithTypeAndName toNullableVec(const std::vector> & v) { return createColumn>(v); @@ -122,10 +125,9 @@ try auto haystack = createColumn(haystack_raw, "haystack"); auto needle = createColumn(needle_raw, "needle"); - auto escape = createConstColumn(1, static_cast('\\')); auto expected = createColumn(result_raw, "result"); - auto result = executeFunction("like3Args", {haystack, needle, escape}); + auto result = executeFunction(func_name, {haystack, needle, escape}); ASSERT_COLUMN_EQ(expected, result); @@ -155,10 +157,9 @@ try auto nullable_haystack = createColumn>(nullable_haystack_raw, "haystack"); auto nullable_needle = createColumn>(nullable_needle_raw, "needle"); - auto nullable_escape = createConstColumn>(1, static_cast('\\')); auto nullable_expected = createColumn>(nullable_result_raw, "result"); - auto nullable_result = executeFunction("like3Args", {nullable_haystack, nullable_needle, nullable_escape}); + auto nullable_result = executeFunction(func_name, {nullable_haystack, nullable_needle, escape}); ASSERT_COLUMN_EQ(nullable_expected, nullable_result); } @@ -196,10 +197,9 @@ try auto haystack = createConstColumn>(1, cas.src); auto needle = createColumn>(needle_raw); - auto escape = createConstColumn>(1, static_cast('\\')); auto expected = createColumn>(result_raw); - auto result = executeFunction("like3Args", {haystack, needle, escape}); + auto result = executeFunction(func_name, {haystack, needle, escape}); ASSERT_COLUMN_EQ(expected, result); } } @@ -213,16 +213,18 @@ TEST_F(StringMatch, LikeVectorWithVector) ASSERT_COLUMN_EQ( toNullableVec(expect), executeFunction( - "like", + func_name, toNullableVec(haystack), - toNullableVec(needle))); + toNullableVec(needle), + escape)); ASSERT_COLUMN_EQ( toVec(expect), executeFunction( - "like", + func_name, toVec(haystack), - toVec(needle))); + toVec(needle), + escape)); std::vector> haystack_null = {{}, "a"}; std::vector> needle_null = {"我_tif%", {}}; @@ -230,9 +232,10 @@ TEST_F(StringMatch, LikeVectorWithVector) ASSERT_COLUMN_EQ( toNullableVec(expect_null), executeFunction( - "like", + func_name, toNullableVec(haystack_null), - toNullableVec(needle_null))); + toNullableVec(needle_null), + escape)); } TEST_F(StringMatch, LikeConstWithVector) @@ -243,32 +246,36 @@ TEST_F(StringMatch, LikeConstWithVector) ASSERT_COLUMN_EQ( toNullableVec(expect), executeFunction( - "like", + func_name, toConst("abcaba"), - toNullableVec(needle))); + toNullableVec(needle), + escape)); ASSERT_COLUMN_EQ( toVec(expect), executeFunction( - "like", + func_name, toConst("abcaba"), - toVec(needle))); + toVec(needle), + escape)); ASSERT_COLUMN_EQ( toVec(expect1), executeFunction( - "like", + func_name, toConst(long_str), - toVec(needle))); + toVec(needle), + escape)); std::vector> needle_null = {{}}; std::vector> expect_null = {{}}; ASSERT_COLUMN_EQ( toNullableVec(expect_null), executeFunction( - "like", + func_name, toConst("abc"), - toNullableVec(needle_null))); + toNullableVec(needle_null), + escape)); } TEST_F(StringMatch, LikeVectorWithConst) @@ -281,46 +288,52 @@ TEST_F(StringMatch, LikeVectorWithConst) ASSERT_COLUMN_EQ( toNullableVec(expect), executeFunction( - "like", + func_name, toNullableVec(haystack), - toConst("%aa%"))); + toConst("%aa%"), + escape)); ASSERT_COLUMN_EQ( toVec(expect), executeFunction( - "like", + func_name, toVec(haystack), - toConst("%aa%"))); + toConst("%aa%"), + escape)); ASSERT_COLUMN_EQ( toVec(expect1), executeFunction( - "like", + func_name, toVec(haystack), - toConst("%爱tif%"))); + toConst("%爱tif%"), + escape)); ASSERT_COLUMN_EQ( toVec(expect2), executeFunction( - "like", + func_name, toVec(haystack), - toConst("%不爱tif%"))); + toConst("%不爱tif%"), + escape)); ASSERT_COLUMN_EQ( toVec(expect3), executeFunction( - "like", + func_name, toVec(haystack), - toConst(long_pattern))); + toConst(long_pattern), + escape)); std::vector> haystack_null = {{}}; std::vector> expect_null = {{}}; ASSERT_COLUMN_EQ( toNullableVec(expect_null), executeFunction( - "like", + func_name, toNullableVec(haystack_null), - toConst("abc"))); + toConst("abc"), + escape)); } TEST_F(StringMatch, LikeConstWithConst) @@ -328,30 +341,34 @@ TEST_F(StringMatch, LikeConstWithConst) ASSERT_COLUMN_EQ( toConst(1), executeFunction( - "like", + func_name, toConst("resaasfe"), - toConst("%aa%"))); + toConst("%aa%"), + escape)); ASSERT_COLUMN_EQ( toConst(0), executeFunction( - "like", + func_name, toConst("abcde"), - toConst("%aa%"))); + toConst("%aa%"), + escape)); ASSERT_COLUMN_EQ( toConst(1), executeFunction( - "like", + func_name, toConst("我爱tiflash"), - toConst("%爱tif%"))); + toConst("%爱tif%"), + escape)); ASSERT_COLUMN_EQ( toConst(0), executeFunction( - "like", + func_name, toConst("我爱tiflash"), - toConst("%不爱tif%"))); + toConst("%不爱tif%"), + escape)); } } // namespace tests diff --git a/dbms/src/Functions/tests/gtest_strings_tidb_concat.cpp b/dbms/src/Functions/tests/gtest_strings_tidb_concat.cpp index a0c06a5d6a8..24295bc079b 100644 --- a/dbms/src/Functions/tests/gtest_strings_tidb_concat.cpp +++ b/dbms/src/Functions/tests/gtest_strings_tidb_concat.cpp @@ -28,6 +28,7 @@ class StringTidbConcat : public DB::tests::FunctionTest static constexpr auto func_name = "tidbConcat"; using Type = Nullable; + using NotNullType = String; InferredDataVector test_strings = {"", "www.pingcap", "中文.测.试。。。", {}}; }; @@ -45,7 +46,7 @@ try createColumn({value}))); // const ASSERT_COLUMN_EQ( - createConstColumn(1, value), + value.has_value() ? createConstColumn(1, value.value()) : createConstColumn(1, value), executeFunction( StringTidbConcat::func_name, createConstColumn(1, value))); @@ -64,7 +65,7 @@ try // all args is const or has only null const auto is_result_const = (is_value1_const && is_value2_const) || (!value1.has_value() && is_value1_const) || (!value2.has_value() && is_value2_const); ASSERT_COLUMN_EQ( - is_result_const ? createConstColumn(1, result) : createColumn({result}), + is_result_const ? (is_result_not_null ? createConstColumn(1, result.value()) : createConstColumn(1, result)) : createColumn({result}), executeFunction( StringTidbConcat::func_name, is_value1_const ? createConstColumn(1, value1) : createColumn({value1}), diff --git a/dbms/src/Functions/tests/gtest_strings_trim.cpp b/dbms/src/Functions/tests/gtest_strings_trim.cpp index be2616c4818..55c4063abb5 100644 --- a/dbms/src/Functions/tests/gtest_strings_trim.cpp +++ b/dbms/src/Functions/tests/gtest_strings_trim.cpp @@ -600,37 +600,37 @@ try { // trim(const) ASSERT_COLUMN_EQ( - createConstColumn>(5, "x"), + createConstColumn(5, "x"), executeFunction("tidbTrim", createConstColumn>(5, " x "))); // trim(const from const) ASSERT_COLUMN_EQ( - createConstColumn>(5, "a"), + createConstColumn(5, "a"), executeFunction("tidbTrim", createConstColumn>(5, "xax"), createConstColumn>(5, "x"))); // trim(leading|trailing|both const from const) ASSERT_COLUMN_EQ( - createConstColumn>(5, "a"), + createConstColumn(5, "a"), executeFunction("tidbTrim", createConstColumn>(5, "xax"), createConstColumn>(5, "x"), createConstColumn>(5, 0))); ASSERT_COLUMN_EQ( - createConstColumn>(5, "a"), + createConstColumn(5, "a"), executeFunction("tidbTrim", createConstColumn>(5, "xax"), createConstColumn>(5, "x"), createConstColumn>(5, 1))); ASSERT_COLUMN_EQ( - createConstColumn>(5, "ax"), + createConstColumn(5, "ax"), executeFunction("tidbTrim", createConstColumn>(5, "xax"), createConstColumn>(5, "x"), createConstColumn>(5, 2))); ASSERT_COLUMN_EQ( - createConstColumn>(5, "xa"), + createConstColumn(5, "xa"), executeFunction("tidbTrim", createConstColumn>(5, "xax"), createConstColumn>(5, "x"), @@ -955,7 +955,7 @@ try res_itr++, input_itr++) { ASSERT_COLUMN_EQ( - createConstColumn>(5, *res_itr), + input_itr->has_value() ? createConstColumn(5, res_itr->value()) : createConstColumn>(5, *res_itr), executeFunction("tidbTrim", createConstColumn>(5, *input_itr), createConstColumn>(5, "啊啊"), diff --git a/dbms/src/Functions/tests/gtest_substring.cpp b/dbms/src/Functions/tests/gtest_substring.cpp index 1506b3340a6..374e38970f1 100644 --- a/dbms/src/Functions/tests/gtest_substring.cpp +++ b/dbms/src/Functions/tests/gtest_substring.cpp @@ -41,7 +41,7 @@ try createConstColumn>(4, 4))); // const, const, const ASSERT_COLUMN_EQ( - createConstColumn>(1, "www."), + createConstColumn(1, "www."), executeFunction( "substringUTF8", createConstColumn>(1, "www.pingcap.com"), diff --git a/dbms/src/Functions/tests/gtest_substring_index.cpp b/dbms/src/Functions/tests/gtest_substring_index.cpp index a8208b45042..9bb4675c329 100644 --- a/dbms/src/Functions/tests/gtest_substring_index.cpp +++ b/dbms/src/Functions/tests/gtest_substring_index.cpp @@ -75,7 +75,9 @@ class SubstringIndexTest : public DB::tests::FunctionTest auto inner_test = [&](bool is_str_const, bool is_delim_const, bool is_count_const) { bool is_one_of_args_null_const = (is_str_const && !str.has_value()) || (is_delim_const && !delim.has_value()) || (is_count_const && !count.has_value()); bool is_result_const = (is_str_const && is_delim_const && is_count_const) || is_one_of_args_null_const; - auto expected_res_column = is_result_const ? createConstColumn>(1, result) : createColumn>({result}); + if (is_result_const && !is_one_of_args_null_const && !result.has_value()) + throw Exception("Should not reach here"); + auto expected_res_column = is_result_const ? (is_one_of_args_null_const ? createConstColumn>(1, result) : createConstColumn(1, result.value())) : createColumn>({result}); auto str_column = is_str_const ? createConstColumn>(1, str) : createColumn>({str}); auto delim_column = is_delim_const ? createConstColumn>(1, delim) : createColumn>({delim}); auto count_column = is_count_const ? createConstColumn>(1, count) : createColumn>({count}); diff --git a/dbms/src/Functions/tests/gtest_unix_timestamp.cpp b/dbms/src/Functions/tests/gtest_unix_timestamp.cpp index 1fa02141c4c..15909a90af9 100644 --- a/dbms/src/Functions/tests/gtest_unix_timestamp.cpp +++ b/dbms/src/Functions/tests/gtest_unix_timestamp.cpp @@ -86,16 +86,16 @@ try executeFunction(func_name_dec, createConstColumn(std::make_tuple(6), 10, date_time_with_fsp_data[0]))); /// case 2, func(nullable(not null const)) ASSERT_COLUMN_EQ( - createConstColumn>(10, date_result[0]), + createConstColumn(10, date_result[0]), executeFunction(func_name_int, createConstColumn>(10, date_data[0]))); ASSERT_COLUMN_EQ( - createConstColumn>(10, date_time_int_result[0]), + createConstColumn(10, date_time_int_result[0]), executeFunction(func_name_int, createConstColumn>(std::make_tuple(0), 10, date_time_data[0]))); ASSERT_COLUMN_EQ( - createConstColumn>(std::make_tuple(12, 0), 10, date_time_decimal_result[0]), + createConstColumn(std::make_tuple(12, 0), 10, date_time_decimal_result[0]), executeFunction(func_name_dec, createConstColumn>(std::make_tuple(0), 10, date_time_data[0]))); ASSERT_COLUMN_EQ( - createConstColumn>(std::make_tuple(18, 6), 10, date_time_with_fsp_result[0]), + createConstColumn(std::make_tuple(18, 6), 10, date_time_with_fsp_result[0]), executeFunction(func_name_dec, createConstColumn>(std::make_tuple(6), 10, date_time_with_fsp_data[0]))); /// case 3, func(nullable(null const)) ASSERT_COLUMN_EQ( diff --git a/dbms/src/TestUtils/FunctionTestUtils.h b/dbms/src/TestUtils/FunctionTestUtils.h index 615a58ebda5..7704c69a89f 100644 --- a/dbms/src/TestUtils/FunctionTestUtils.h +++ b/dbms/src/TestUtils/FunctionTestUtils.h @@ -536,7 +536,7 @@ ColumnWithTypeAndName executeFunction( const String & func_name, const ColumnsWithTypeAndName & columns, const TiDB::TiDBCollatorPtr & collator = nullptr, - bool raw_function_test = true); + bool raw_function_test = false); ColumnWithTypeAndName executeFunction( Context & context, @@ -544,7 +544,7 @@ ColumnWithTypeAndName executeFunction( const ColumnNumbers & argument_column_numbers, const ColumnsWithTypeAndName & columns, const TiDB::TiDBCollatorPtr & collator = nullptr, - bool raw_function_test = true); + bool raw_function_test = false); template ColumnWithTypeAndName executeFunction( @@ -562,7 +562,7 @@ DataTypePtr getReturnTypeForFunction( const String & func_name, const ColumnsWithTypeAndName & columns, const TiDB::TiDBCollatorPtr & collator = nullptr, - bool raw_function_test = true); + bool raw_function_test = false); template ColumnWithTypeAndName createNullableColumn(InferredDataVector init_vec, const std::vector & null_map, const String name = "") @@ -687,7 +687,7 @@ class FunctionTest : public ::testing::Test const String & func_name, const ColumnsWithTypeAndName & columns, const TiDB::TiDBCollatorPtr & collator = nullptr, - bool raw_function_test = true) + bool raw_function_test = false) { return DB::tests::executeFunction(context, func_name, columns, collator, raw_function_test); } @@ -704,7 +704,7 @@ class FunctionTest : public ::testing::Test const ColumnNumbers & argument_column_numbers, const ColumnsWithTypeAndName & columns, const TiDB::TiDBCollatorPtr & collator = nullptr, - bool raw_function_test = true) + bool raw_function_test = false) { return DB::tests::executeFunction(context, func_name, argument_column_numbers, columns, collator, raw_function_test); } From 187a591269fb2dc0c3fd81b1f3aeb671963dc8c8 Mon Sep 17 00:00:00 2001 From: hehechen Date: Wed, 1 Jun 2022 21:14:27 +0800 Subject: [PATCH 081/127] Tiflash pagectl support encrypted data (#5003) close pingcap/tiflash#4962 --- dbms/src/Server/CLIService.h | 237 ++++++++++++++++++ dbms/src/Server/CMakeLists.txt | 4 + dbms/src/Server/DTTool/DTTool.h | 200 +-------------- dbms/src/Server/config_tools.h.in | 1 + dbms/src/Server/main.cpp | 6 + dbms/src/Storages/Page/CMakeLists.txt | 1 + dbms/src/Storages/Page/V2/CMakeLists.txt | 14 +- dbms/src/Storages/Page/V2/gc/DataCompactor.h | 7 +- dbms/src/Storages/Page/V3/BlobStore.h | 2 +- dbms/src/Storages/Page/V3/PageDirectory.h | 4 +- dbms/src/Storages/Page/V3/PageStorageImpl.h | 2 +- .../src/Storages/Page/V3/tests/CMakeLists.txt | 8 +- dbms/src/Storages/Page/tools/CMakeLists.txt | 17 ++ .../Page/tools/PageCtl/CMakeLists.txt | 24 ++ dbms/src/Storages/Page/tools/PageCtl/Main.cpp | 20 ++ .../Storages/Page/tools/PageCtl/MainEntry.cpp | 69 +++++ .../Page/tools/PageCtl/PageStorageCtl.h | 24 ++ .../PageCtl/PageStorageCtlV2.cpp} | 33 +-- .../Page/tools/PageCtl/PageStorageCtlV2.h | 17 ++ .../PageCtl/PageStorageCtlV3.cpp} | 86 +++++-- .../Page/tools/PageCtl/PageStorageCtlV3.h | 17 ++ 21 files changed, 523 insertions(+), 270 deletions(-) create mode 100644 dbms/src/Server/CLIService.h create mode 100644 dbms/src/Storages/Page/tools/CMakeLists.txt create mode 100644 dbms/src/Storages/Page/tools/PageCtl/CMakeLists.txt create mode 100644 dbms/src/Storages/Page/tools/PageCtl/Main.cpp create mode 100644 dbms/src/Storages/Page/tools/PageCtl/MainEntry.cpp create mode 100644 dbms/src/Storages/Page/tools/PageCtl/PageStorageCtl.h rename dbms/src/Storages/Page/{V2/tests/page_storage_ctl.cpp => tools/PageCtl/PageStorageCtlV2.cpp} (93%) create mode 100644 dbms/src/Storages/Page/tools/PageCtl/PageStorageCtlV2.h rename dbms/src/Storages/Page/{V3/tests/page_storage_ctl.cpp => tools/PageCtl/PageStorageCtlV3.cpp} (87%) create mode 100644 dbms/src/Storages/Page/tools/PageCtl/PageStorageCtlV3.h diff --git a/dbms/src/Server/CLIService.h b/dbms/src/Server/CLIService.h new file mode 100644 index 00000000000..18c9d61260f --- /dev/null +++ b/dbms/src/Server/CLIService.h @@ -0,0 +1,237 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +using RaftStoreFFIFunc = void (*)(int argc, const char * const * argv, const DB::EngineStoreServerHelper *); + +template +struct CLIService : public BaseDaemon +{ + struct TiFlashProxyConfig + { + static const std::string config_prefix; + std::vector args; + std::unordered_map val_map; + bool is_proxy_runnable = false; + + static constexpr char ENGINE_STORE_VERSION[] = "engine-version"; + static constexpr char ENGINE_STORE_GIT_HASH[] = "engine-git-hash"; + static constexpr char ENGINE_STORE_ADDRESS[] = "engine-addr"; + static constexpr char ENGINE_STORE_ADVERTISE_ADDRESS[] = "advertise-engine-addr"; + static constexpr char PD_ENDPOINTS[] = "pd-endpoints"; + static constexpr char ENGINE_LABEL[] = "engine-label"; + static constexpr char ENGINE_LABEL_VALUE[] = "tiflash"; + + explicit TiFlashProxyConfig(Poco::Util::LayeredConfiguration & config); + }; + + struct RaftStoreProxyRunner : boost::noncopyable + { + struct RunRaftStoreProxyParms + { + const DB::EngineStoreServerHelper * helper; + const TiFlashProxyConfig & conf; + const RaftStoreFFIFunc ffi_function; + + /// set big enough stack size to avoid runtime error like stack-overflow. + size_t stack_size = 1024 * 1024 * 20; + }; + + explicit RaftStoreProxyRunner(RunRaftStoreProxyParms && parms_); + + void join(); + + void run(); + + private: + static void * runRaftStoreProxyFfi(void * pv); + + private: + RunRaftStoreProxyParms parms; + pthread_t thread; + }; + + Func func; + RaftStoreFFIFunc ffi_function; + const Args & args; + std::unique_ptr global_context; + + explicit CLIService(Func func_, const Args & args_, const std::string & config_file, RaftStoreFFIFunc ffi_function = nullptr); + + int main(const std::vector &) override; +}; + +template +CLIService::TiFlashProxyConfig::TiFlashProxyConfig(Poco::Util::LayeredConfiguration & config) +{ + if (!config.has(config_prefix)) + return; + + Poco::Util::AbstractConfiguration::Keys keys; + config.keys(config_prefix, keys); + { + std::unordered_map args_map; + for (const auto & key : keys) + { + const auto k = config_prefix + "." + key; + args_map[key] = config.getString(k); + } + args_map[PD_ENDPOINTS] = config.getString("raft.pd_addr"); + args_map[ENGINE_STORE_VERSION] = TiFlashBuildInfo::getReleaseVersion(); + args_map[ENGINE_STORE_GIT_HASH] = TiFlashBuildInfo::getGitHash(); + if (!args_map.count(ENGINE_STORE_ADDRESS)) + args_map[ENGINE_STORE_ADDRESS] = config.getString("flash.service_addr"); + else + args_map[ENGINE_STORE_ADVERTISE_ADDRESS] = args_map[ENGINE_STORE_ADDRESS]; + args_map[ENGINE_LABEL] = ENGINE_LABEL_VALUE; + + for (auto && [k, v] : args_map) + { + val_map.emplace("--" + k, std::move(v)); + } + } + + args.push_back("TiFlash Proxy"); + for (const auto & v : val_map) + { + args.push_back(v.first.data()); + args.push_back(v.second.data()); + } + is_proxy_runnable = true; +} +template +CLIService::RaftStoreProxyRunner::RaftStoreProxyRunner(CLIService::RaftStoreProxyRunner::RunRaftStoreProxyParms && parms_) + : parms(std::move(parms_)) +{} +template +void CLIService::RaftStoreProxyRunner::join() +{ + if (!parms.conf.is_proxy_runnable) + return; + pthread_join(thread, nullptr); +} +template +void CLIService::RaftStoreProxyRunner::run() +{ + if (!parms.conf.is_proxy_runnable) + return; + pthread_attr_t attribute; + pthread_attr_init(&attribute); + pthread_attr_setstacksize(&attribute, parms.stack_size); + pthread_create(&thread, &attribute, runRaftStoreProxyFfi, &parms); + pthread_attr_destroy(&attribute); +} +template +void * CLIService::RaftStoreProxyRunner::runRaftStoreProxyFfi(void * pv) +{ + auto & parms = *static_cast(pv); + if (nullptr == parms.ffi_function) + { + throw DB::Exception("proxy is not available"); + } + parms.ffi_function(static_cast(parms.conf.args.size()), parms.conf.args.data(), parms.helper); + return nullptr; +} + +template +CLIService::CLIService(Func func_, const Args & args_, const std::string & config_file, RaftStoreFFIFunc ffi_function) + : func(std::move(func_)) + , ffi_function(ffi_function) + , args(args_) +{ + config_path = config_file; + ConfigProcessor config_processor(config_file); + auto loaded_config = config_processor.loadConfig(); + BaseDaemon::config().add(loaded_config.configuration); + BaseDaemon::config().setString("config-file", config_file); +} + +template +int CLIService::main(const std::vector &) +{ + using namespace DB; + TiFlashProxyConfig proxy_conf(config()); + EngineStoreServerWrap tiflash_instance_wrap{}; + auto helper = GetEngineStoreServerHelper( + &tiflash_instance_wrap); + + typename RaftStoreProxyRunner::RunRaftStoreProxyParms parms{&helper, proxy_conf, ffi_function}; + RaftStoreProxyRunner proxy_runner(std::move(parms)); + + proxy_runner.run(); + + if (proxy_conf.is_proxy_runnable) + { + while (!tiflash_instance_wrap.proxy_helper) + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + + SCOPE_EXIT({ + if (!proxy_conf.is_proxy_runnable) + { + proxy_runner.join(); + return; + } + tiflash_instance_wrap.status = EngineStoreServerStatus::Terminated; + tiflash_instance_wrap.tmt = nullptr; + proxy_runner.join(); + }); + + global_context = std::make_unique(Context::createGlobal()); + global_context->setGlobalContext(*global_context); + global_context->setApplicationType(Context::ApplicationType::SERVER); + + /// Init File Provider + if (proxy_conf.is_proxy_runnable) + { + bool enable_encryption = tiflash_instance_wrap.proxy_helper->checkEncryptionEnabled(); + if (enable_encryption) + { + auto method = tiflash_instance_wrap.proxy_helper->getEncryptionMethod(); + enable_encryption = (method != EncryptionMethod::Plaintext); + } + KeyManagerPtr key_manager = std::make_shared(&tiflash_instance_wrap); + global_context->initializeFileProvider(key_manager, enable_encryption); + } + else + { + KeyManagerPtr key_manager = std::make_shared(false); + global_context->initializeFileProvider(key_manager, false); + } + + return func(*global_context, args); +} + + +template +inline const std::string CLIService::TiFlashProxyConfig::config_prefix = "flash.proxy"; diff --git a/dbms/src/Server/CMakeLists.txt b/dbms/src/Server/CMakeLists.txt index 6c3d289dea6..63cf6d0e1f9 100644 --- a/dbms/src/Server/CMakeLists.txt +++ b/dbms/src/Server/CMakeLists.txt @@ -22,6 +22,7 @@ option(ENABLE_CLICKHOUSE_SERVER "Enable server" ${ENABLE_CLICKHOUSE_ALL}) option(ENABLE_CLICKHOUSE_CLIENT "Enable client" ${ENABLE_CLICKHOUSE_ALL}) option(ENABLE_TIFLASH_DTTOOL "Enable dttool: tools to manage dmfile" ${ENABLE_CLICKHOUSE_ALL}) option(ENABLE_TIFLASH_DTWORKLOAD "Enable dtworkload: tools to test and stress DeltaTree" ${ENABLE_CLICKHOUSE_ALL}) +option(ENABLE_TIFLASH_PAGECTL "Enable pagectl: tools to debug page storage" ${ENABLE_CLICKHOUSE_ALL}) configure_file (config_tools.h.in ${CMAKE_CURRENT_BINARY_DIR}/config_tools.h) @@ -135,6 +136,9 @@ endif () if (ENABLE_TIFLASH_DTWORKLOAD) target_link_libraries(tiflash dt-workload-lib) endif () +if (ENABLE_TIFLASH_PAGECTL) + target_link_libraries(tiflash page-ctl-lib) +endif () # install always because depian package want this files: install (TARGETS tiflash RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT tiflash) diff --git a/dbms/src/Server/DTTool/DTTool.h b/dbms/src/Server/DTTool/DTTool.h index 911c29bf98b..6236bd6cdb9 100644 --- a/dbms/src/Server/DTTool/DTTool.h +++ b/dbms/src/Server/DTTool/DTTool.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -76,205 +77,6 @@ int migrateEntry(const std::vector & opts, RaftStoreFFIFunc ffi_fun namespace DTTool { -template -struct CLIService : public BaseDaemon -{ - struct TiFlashProxyConfig - { - static const std::string config_prefix; - std::vector args; - std::unordered_map val_map; - bool is_proxy_runnable = false; - - static constexpr char ENGINE_STORE_VERSION[] = "engine-version"; - static constexpr char ENGINE_STORE_GIT_HASH[] = "engine-git-hash"; - static constexpr char ENGINE_STORE_ADDRESS[] = "engine-addr"; - static constexpr char ENGINE_STORE_ADVERTISE_ADDRESS[] = "advertise-engine-addr"; - static constexpr char PD_ENDPOINTS[] = "pd-endpoints"; - - explicit TiFlashProxyConfig(Poco::Util::LayeredConfiguration & config); - }; - - struct RaftStoreProxyRunner : boost::noncopyable - { - struct RunRaftStoreProxyParms - { - const DB::EngineStoreServerHelper * helper; - const TiFlashProxyConfig & conf; - const RaftStoreFFIFunc ffi_function; - - /// set big enough stack size to avoid runtime error like stack-overflow. - size_t stack_size = 1024 * 1024 * 20; - }; - - explicit RaftStoreProxyRunner(RunRaftStoreProxyParms && parms_); - - void join(); - - void run(); - - private: - static void * runRaftStoreProxyFfi(void * pv); - - private: - RunRaftStoreProxyParms parms; - pthread_t thread; - }; - - Func func; - RaftStoreFFIFunc ffi_function; - const Args & args; - std::unique_ptr global_context; - - explicit CLIService(Func func_, const Args & args_, const std::string & config_file, RaftStoreFFIFunc ffi_function = nullptr); - - int main(const std::vector &) override; -}; - -template -CLIService::TiFlashProxyConfig::TiFlashProxyConfig(Poco::Util::LayeredConfiguration & config) -{ - if (!config.has(config_prefix)) - return; - - Poco::Util::AbstractConfiguration::Keys keys; - config.keys(config_prefix, keys); - { - std::unordered_map args_map; - for (const auto & key : keys) - { - const auto k = config_prefix + "." + key; - args_map[key] = config.getString(k); - } - args_map[PD_ENDPOINTS] = config.getString("raft.pd_addr"); - args_map[ENGINE_STORE_VERSION] = TiFlashBuildInfo::getReleaseVersion(); - args_map[ENGINE_STORE_GIT_HASH] = TiFlashBuildInfo::getGitHash(); - if (!args_map.count(ENGINE_STORE_ADDRESS)) - args_map[ENGINE_STORE_ADDRESS] = config.getString("flash.service_addr"); - else - args_map[ENGINE_STORE_ADVERTISE_ADDRESS] = args_map[ENGINE_STORE_ADDRESS]; - - for (auto && [k, v] : args_map) - { - val_map.emplace("--" + k, std::move(v)); - } - } - - args.push_back("TiFlash Proxy"); - for (const auto & v : val_map) - { - args.push_back(v.first.data()); - args.push_back(v.second.data()); - } - is_proxy_runnable = true; -} -template -CLIService::RaftStoreProxyRunner::RaftStoreProxyRunner(CLIService::RaftStoreProxyRunner::RunRaftStoreProxyParms && parms_) - : parms(std::move(parms_)) -{} -template -void CLIService::RaftStoreProxyRunner::join() -{ - if (!parms.conf.is_proxy_runnable) - return; - pthread_join(thread, nullptr); -} -template -void CLIService::RaftStoreProxyRunner::run() -{ - if (!parms.conf.is_proxy_runnable) - return; - pthread_attr_t attribute; - pthread_attr_init(&attribute); - pthread_attr_setstacksize(&attribute, parms.stack_size); - pthread_create(&thread, &attribute, runRaftStoreProxyFfi, &parms); - pthread_attr_destroy(&attribute); -} -template -void * CLIService::RaftStoreProxyRunner::runRaftStoreProxyFfi(void * pv) -{ - auto & parms = *static_cast(pv); - if (nullptr == parms.ffi_function) - { - throw DB::Exception("proxy is not available"); - } - parms.ffi_function(static_cast(parms.conf.args.size()), parms.conf.args.data(), parms.helper); - return nullptr; -} - -template -CLIService::CLIService(Func func_, const Args & args_, const std::string & config_file, RaftStoreFFIFunc ffi_function) - : func(std::move(func_)) - , ffi_function(ffi_function) - , args(args_) -{ - config_path = config_file; - ConfigProcessor config_processor(config_file); - auto loaded_config = config_processor.loadConfig(); - BaseDaemon::config().add(loaded_config.configuration); - BaseDaemon::config().setString("config-file", config_file); -} - -template -int CLIService::main(const std::vector &) -{ - using namespace DB; - TiFlashProxyConfig proxy_conf(config()); - EngineStoreServerWrap tiflash_instance_wrap{}; - auto helper = GetEngineStoreServerHelper( - &tiflash_instance_wrap); - - typename RaftStoreProxyRunner::RunRaftStoreProxyParms parms{&helper, proxy_conf, ffi_function}; - RaftStoreProxyRunner proxy_runner(std::move(parms)); - - proxy_runner.run(); - - if (proxy_conf.is_proxy_runnable) - { - while (!tiflash_instance_wrap.proxy_helper) - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - } - - SCOPE_EXIT({ - if (!proxy_conf.is_proxy_runnable) - { - proxy_runner.join(); - return; - } - tiflash_instance_wrap.status = EngineStoreServerStatus::Terminated; - tiflash_instance_wrap.tmt = nullptr; - proxy_runner.join(); - }); - - global_context = std::make_unique(Context::createGlobal()); - global_context->setGlobalContext(*global_context); - global_context->setApplicationType(Context::ApplicationType::SERVER); - - /// Init File Provider - if (proxy_conf.is_proxy_runnable) - { - bool enable_encryption = tiflash_instance_wrap.proxy_helper->checkEncryptionEnabled(); - if (enable_encryption) - { - auto method = tiflash_instance_wrap.proxy_helper->getEncryptionMethod(); - enable_encryption = (method != EncryptionMethod::Plaintext); - } - KeyManagerPtr key_manager = std::make_shared(&tiflash_instance_wrap); - global_context->initializeFileProvider(key_manager, enable_encryption); - } - else - { - KeyManagerPtr key_manager = std::make_shared(false); - global_context->initializeFileProvider(key_manager, false); - } - - return func(*global_context, args); -} - - -template -inline const std::string CLIService::TiFlashProxyConfig::config_prefix = "flash.proxy"; - namespace detail { using namespace DB; diff --git a/dbms/src/Server/config_tools.h.in b/dbms/src/Server/config_tools.h.in index 03df94cc8e1..61aa3f41591 100644 --- a/dbms/src/Server/config_tools.h.in +++ b/dbms/src/Server/config_tools.h.in @@ -6,3 +6,4 @@ #cmakedefine01 ENABLE_CLICKHOUSE_CLIENT #cmakedefine01 ENABLE_TIFLASH_DTTOOL #cmakedefine01 ENABLE_TIFLASH_DTWORKLOAD +#cmakedefine01 ENABLE_TIFLASH_PAGECTL diff --git a/dbms/src/Server/main.cpp b/dbms/src/Server/main.cpp index ace9dfc80ee..11cccf84729 100644 --- a/dbms/src/Server/main.cpp +++ b/dbms/src/Server/main.cpp @@ -38,6 +38,9 @@ #if ENABLE_TIFLASH_DTWORKLOAD #include #endif +#if ENABLE_TIFLASH_PAGECTL +#include +#endif #include #include @@ -103,6 +106,9 @@ std::pair clickhouse_applications[] = { #endif #if ENABLE_TIFLASH_DTWORKLOAD {"dtworkload", DB::DM::tests::DTWorkload::mainEntry}, +#endif +#if ENABLE_TIFLASH_PAGECTL + {"pagectl", DB::PageStorageCtl::mainEntry}, #endif {"version", mainEntryVersion}, {"errgen", mainExportError}}; diff --git a/dbms/src/Storages/Page/CMakeLists.txt b/dbms/src/Storages/Page/CMakeLists.txt index 1883f0bc0aa..cead83fa126 100644 --- a/dbms/src/Storages/Page/CMakeLists.txt +++ b/dbms/src/Storages/Page/CMakeLists.txt @@ -13,6 +13,7 @@ # limitations under the License. add_subdirectory(V2) +add_subdirectory(tools) # PageStorage Stress test if (ENABLE_V3_PAGESTORAGE) diff --git a/dbms/src/Storages/Page/V2/CMakeLists.txt b/dbms/src/Storages/Page/V2/CMakeLists.txt index e840960b653..f994358f2b1 100644 --- a/dbms/src/Storages/Page/V2/CMakeLists.txt +++ b/dbms/src/Storages/Page/V2/CMakeLists.txt @@ -42,16 +42,4 @@ add_library(page_storage_v2 EXCLUDE_FROM_ALL ) target_include_directories(page_storage_v2 PUBLIC ${TiFlash_SOURCE_DIR}/contrib/tiflash-proxy/raftstore-proxy/ffi/src) target_link_libraries(page_storage_v2 clickhouse_common_io cpptoml - kv_client tipb) # TODO: remove dependency on these libs. Now we need them for DB::Context - -### Build a control binary for PageStorage -## For `page_ctl`, we need to define `PAGE_STORAGE_UTIL_DEBUGGGING` -add_executable(page_ctl EXCLUDE_FROM_ALL - tests/page_storage_ctl.cpp - ${page_storage_v2_headers} ${page_storage_v2_sources} - ${io_base_headers} ${io_base_sources} -) -target_include_directories(page_ctl PUBLIC ${TiFlash_SOURCE_DIR}/contrib/tiflash-proxy/raftstore-proxy/ffi/src) -target_link_libraries(page_ctl clickhouse_common_io cpptoml) -target_compile_options(page_ctl PRIVATE -Wno-format) -target_compile_definitions(page_ctl PRIVATE PAGE_STORAGE_UTIL_DEBUGGGING DBMS_PUBLIC_GTEST) + kv_client tipb) # TODO: remove dependency on these libs. Now we need them for DB::Context \ No newline at end of file diff --git a/dbms/src/Storages/Page/V2/gc/DataCompactor.h b/dbms/src/Storages/Page/V2/gc/DataCompactor.h index 1ddd36f87dd..eede1775cdf 100644 --- a/dbms/src/Storages/Page/V2/gc/DataCompactor.h +++ b/dbms/src/Storages/Page/V2/gc/DataCompactor.h @@ -60,10 +60,6 @@ class DataCompactor : private boost::noncopyable std::tuple tryMigrate(const PageFileSet & page_files, SnapshotPtr && snapshot, const WritingFilesSnapshot & writing_files); -#ifndef DBMS_PUBLIC_GTEST -private: -#endif - /** * Collect valid page of snapshot. * Return { @@ -72,6 +68,9 @@ class DataCompactor : private boost::noncopyable * } */ static ValidPages collectValidPagesInPageFile(const SnapshotPtr & snapshot); +#ifndef DBMS_PUBLIC_GTEST +private: +#endif struct CompactCandidates { diff --git a/dbms/src/Storages/Page/V3/BlobStore.h b/dbms/src/Storages/Page/V3/BlobStore.h index 5a3e98400d1..24bf4652123 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.h +++ b/dbms/src/Storages/Page/V3/BlobStore.h @@ -316,7 +316,7 @@ class BlobStore : private Allocator BlobFilePtr getBlobFile(BlobFileId blob_id); friend class PageDirectoryFactory; - friend class PageStorageControl; + friend class PageStorageControlV3; #ifndef DBMS_PUBLIC_GTEST private: diff --git a/dbms/src/Storages/Page/V3/PageDirectory.h b/dbms/src/Storages/Page/V3/PageDirectory.h index 39b5a05a40a..bd7c433022f 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.h +++ b/dbms/src/Storages/Page/V3/PageDirectory.h @@ -255,7 +255,7 @@ class VersionedPageEntries being_ref_count, entries.size()); } - friend class PageStorageControl; + friend class PageStorageControlV3; private: mutable std::mutex m; @@ -376,7 +376,7 @@ class PageDirectory DISALLOW_COPY_AND_MOVE(PageDirectory); friend class PageDirectoryFactory; - friend class PageStorageControl; + friend class PageStorageControlV3; private: // Only `std::map` is allow for `MVCCMap`. Cause `std::map::insert` ensure that diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.h b/dbms/src/Storages/Page/V3/PageStorageImpl.h index f49601ce2ad..b4cdd425e59 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.h +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.h @@ -115,7 +115,7 @@ class PageStorageImpl : public DB::PageStorage #endif friend class PageDirectoryFactory; - friend class PageStorageControl; + friend class PageStorageControlV3; #ifndef DBMS_PUBLIC_GTEST private: #endif diff --git a/dbms/src/Storages/Page/V3/tests/CMakeLists.txt b/dbms/src/Storages/Page/V3/tests/CMakeLists.txt index 8bab6afcded..355247c9eba 100644 --- a/dbms/src/Storages/Page/V3/tests/CMakeLists.txt +++ b/dbms/src/Storages/Page/V3/tests/CMakeLists.txt @@ -26,10 +26,4 @@ add_executable(gtests_page_storage_v3 ${ps_v3_gtest_sources} ${TiFlash_SOURCE_DI target_link_libraries(gtests_page_storage_v3 page_storage_v3 gtest_main) target_compile_options(gtests_page_storage_v3 PRIVATE -Wno-unknown-pragmas) target_compile_definitions(gtests_page_storage_v3 PRIVATE DBMS_PUBLIC_GTEST) -add_check(gtests_page_storage_v3) - - -add_executable(page_storage_ctl EXCLUDE_FROM_ALL page_storage_ctl.cpp) -target_compile_definitions(page_storage_ctl PUBLIC DBMS_PUBLIC_GTEST) -target_link_libraries(page_storage_ctl dbms page_storage_v3) -target_compile_options(page_storage_ctl PRIVATE -Wno-format -lc++) # turn off printf format check +add_check(gtests_page_storage_v3) \ No newline at end of file diff --git a/dbms/src/Storages/Page/tools/CMakeLists.txt b/dbms/src/Storages/Page/tools/CMakeLists.txt new file mode 100644 index 00000000000..629dedd01fc --- /dev/null +++ b/dbms/src/Storages/Page/tools/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright 2022 PingCAP, Ltd. +# +# 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. + +include_directories (${CMAKE_CURRENT_BINARY_DIR}) + +add_subdirectory (PageCtl EXCLUDE_FROM_ALL) diff --git a/dbms/src/Storages/Page/tools/PageCtl/CMakeLists.txt b/dbms/src/Storages/Page/tools/PageCtl/CMakeLists.txt new file mode 100644 index 00000000000..576b5e07a0f --- /dev/null +++ b/dbms/src/Storages/Page/tools/PageCtl/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright 2022 PingCAP, Ltd. +# +# 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. + +include_directories (${CMAKE_CURRENT_BINARY_DIR}) + +add_library(page-ctl-lib MainEntry.cpp PageStorageCtlV3.cpp PageStorageCtlV2.cpp ${page-ctl-src}) +target_include_directories(page-ctl-lib PUBLIC ${TiFlash_SOURCE_DIR}/libs/libdaemon/include) +target_link_libraries(page-ctl-lib dbms daemon tiflash-dttool-entry-object clickhouse-server-lib) +target_compile_options(page-ctl-lib PRIVATE -Wno-format) + +add_executable(page-ctl Main.cpp) +target_link_libraries(page-ctl page-ctl-lib dbms clickhouse_functions clickhouse-server-lib) +target_compile_options(page-ctl PRIVATE -Wno-format) diff --git a/dbms/src/Storages/Page/tools/PageCtl/Main.cpp b/dbms/src/Storages/Page/tools/PageCtl/Main.cpp new file mode 100644 index 00000000000..ae9901ec864 --- /dev/null +++ b/dbms/src/Storages/Page/tools/PageCtl/Main.cpp @@ -0,0 +1,20 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include + +int main(int argc, char ** argv) +{ + return DB::PageStorageCtl::mainEntry(argc, argv); +} diff --git a/dbms/src/Storages/Page/tools/PageCtl/MainEntry.cpp b/dbms/src/Storages/Page/tools/PageCtl/MainEntry.cpp new file mode 100644 index 00000000000..69b41435c34 --- /dev/null +++ b/dbms/src/Storages/Page/tools/PageCtl/MainEntry.cpp @@ -0,0 +1,69 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include +#include + +#include +#include +namespace DB +{ +int PageStorageCtl::mainEntry(int argc, char ** argv) +{ + namespace po = boost::program_options; + using po::value; + po::options_description desc("Allowed commands"); + desc.add_options()("help,h", "produce help message") // + ("page_storage_version,V", value(), "PageStorage Version: 2 means PageStorage V2, 3 means PageStorage V3.\n"); + po::variables_map options; + po::parsed_options parsed = po::command_line_parser(argc, argv) + .options(desc) + .allow_unregistered() + .run(); + po::store(parsed, options); + po::notify(options); + if (options.count("page_storage_version") == 0 && options.count("help") == 0) + { + std::cerr << "Invalid arg page_storage_version." << std::endl; + std::cerr << desc << std::endl; + exit(0); + } + if (options.count("page_storage_version") > 0) + { + int ps_version = options["page_storage_version"].as(); + if (ps_version == 3) + { + pageStorageV3CtlEntry(argc - 2, argv + 2); + return 0; + } + else if (ps_version == 2) + { + return pageStorageV2CtlEntry(argc - 2, argv + 2); + } + else + { + std::cerr << "Invalid arg page_storage_version." << std::endl; + std::cerr << desc << std::endl; + exit(0); + } + } + if (options.count("help") > 0) + { + std::cerr << desc << std::endl; + exit(0); + } + return 0; +} +} // namespace DB diff --git a/dbms/src/Storages/Page/tools/PageCtl/PageStorageCtl.h b/dbms/src/Storages/Page/tools/PageCtl/PageStorageCtl.h new file mode 100644 index 00000000000..c8f35a7750a --- /dev/null +++ b/dbms/src/Storages/Page/tools/PageCtl/PageStorageCtl.h @@ -0,0 +1,24 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +namespace DB +{ +class PageStorageCtl +{ +public: + static int mainEntry(int argc, char ** argv); +}; +} // namespace DB \ No newline at end of file diff --git a/dbms/src/Storages/Page/V2/tests/page_storage_ctl.cpp b/dbms/src/Storages/Page/tools/PageCtl/PageStorageCtlV2.cpp similarity index 93% rename from dbms/src/Storages/Page/V2/tests/page_storage_ctl.cpp rename to dbms/src/Storages/Page/tools/PageCtl/PageStorageCtlV2.cpp index 2fb7e31d70e..f9488c4dfd9 100644 --- a/dbms/src/Storages/Page/V2/tests/page_storage_ctl.cpp +++ b/dbms/src/Storages/Page/tools/PageCtl/PageStorageCtlV2.cpp @@ -28,34 +28,21 @@ using namespace DB::PS::V2; DB::WriteBatch::SequenceID debugging_recover_stop_sequence = 0; -/* some exported global vars */ -namespace DB -{ -#if __APPLE__ && __clang__ -__thread bool is_background_thread = false; -#else -thread_local bool is_background_thread = false; -#endif -} // namespace DB -/* some exported global vars */ -void Usage(const char * prog) +void Usage() { fprintf(stderr, R"HELP( -Usage: %s +Usage: mode == 1 -> dump all page entries 2 -> dump valid page entries - param: %s 2 [max-recover-sequence] + param: 2 [max-recover-sequence] 3 -> check all page entries and page data checksum 4 -> list capacity of all page files 5 -> list all page files 1000 -> gc files - param: %s 1000 [run-gc-times=1] [min-gc-file-num=10] [min-gc-bytes=134217728] [max-gc-valid-rate=0.35] -)HELP", - prog, - prog, - prog); + param: 1000 [run-gc-times=1] [min-gc-file-num=10] [min-gc-bytes=134217728] [max-gc-valid-rate=0.35] + )HELP"); } void printPageEntry(const DB::PageId pid, const DB::PageEntry & entry) @@ -117,7 +104,7 @@ PageStorage::Config parse_storage_config(int argc, char ** argv, Poco::Logger * return config; } -int main(int argc, char ** argv) +int pageStorageV2CtlEntry(int argc, char ** argv) try { (void)argc; @@ -125,7 +112,7 @@ try if (argc < 3) { - Usage(argv[0]); + Usage(); return 1; } @@ -153,7 +140,7 @@ try LOG_FMT_INFO(logger, "Running with [mode={}]", mode); break; default: - Usage(argv[0]); + Usage(); return 1; } @@ -271,13 +258,13 @@ void dump_all_entries(PageFileSet & page_files, int32_t mode) id_and_caches.emplace_back(std::make_pair(record.page_id, record.entry)); break; case DB::WriteBatch::WriteType::DEL: - printf("DEL\t%lld\n", // + printf("DEL\t%lld\t%llu\t%u\n", // record.page_id, page_file.getFileId(), page_file.getLevel()); break; case DB::WriteBatch::WriteType::REF: - printf("REF\t%lld\t%lld\t\n", // + printf("REF\t%lld\t%lld\t\t%llu\t%u\n", // record.page_id, record.ori_page_id, page_file.getFileId(), diff --git a/dbms/src/Storages/Page/tools/PageCtl/PageStorageCtlV2.h b/dbms/src/Storages/Page/tools/PageCtl/PageStorageCtlV2.h new file mode 100644 index 00000000000..6d573ffaba7 --- /dev/null +++ b/dbms/src/Storages/Page/tools/PageCtl/PageStorageCtlV2.h @@ -0,0 +1,17 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +int pageStorageV2CtlEntry(int argc, char ** argv); diff --git a/dbms/src/Storages/Page/V3/tests/page_storage_ctl.cpp b/dbms/src/Storages/Page/tools/PageCtl/PageStorageCtlV3.cpp similarity index 87% rename from dbms/src/Storages/Page/V3/tests/page_storage_ctl.cpp rename to dbms/src/Storages/Page/tools/PageCtl/PageStorageCtlV3.cpp index 7ea8da6892a..c9b871c67d2 100644 --- a/dbms/src/Storages/Page/V3/tests/page_storage_ctl.cpp +++ b/dbms/src/Storages/Page/tools/PageCtl/PageStorageCtlV3.cpp @@ -12,19 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include #include +#include #include -#include -#include #include -#include -#include -#include +#include #include #include #include -#include #include #include @@ -32,6 +27,9 @@ namespace DB::PS::V3 { +extern "C" { +void run_raftstore_proxy_ffi(int argc, const char * const * argv, const DB::EngineStoreServerHelper *); +} struct ControlOptions { enum DisplayType @@ -49,6 +47,8 @@ struct ControlOptions UInt64 query_ns_id = DB::TEST_NAMESPACE_ID; UInt64 check_page_id = UINT64_MAX; bool enable_fo_check = true; + bool is_imitative = true; + String config_file_path; static ControlOptions parse(int argc, char ** argv); }; @@ -65,12 +65,12 @@ ControlOptions ControlOptions::parse(int argc, char ** argv) ("display_mode,D", value()->default_value(1), "Display Mode: 1 is summary information,\n 2 is display all of stored page and version chaim(will be very long),\n 3 is display all blobs(in disk) data distribution. \n 4 is check every data is valid.") // ("enable_fo_check,E", value()->default_value(true), "Also check the evert field offsets. This options only works when `display_mode` is 4.") // ("query_ns_id,N", value()->default_value(DB::TEST_NAMESPACE_ID), "When used `check_page_id`/`query_page_id`/`query_blob_id` to query results. You can specify a namespace id.")("check_page_id,C", value()->default_value(UINT64_MAX), "Check a single Page id, display the exception if meet. And also will check the field offsets.") // - ("query_page_id,W", value()->default_value(UINT64_MAX), "Quert a single Page id, and print its version chaim.") // - ("query_blob_id,B", value()->default_value(UINT32_MAX), "Quert a single Blob id, and print its data distribution."); + ("query_page_id,W", value()->default_value(UINT64_MAX), "Query a single Page id, and print its version chaim.") // + ("query_blob_id,B", value()->default_value(UINT32_MAX), "Query a single Blob id, and print its data distribution.")("imitative,I", value()->default_value(true), "Use imitative context instead. (encryption is not supported in this mode so that no need to set config_file_path)")("config_file_path", value(), "Path to TiFlash config (tiflash.toml)."); static_assert(sizeof(DB::PageId) == sizeof(UInt64)); - static_assert(sizeof(DB::BlobFileId) == sizeof(UInt32)); + static_assert(sizeof(DB::BlobFileId) == sizeof(UInt64)); po::variables_map options; po::store(po::parse_command_line(argc, argv, desc), options); @@ -97,6 +97,21 @@ ControlOptions ControlOptions::parse(int argc, char ** argv) opt.enable_fo_check = options["enable_fo_check"].as(); opt.check_page_id = options["check_page_id"].as(); opt.query_ns_id = options["query_ns_id"].as(); + opt.is_imitative = options["imitative"].as(); + if (opt.is_imitative && options.count("config_file_path") != 0) + { + std::cerr << "config_file_path is not allowed in imitative mode" << std::endl; + exit(0); + } + else if (!opt.is_imitative && options.count("config_file_path") == 0) + { + std::cerr << "config_file_path is required in proxy mode" << std::endl; + exit(0); + } + if (options.count("config_file_path") != 0) + { + opt.config_file_path = options["config_file_path"].as(); + } if (opt.display_mode < DisplayType::DISPLAY_SUMMARY_INFO || opt.display_mode > DisplayType::CHECK_ALL_DATA_CRC) { @@ -108,15 +123,39 @@ ControlOptions ControlOptions::parse(int argc, char ** argv) return opt; } -class PageStorageControl +class PageStorageControlV3 { public: - explicit PageStorageControl(const ControlOptions & options_) + explicit PageStorageControlV3(const ControlOptions & options_) : options(options_) { } void run() + { + try + { + if (options.is_imitative) + { + Context context = Context::createGlobal(); + getPageStorageV3Info(context, options); + } + else + { + PageDirectory::MVCCMapType type; + CLIService service(getPageStorageV3Info, options, options.config_file_path, run_raftstore_proxy_ffi); + service.run({""}); + } + } + catch (...) + { + DB::tryLogCurrentException("exception thrown"); + std::abort(); // Finish testing if some error happened. + } + } + +private: + static int getPageStorageV3Info(Context & context, const ControlOptions & options) { DB::PSDiskDelegatorPtr delegator; if (options.paths.size() == 1) @@ -128,13 +167,20 @@ class PageStorageControl delegator = std::make_shared(options.paths); } - auto key_manager = std::make_shared(false); - auto file_provider = std::make_shared(key_manager, false); - + FileProviderPtr file_provider_ptr; + if (options.is_imitative) + { + auto key_manager = std::make_shared(false); + file_provider_ptr = std::make_shared(key_manager, false); + } + else + { + file_provider_ptr = context.getFileProvider(); + } BlobStore::Config blob_config; PageStorage::Config config; - PageStorageImpl ps_v3("PageStorageControl", delegator, config, file_provider); + PageStorageImpl ps_v3("PageStorageControlV3", delegator, config, file_provider_ptr); ps_v3.restore(); PageDirectory::MVCCMapType & mvcc_table_directory = ps_v3.page_directory->mvcc_table_directory; @@ -171,9 +217,9 @@ class PageStorageControl std::cout << "Invalid display mode." << std::endl; break; } + return 0; } -private: static String getBlobsInfo(BlobStore & blob_store, UInt32 blob_id) { auto stat_info = [](const BlobStore::BlobStats::BlobStatPtr & stat, const String & path) { @@ -469,9 +515,9 @@ class PageStorageControl } // namespace DB::PS::V3 using namespace DB::PS::V3; -int main(int argc, char ** argv) + +void pageStorageV3CtlEntry(int argc, char ** argv) { const auto & options = ControlOptions::parse(argc, argv); - PageStorageControl(options).run(); - return 0; + PageStorageControlV3(options).run(); } diff --git a/dbms/src/Storages/Page/tools/PageCtl/PageStorageCtlV3.h b/dbms/src/Storages/Page/tools/PageCtl/PageStorageCtlV3.h new file mode 100644 index 00000000000..21a929ee599 --- /dev/null +++ b/dbms/src/Storages/Page/tools/PageCtl/PageStorageCtlV3.h @@ -0,0 +1,17 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +void pageStorageV3CtlEntry(int argc, char ** argv); From 226dff62b1c045eebbf16ebb3f459f26328ee2ad Mon Sep 17 00:00:00 2001 From: Lloyd-Pottiger <60744015+Lloyd-Pottiger@users.noreply.github.com> Date: Thu, 2 Jun 2022 12:14:27 +0800 Subject: [PATCH 082/127] update tiflash proxy (#5043) ref pingcap/tiflash#4879 --- contrib/tiflash-proxy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tiflash-proxy b/contrib/tiflash-proxy index 7578b816399..ca2f51f94e5 160000 --- a/contrib/tiflash-proxy +++ b/contrib/tiflash-proxy @@ -1 +1 @@ -Subproject commit 7578b8163992ce933074135f8687ad447d88ea9b +Subproject commit ca2f51f94e55bdd23749dcc02ab4afb94eeb5ae5 From 2ce9529f1069185f82b3018e5b96152fcd02b601 Mon Sep 17 00:00:00 2001 From: lidezhu <47731263+lidezhu@users.noreply.github.com> Date: Thu, 2 Jun 2022 13:13:44 +0800 Subject: [PATCH 083/127] Fix potential data inconsistency under heavy ddl operation (#5044) * fix decoding error under heavy ddl operation * small fix --- dbms/src/Common/FailPoint.cpp | 3 ++- dbms/src/Debug/dbgFuncSchema.cpp | 18 +++++++++++++++++- dbms/src/Storages/IManageableStorage.h | 7 +++++-- dbms/src/Storages/StorageDeltaMerge.cpp | 13 ++++++++----- dbms/src/Storages/StorageDeltaMerge.h | 9 +++++++-- .../DecodingStorageSchemaSnapshot.h | 7 ++++--- .../Storages/Transaction/PartitionStreams.cpp | 12 ++++++------ .../Transaction/tests/RowCodecTestUtils.h | 4 ++-- dbms/src/TiDB/Schema/SchemaBuilder.cpp | 13 ++++++++++++- dbms/src/TiDB/Schema/TiDBSchemaSyncer.h | 9 +++++++++ .../ddl/alter_column_when_pk_is_handle.test | 18 +++++++++++++++++- 11 files changed, 89 insertions(+), 24 deletions(-) diff --git a/dbms/src/Common/FailPoint.cpp b/dbms/src/Common/FailPoint.cpp index 921dd5bf748..c6c3caa44ad 100644 --- a/dbms/src/Common/FailPoint.cpp +++ b/dbms/src/Common/FailPoint.cpp @@ -65,7 +65,8 @@ std::unordered_map> FailPointHelper::f M(exception_when_read_from_log) \ M(exception_mpp_hash_build) \ M(exception_before_drop_segment) \ - M(exception_after_drop_segment) + M(exception_after_drop_segment) \ + M(exception_between_schema_change_in_the_same_diff) #define APPLY_FOR_FAILPOINTS(M) \ M(skip_check_segment_update) \ diff --git a/dbms/src/Debug/dbgFuncSchema.cpp b/dbms/src/Debug/dbgFuncSchema.cpp index 8b73ddc23a3..c388015dc10 100644 --- a/dbms/src/Debug/dbgFuncSchema.cpp +++ b/dbms/src/Debug/dbgFuncSchema.cpp @@ -34,6 +34,7 @@ namespace DB { namespace ErrorCodes { +extern const int FAIL_POINT_ERROR; extern const int UNKNOWN_TABLE; } // namespace ErrorCodes @@ -62,7 +63,22 @@ void dbgFuncRefreshSchemas(Context & context, const ASTs &, DBGInvoker::Printer { TMTContext & tmt = context.getTMTContext(); auto schema_syncer = tmt.getSchemaSyncer(); - schema_syncer->syncSchemas(context); + try + { + schema_syncer->syncSchemas(context); + } + catch (Exception & e) + { + if (e.code() == ErrorCodes::FAIL_POINT_ERROR) + { + output(e.message()); + return; + } + else + { + throw; + } + } output("schemas refreshed"); } diff --git a/dbms/src/Storages/IManageableStorage.h b/dbms/src/Storages/IManageableStorage.h index e41d092ca87..ebf84c592e4 100644 --- a/dbms/src/Storages/IManageableStorage.h +++ b/dbms/src/Storages/IManageableStorage.h @@ -157,12 +157,15 @@ class IManageableStorage : public IStorage /// when `need_block` is true, it will try return a cached block corresponding to DecodingStorageSchemaSnapshotConstPtr, /// and `releaseDecodingBlock` need to be called when the block is free /// when `need_block` is false, it will just return an nullptr - virtual std::pair getSchemaSnapshotAndBlockForDecoding(bool /* need_block */) + /// This method must be called under the protection of table structure lock + virtual std::pair getSchemaSnapshotAndBlockForDecoding(const TableStructureLockHolder & /* table_structure_lock */, bool /* need_block */) { throw Exception("Method getDecodingSchemaSnapshot is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); }; - virtual void releaseDecodingBlock(Int64 /* schema_version */, BlockUPtr /* block */) + /// The `block_decoding_schema_version` is just an internal version for `DecodingStorageSchemaSnapshot`, + /// And it has no relation with the table schema version. + virtual void releaseDecodingBlock(Int64 /* block_decoding_schema_version */, BlockUPtr /* block */) { throw Exception("Method getDecodingSchemaSnapshot is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); } diff --git a/dbms/src/Storages/StorageDeltaMerge.cpp b/dbms/src/Storages/StorageDeltaMerge.cpp index fc73e28e23a..67d32c73a05 100644 --- a/dbms/src/Storages/StorageDeltaMerge.cpp +++ b/dbms/src/Storages/StorageDeltaMerge.cpp @@ -901,14 +901,16 @@ void StorageDeltaMerge::deleteRows(const Context & context, size_t delete_rows) LOG_FMT_ERROR(log, "Rows after delete range not match, expected: {}, got: {}", (total_rows - delete_rows), after_delete_rows); } -std::pair StorageDeltaMerge::getSchemaSnapshotAndBlockForDecoding(bool need_block) +std::pair StorageDeltaMerge::getSchemaSnapshotAndBlockForDecoding(const TableStructureLockHolder & table_structure_lock, bool need_block) { + (void)table_structure_lock; std::lock_guard lock{decode_schema_mutex}; - if (!decoding_schema_snapshot || decoding_schema_snapshot->schema_version < tidb_table_info.schema_version) + if (!decoding_schema_snapshot || decoding_schema_changed) { auto & store = getAndMaybeInitStore(); - decoding_schema_snapshot = std::make_shared(store->getStoreColumns(), tidb_table_info, store->getHandle()); + decoding_schema_snapshot = std::make_shared(store->getStoreColumns(), tidb_table_info, store->getHandle(), decoding_schema_version++); cache_blocks.clear(); + decoding_schema_changed = false; } if (need_block) @@ -930,10 +932,10 @@ std::pair StorageDeltaMerg } } -void StorageDeltaMerge::releaseDecodingBlock(Int64 schema_version, BlockUPtr block_ptr) +void StorageDeltaMerge::releaseDecodingBlock(Int64 block_decoding_schema_version, BlockUPtr block_ptr) { std::lock_guard lock{decode_schema_mutex}; - if (!decoding_schema_snapshot || schema_version < decoding_schema_snapshot->schema_version) + if (!decoding_schema_snapshot || block_decoding_schema_version < decoding_schema_snapshot->decoding_schema_version) return; if (cache_blocks.size() >= max_cached_blocks_num) return; @@ -1113,6 +1115,7 @@ try updateTableColumnInfo(); } } + decoding_schema_changed = true; SortDescription pk_desc = getPrimarySortDescription(); ColumnDefines store_columns = getStoreColumnDefines(); diff --git a/dbms/src/Storages/StorageDeltaMerge.h b/dbms/src/Storages/StorageDeltaMerge.h index e304c713b7b..79ee225d237 100644 --- a/dbms/src/Storages/StorageDeltaMerge.h +++ b/dbms/src/Storages/StorageDeltaMerge.h @@ -151,9 +151,9 @@ class StorageDeltaMerge size_t getRowKeyColumnSize() const override { return rowkey_column_size; } - std::pair getSchemaSnapshotAndBlockForDecoding(bool /* need_block */) override; + std::pair getSchemaSnapshotAndBlockForDecoding(const TableStructureLockHolder & table_structure_lock, bool /* need_block */) override; - void releaseDecodingBlock(Int64 schema_version, BlockUPtr block) override; + void releaseDecodingBlock(Int64 block_decoding_schema_version, BlockUPtr block) override; bool initStoreIfDataDirExist() override; @@ -238,6 +238,11 @@ class StorageDeltaMerge mutable std::mutex decode_schema_mutex; DecodingStorageSchemaSnapshotPtr decoding_schema_snapshot; + // The following two members must be used under the protection of table structure lock + bool decoding_schema_changed = false; + // internal version for `decoding_schema_snapshot` + Int64 decoding_schema_version = 1; + // avoid creating block every time when decoding row std::vector cache_blocks; // avoid creating too many cached blocks(the typical num should be less and equal than raft apply thread) diff --git a/dbms/src/Storages/Transaction/DecodingStorageSchemaSnapshot.h b/dbms/src/Storages/Transaction/DecodingStorageSchemaSnapshot.h index 6cedbe3f0c0..c636d9e60ab 100644 --- a/dbms/src/Storages/Transaction/DecodingStorageSchemaSnapshot.h +++ b/dbms/src/Storages/Transaction/DecodingStorageSchemaSnapshot.h @@ -67,13 +67,14 @@ struct DecodingStorageSchemaSnapshot bool pk_is_handle; bool is_common_handle; TMTPKType pk_type = TMTPKType::UNSPECIFIED; - Int64 schema_version = DEFAULT_UNSPECIFIED_SCHEMA_VERSION; + // an internal increasing version for `DecodingStorageSchemaSnapshot`, has no relation with the table schema version + Int64 decoding_schema_version; - DecodingStorageSchemaSnapshot(DM::ColumnDefinesPtr column_defines_, const TiDB::TableInfo & table_info_, const DM::ColumnDefine & original_handle_) + DecodingStorageSchemaSnapshot(DM::ColumnDefinesPtr column_defines_, const TiDB::TableInfo & table_info_, const DM::ColumnDefine & original_handle_, Int64 decoding_schema_version_) : column_defines{std::move(column_defines_)} , pk_is_handle{table_info_.pk_is_handle} , is_common_handle{table_info_.is_common_handle} - , schema_version{table_info_.schema_version} + , decoding_schema_version{decoding_schema_version_} { std::unordered_map column_lut; for (size_t i = 0; i < table_info_.columns.size(); i++) diff --git a/dbms/src/Storages/Transaction/PartitionStreams.cpp b/dbms/src/Storages/Transaction/PartitionStreams.cpp index ada792c80f7..4b2ca6c07a8 100644 --- a/dbms/src/Storages/Transaction/PartitionStreams.cpp +++ b/dbms/src/Storages/Transaction/PartitionStreams.cpp @@ -114,14 +114,14 @@ static void writeRegionDataToStorage( /// Read region data as block. Stopwatch watch; - Int64 block_schema_version = DEFAULT_UNSPECIFIED_SCHEMA_VERSION; + Int64 block_decoding_schema_version = -1; BlockUPtr block_ptr = nullptr; if (need_decode) { LOG_FMT_TRACE(log, "{} begin to decode table {}, region {}", FUNCTION_NAME, table_id, region->id()); DecodingStorageSchemaSnapshotConstPtr decoding_schema_snapshot; - std::tie(decoding_schema_snapshot, block_ptr) = storage->getSchemaSnapshotAndBlockForDecoding(true); - block_schema_version = decoding_schema_snapshot->schema_version; + std::tie(decoding_schema_snapshot, block_ptr) = storage->getSchemaSnapshotAndBlockForDecoding(lock, true); + block_decoding_schema_version = decoding_schema_snapshot->decoding_schema_version; auto reader = RegionBlockReader(decoding_schema_snapshot); if (!reader.read(*block_ptr, data_list_read, force_decode)) @@ -153,7 +153,7 @@ static void writeRegionDataToStorage( write_part_cost = watch.elapsedMilliseconds(); GET_METRIC(tiflash_raft_write_data_to_storage_duration_seconds, type_write).Observe(write_part_cost / 1000.0); if (need_decode) - storage->releaseDecodingBlock(block_schema_version, std::move(block_ptr)); + storage->releaseDecodingBlock(block_decoding_schema_version, std::move(block_ptr)); LOG_FMT_TRACE(log, "{}: table {}, region {}, cost [region decode {}, write part {}] ms", FUNCTION_NAME, table_id, region->id(), region_decode_cost, write_part_cost); return true; @@ -455,7 +455,7 @@ RegionPtrWithBlock::CachePtr GenRegionPreDecodeBlockData(const RegionPtr & regio } DecodingStorageSchemaSnapshotConstPtr decoding_schema_snapshot; - std::tie(decoding_schema_snapshot, std::ignore) = storage->getSchemaSnapshotAndBlockForDecoding(false); + std::tie(decoding_schema_snapshot, std::ignore) = storage->getSchemaSnapshotAndBlockForDecoding(lock, false); res_block = createBlockSortByColumnID(decoding_schema_snapshot); auto reader = RegionBlockReader(decoding_schema_snapshot); if (!reader.read(res_block, *data_list_read, force_decode)) @@ -508,7 +508,7 @@ AtomicGetStorageSchema(const RegionPtr & region, TMTContext & tmt) auto table_lock = storage->lockStructureForShare(getThreadName()); dm_storage = std::dynamic_pointer_cast(storage); // only dt storage engine support `getSchemaSnapshotAndBlockForDecoding`, other engine will throw exception - std::tie(schema_snapshot, std::ignore) = storage->getSchemaSnapshotAndBlockForDecoding(false); + std::tie(schema_snapshot, std::ignore) = storage->getSchemaSnapshotAndBlockForDecoding(table_lock, false); std::tie(std::ignore, drop_lock) = std::move(table_lock).release(); return true; }; diff --git a/dbms/src/Storages/Transaction/tests/RowCodecTestUtils.h b/dbms/src/Storages/Transaction/tests/RowCodecTestUtils.h index c28ea531afe..20b395a9952 100644 --- a/dbms/src/Storages/Transaction/tests/RowCodecTestUtils.h +++ b/dbms/src/Storages/Transaction/tests/RowCodecTestUtils.h @@ -285,11 +285,11 @@ inline DecodingStorageSchemaSnapshotConstPtr getDecodingStorageSchemaSnapshot(co if (handle_id != EXTRA_HANDLE_COLUMN_ID) { auto iter = std::find_if(store_columns.begin(), store_columns.end(), [&](const ColumnDefine & cd) { return cd.id == handle_id; }); - return std::make_shared(std::make_shared(store_columns), table_info, *iter); + return std::make_shared(std::make_shared(store_columns), table_info, *iter, /* decoding_schema_version_ */ 1); } else { - return std::make_shared(std::make_shared(store_columns), table_info, store_columns[0]); + return std::make_shared(std::make_shared(store_columns), table_info, store_columns[0], /* decoding_schema_version_ */ 1); } } diff --git a/dbms/src/TiDB/Schema/SchemaBuilder.cpp b/dbms/src/TiDB/Schema/SchemaBuilder.cpp index 64d118eec3e..99e540e6c95 100644 --- a/dbms/src/TiDB/Schema/SchemaBuilder.cpp +++ b/dbms/src/TiDB/Schema/SchemaBuilder.cpp @@ -61,6 +61,7 @@ extern const char exception_before_step_2_rename_in_exchange_partition[]; extern const char exception_after_step_2_in_exchange_partition[]; extern const char exception_before_step_3_rename_in_exchange_partition[]; extern const char exception_after_step_3_in_exchange_partition[]; +extern const char exception_between_schema_change_in_the_same_diff[]; } // namespace FailPoints bool isReservedDatabase(Context & context, const String & database_name) @@ -336,6 +337,7 @@ void SchemaBuilder::applyAlterPhysicalTable(DBInfoPtr db_inf FmtBuffer fmt_buf; fmt_buf.fmtAppend("Detected schema changes: {}: ", name_mapper.debugCanonicalName(*db_info, *table_info)); for (const auto & schema_change : schema_changes) + { for (const auto & command : schema_change.first) { if (command.type == AlterCommand::ADD_COLUMN) @@ -347,6 +349,7 @@ void SchemaBuilder::applyAlterPhysicalTable(DBInfoPtr db_inf else if (command.type == AlterCommand::RENAME_COLUMN) fmt_buf.fmtAppend("RENAME COLUMN from {} to {}, ", command.column_name, command.new_column_name); } + } return fmt_buf.toString(); }; LOG_DEBUG(log, log_str()); @@ -355,8 +358,16 @@ void SchemaBuilder::applyAlterPhysicalTable(DBInfoPtr db_inf // Using original table info with updated columns instead of using new_table_info directly, // so that other changes (RENAME commands) won't be saved. // Also, updating schema_version as altering column is structural. - for (const auto & schema_change : schema_changes) + for (size_t i = 0; i < schema_changes.size(); i++) { + if (i > 0) + { + /// If there are multiple schema change in the same diff, + /// the table schema version will be set to the latest schema version after the first schema change is applied. + /// Throw exception in the middle of the schema change to mock the case that there is a race between data decoding and applying different schema change. + FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::exception_between_schema_change_in_the_same_diff); + } + const auto & schema_change = schema_changes[i]; /// Update column infos by applying schema change in this step. schema_change.second(orig_table_info); /// Update schema version aggressively for the sake of correctness. diff --git a/dbms/src/TiDB/Schema/TiDBSchemaSyncer.h b/dbms/src/TiDB/Schema/TiDBSchemaSyncer.h index b682abf1af4..8aab2b3302e 100644 --- a/dbms/src/TiDB/Schema/TiDBSchemaSyncer.h +++ b/dbms/src/TiDB/Schema/TiDBSchemaSyncer.h @@ -28,6 +28,11 @@ namespace DB { +namespace ErrorCodes +{ +extern const int FAIL_POINT_ERROR; +}; + template struct TiDBSchemaSyncer : public SchemaSyncer { @@ -177,6 +182,10 @@ struct TiDBSchemaSyncer : public SchemaSyncer } catch (Exception & e) { + if (e.code() == ErrorCodes::FAIL_POINT_ERROR) + { + throw; + } GET_METRIC(tiflash_schema_apply_count, type_failed).Increment(); LOG_FMT_WARNING(log, "apply diff meets exception : {} \n stack is {}", e.displayText(), e.getStackTrace().toString()); return false; diff --git a/tests/fullstack-test2/ddl/alter_column_when_pk_is_handle.test b/tests/fullstack-test2/ddl/alter_column_when_pk_is_handle.test index ca92828e6cf..df0aa13823a 100644 --- a/tests/fullstack-test2/ddl/alter_column_when_pk_is_handle.test +++ b/tests/fullstack-test2/ddl/alter_column_when_pk_is_handle.test @@ -37,9 +37,25 @@ mysql> set session tidb_isolation_read_engines='tiflash'; select * from test.t | 1 | world | 0.00 | 2 | NULL | +---+-------+------+------+------+ -# Need to apply a lossy type change to reorganize data. issue#3714 +=> DBGInvoke __enable_schema_sync_service('false') + +>> DBGInvoke __enable_fail_point(exception_between_schema_change_in_the_same_diff) + +# stop decoding data +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# Need to apply a lossy type change to reorganize data. issue#3714 mysql> alter table test.t modify c decimal(6,3) +# refresh schema and hit the `exception_between_schema_change_in_the_same_diff` failpoint +>> DBGInvoke __refresh_schemas() + +>> DBGInvoke __disable_fail_point(exception_between_schema_change_in_the_same_diff) + +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke __enable_schema_sync_service('true') + mysql> set session tidb_isolation_read_engines='tiflash'; select * from test.t +---+-------+-------+------+------+ | a | b | c | d | e | From 20d2198e5c2a191154431272f907f8758a8bd17e Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Thu, 2 Jun 2022 18:10:28 +0800 Subject: [PATCH 084/127] *: fix invalid fmt format string in CreatingSetsBlockInputStream.cpp (#5053) ref pingcap/tiflash#5052 --- dbms/src/DataStreams/CreatingSetsBlockInputStream.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dbms/src/DataStreams/CreatingSetsBlockInputStream.cpp b/dbms/src/DataStreams/CreatingSetsBlockInputStream.cpp index 47e7a56aa35..22545327edd 100644 --- a/dbms/src/DataStreams/CreatingSetsBlockInputStream.cpp +++ b/dbms/src/DataStreams/CreatingSetsBlockInputStream.cpp @@ -228,7 +228,7 @@ void CreatingSetsBlockInputStream::createOne(SubqueryForSet & subquery) if (done_with_set && done_with_join && done_with_table) { - if (IProfilingBlockInputStream * profiling_in = dynamic_cast(&*subquery.source)) + if (auto * profiling_in = dynamic_cast(&*subquery.source)) profiling_in->cancel(false); break; @@ -248,7 +248,7 @@ void CreatingSetsBlockInputStream::createOne(SubqueryForSet & subquery) watch.stop(); size_t head_rows = 0; - if (IProfilingBlockInputStream * profiling_in = dynamic_cast(&*subquery.source)) + if (auto * profiling_in = dynamic_cast(&*subquery.source)) { const BlockStreamProfileInfo & profile_info = profiling_in->getProfileInfo(); @@ -272,7 +272,7 @@ void CreatingSetsBlockInputStream::createOne(SubqueryForSet & subquery) if (subquery.table) msg.fmtAppend("Table with {} rows. ", head_rows); - msg.fmtAppend("In {.3f} sec. ", watch.elapsedSeconds()); + msg.fmtAppend("In {:.3f} sec. ", watch.elapsedSeconds()); msg.fmtAppend("using {} threads.", subquery.join ? subquery.join->getBuildConcurrency() : 1); return msg.toString(); }; From 677ad751e33a083c0311ee02114078b53f5f5e43 Mon Sep 17 00:00:00 2001 From: lidezhu <47731263+lidezhu@users.noreply.github.com> Date: Fri, 3 Jun 2022 09:16:27 +0800 Subject: [PATCH 085/127] increase bg gc check interval (#5056) close pingcap/tiflash#5057 --- dbms/src/Interpreters/Settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbms/src/Interpreters/Settings.h b/dbms/src/Interpreters/Settings.h index 87d007c101f..9361e0525d2 100644 --- a/dbms/src/Interpreters/Settings.h +++ b/dbms/src/Interpreters/Settings.h @@ -272,7 +272,7 @@ struct Settings M(SettingUInt64, dt_segment_delta_small_column_file_size, 8388608, "Determine whether a column file in delta is small or not. 8MB by default.") \ M(SettingUInt64, dt_segment_stable_pack_rows, DEFAULT_MERGE_BLOCK_SIZE, "Expected stable pack rows in DeltaTree Engine.") \ M(SettingFloat, dt_segment_wait_duration_factor, 1, "The factor of wait duration in a write stall.") \ - M(SettingUInt64, dt_bg_gc_check_interval, 5, "Background gc thread check interval, the unit is second.") \ + M(SettingUInt64, dt_bg_gc_check_interval, 60, "Background gc thread check interval, the unit is second.") \ M(SettingInt64, dt_bg_gc_max_segments_to_check_every_round, 100, "Max segments to check in every gc round, value less than or equal to 0 means gc no segments.") \ M(SettingFloat, dt_bg_gc_ratio_threhold_to_trigger_gc, 1.2, "Trigger segment's gc when the ratio of invalid version exceed this threhold. Values smaller than or equal to 1.0 means gc all " \ "segments") \ From b49a78780156bcc4769a9017d3bb0351c43e7d8c Mon Sep 17 00:00:00 2001 From: JaySon Date: Fri, 3 Jun 2022 16:12:28 +0800 Subject: [PATCH 086/127] PageStorage: Fix pages are not deleted under some cases (#5069) close pingcap/tiflash#5054 --- dbms/src/Storages/DeltaMerge/StoragePool.cpp | 18 +-- dbms/src/Storages/Page/Page.h | 3 +- dbms/src/Storages/Page/V2/PageEntries.h | 26 ++-- .../V3/tests/gtest_page_storage_mix_mode.cpp | 126 ++++++++++++++++++ 4 files changed, 149 insertions(+), 24 deletions(-) diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.cpp b/dbms/src/Storages/DeltaMerge/StoragePool.cpp index fa765cd9b1d..752898f9c75 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.cpp +++ b/dbms/src/Storages/DeltaMerge/StoragePool.cpp @@ -515,21 +515,9 @@ void StoragePool::dataRegisterExternalPagesCallbacks(const ExternalPageCallbacks } case PageStorageRunMode::MIX_MODE: { - // When PageStorage run as Mix Mode. - // We need both get alive pages from V2 and V3 which will feedback for the DM. - // But V2 and V3 won't GC in the same time. So V3 need proxy V2 external pages callback. - // When V3 GC happend, scan the external pages from V3, in remover will scanner all of external pages from V2. - ExternalPageCallbacks mix_mode_callbacks; - - mix_mode_callbacks.scanner = callbacks.scanner; - mix_mode_callbacks.remover = [this, callbacks](const ExternalPageCallbacks::PathAndIdsVec & path_and_ids_vec, const std::set & valid_ids) { - // ns_id won't used on V2 - auto v2_valid_page_ids = data_storage_v2->getAliveExternalPageIds(ns_id); - v2_valid_page_ids.insert(valid_ids.begin(), valid_ids.end()); - callbacks.remover(path_and_ids_vec, v2_valid_page_ids); - }; - mix_mode_callbacks.ns_id = ns_id; - data_storage_v3->registerExternalPagesCallbacks(mix_mode_callbacks); + // We have transformed all pages from V2 to V3 in `restore`, so + // only need to register callbacks for V3. + data_storage_v3->registerExternalPagesCallbacks(callbacks); break; } default: diff --git a/dbms/src/Storages/Page/Page.h b/dbms/src/Storages/Page/Page.h index b54b25033dd..5328490e5ad 100644 --- a/dbms/src/Storages/Page/Page.h +++ b/dbms/src/Storages/Page/Page.h @@ -128,12 +128,13 @@ struct PageEntry String toDebugString() const { - return fmt::format("PageEntry{{file: {}, offset: 0x{:X}, size: {}, checksum: 0x{:X}, tag: {}, field_offsets_size: {}}}", + return fmt::format("PageEntry{{file: {}, offset: 0x{:X}, size: {}, checksum: 0x{:X}, tag: {}, ref: {}, field_offsets_size: {}}}", file_id, offset, size, checksum, tag, + ref, field_offsets.size()); } diff --git a/dbms/src/Storages/Page/V2/PageEntries.h b/dbms/src/Storages/Page/V2/PageEntries.h index c99e0dade6b..0a0504b0cb5 100644 --- a/dbms/src/Storages/Page/V2/PageEntries.h +++ b/dbms/src/Storages/Page/V2/PageEntries.h @@ -252,7 +252,7 @@ class PageEntriesMixin protected: template - void decreasePageRef(PageId page_id); + void decreasePageRef(PageId page_id, bool keep_tombstone); void copyEntries(const PageEntriesMixin & rhs) { @@ -370,8 +370,10 @@ void PageEntriesMixin::del(PageId page_id) const size_t num_erase = page_ref.erase(page_id); if (num_erase > 0) { - // decrease origin page's ref counting - decreasePageRef(normal_page_id); + // decrease origin page's ref counting, this method can + // only called by base, so we should remove the entry if + // the ref count down to zero + decreasePageRef(normal_page_id, /*keep_tombstone=*/false); } } @@ -392,7 +394,9 @@ void PageEntriesMixin::ref(const PageId ref_id, const PageId page_id) // if RefPage{ref-id} -> Page{normal_page_id} already exists, just ignore if (ori_ref->second == normal_page_id) return; - decreasePageRef(ori_ref->second); + // this method can only called by base, so we should remove the entry if + // the ref count down to zero + decreasePageRef(ori_ref->second, /*keep_tombstone=*/false); } // build ref page_ref[ref_id] = normal_page_id; @@ -408,7 +412,7 @@ void PageEntriesMixin::ref(const PageId ref_id, const PageId page_id) template template -void PageEntriesMixin::decreasePageRef(const PageId page_id) +void PageEntriesMixin::decreasePageRef(const PageId page_id, bool keep_tombstone) { auto iter = normal_pages.find(page_id); if constexpr (must_exist) @@ -421,8 +425,11 @@ void PageEntriesMixin::decreasePageRef(const PageId page_id) if (iter != normal_pages.end()) { auto & entry = iter->second; - entry.ref -= 1; - if (entry.ref == 0) + if (entry.ref > 0) + { + entry.ref -= 1; + } + if (!keep_tombstone && entry.ref == 0) { normal_pages.erase(iter); } @@ -620,7 +627,10 @@ class PageEntriesForDelta : public PageEntriesMixin { ref_deletions.insert(page_id); } - decreasePageRef(page_id); + // If this is the base version, we should remove the entry if + // the ref count down to zero. Otherwise it is the delta version + // we should keep a tombstone. + decreasePageRef(page_id, /*keep_tombstone=*/!this->isBase()); } for (auto it : rhs.page_ref) { diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp index 5517539b898..078daa3e5b4 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include #include @@ -19,6 +20,8 @@ #include #include #include +#include +#include #include namespace DB @@ -879,6 +882,129 @@ try } CATCH + +TEST_F(PageStorageMixedTest, RefV2External2) +try +{ + auto logger = DB::Logger::get("PageStorageMixedTest"); + { + WriteBatch batch; + batch.putExternal(100, 0); + batch.putRefPage(101, 100); + batch.delPage(100); + batch.putExternal(102, 0); + page_writer_v2->write(std::move(batch), nullptr); + } + + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + { + WriteBatch batch; + batch.putExternal(100, 0); + batch.putRefPage(101, 100); + batch.delPage(100); + batch.putExternal(102, 0); + page_writer_mix->writeIntoV3(std::move(batch), nullptr); + } + { + auto snap = storage_pool_mix->log_storage_v2->getSnapshot("zzz"); // must hold + // after transform to v3, delete these from v2 + WriteBatch batch; + batch.delPage(100); + batch.delPage(101); + batch.delPage(102); + page_writer_mix->writeIntoV2(std::move(batch), nullptr); + } + + { + LOG_FMT_INFO(logger, "first check alive id in v2"); + auto alive_dt_ids_in_v2 = storage_pool_mix->log_storage_v2->getAliveExternalPageIds(TEST_NAMESPACE_ID); + EXPECT_EQ(alive_dt_ids_in_v2.size(), 0); + + storage_pool_mix->log_storage_v3->gc(false, nullptr, nullptr); + auto alive_dt_ids_in_v3 = storage_pool_mix->log_storage_v3->getAliveExternalPageIds(TEST_NAMESPACE_ID); + ASSERT_EQ(alive_dt_ids_in_v3.size(), 2); + auto iter = alive_dt_ids_in_v3.begin(); + EXPECT_EQ(*iter, 100); + iter++; + EXPECT_EQ(*iter, 102); + } + + { + LOG_FMT_INFO(logger, "remove 100, create 105"); + StorageSnapshot snap(*storage_pool_mix, nullptr, "xxx", true); // must hold and write + // write delete again + WriteBatch batch; + batch.delPage(100); + batch.putExternal(105, 0); + page_writer_mix->write(std::move(batch), nullptr); + LOG_FMT_INFO(logger, "done"); + } + { + LOG_FMT_INFO(logger, "remove 101, create 106"); + StorageSnapshot snap(*storage_pool_mix, nullptr, "xxx", true); // must hold and write + // write delete again + WriteBatch batch; + batch.delPage(101); + batch.putExternal(106, 0); + page_writer_mix->write(std::move(batch), nullptr); + LOG_FMT_INFO(logger, "done"); + } + { + LOG_FMT_INFO(logger, "remove 102, create 107"); + StorageSnapshot snap(*storage_pool_mix, nullptr, "xxx", true); // must hold and write + // write delete again + WriteBatch batch; + batch.delPage(102); + batch.putExternal(107, 0); + page_writer_mix->write(std::move(batch), nullptr); + LOG_FMT_INFO(logger, "done"); + } + + { + LOG_FMT_INFO(logger, "second check alive id in v2"); + auto alive_dt_ids_in_v2 = storage_pool_mix->log_storage_v2->getAliveExternalPageIds(TEST_NAMESPACE_ID); + EXPECT_EQ(alive_dt_ids_in_v2.size(), 0) << fmt::format("{}", alive_dt_ids_in_v2); + + storage_pool_mix->log_storage_v3->gc(false, nullptr, nullptr); + auto alive_dt_ids_in_v3 = storage_pool_mix->log_storage_v3->getAliveExternalPageIds(TEST_NAMESPACE_ID); + ASSERT_EQ(alive_dt_ids_in_v3.size(), 3) << fmt::format("{}", alive_dt_ids_in_v3); + auto iter = alive_dt_ids_in_v3.begin(); + EXPECT_EQ(*iter, 105); + iter++; + EXPECT_EQ(*iter, 106); + iter++; + EXPECT_EQ(*iter, 107); + } + { + LOG_FMT_INFO(logger, "third check alive id in v2"); + auto alive_dt_ids_in_v2 = storage_pool_mix->log_storage_v2->getAliveExternalPageIds(TEST_NAMESPACE_ID); + EXPECT_EQ(alive_dt_ids_in_v2.size(), 0) << fmt::format("{}", alive_dt_ids_in_v2); + + storage_pool_mix->log_storage_v3->gc(false, nullptr, nullptr); + auto alive_dt_ids_in_v3 = storage_pool_mix->log_storage_v3->getAliveExternalPageIds(TEST_NAMESPACE_ID); + ASSERT_EQ(alive_dt_ids_in_v3.size(), 3) << fmt::format("{}", alive_dt_ids_in_v3); + auto iter = alive_dt_ids_in_v3.begin(); + EXPECT_EQ(*iter, 105); + iter++; + EXPECT_EQ(*iter, 106); + iter++; + EXPECT_EQ(*iter, 107); + } + + { + // cleanup v3 + WriteBatch batch; + batch.delPage(100); + batch.delPage(101); + batch.delPage(102); + batch.delPage(105); + batch.delPage(106); + batch.delPage(107); + page_writer_mix->write(std::move(batch), nullptr); + } +} +CATCH + TEST_F(PageStorageMixedTest, ReadWithSnapshotAfterMergeDelta) try { From a66c082deb2bdd5bcee47de4f626b4abac314dd8 Mon Sep 17 00:00:00 2001 From: lidezhu <47731263+lidezhu@users.noreply.github.com> Date: Fri, 3 Jun 2022 19:04:27 +0800 Subject: [PATCH 087/127] Fix unstable drop table unit test (#5059) close pingcap/tiflash#5060 --- .../tests/gtest_dm_storage_delta_merge.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp index 1cb735a2b65..2dc65c256df 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp @@ -717,14 +717,27 @@ CATCH TEST(StorageDeltaMergeTest, RestoreAfterClearData) try { - Context ctx = DMTestEnv::getContext(); - auto & settings = ctx.getSettingsRef(); + auto & global_settings = ::DB::tests::TiFlashTestEnv::getGlobalContext().getSettingsRef(); + // store the old value to restore global_context settings after the test finish to avoid influence other tests + auto old_global_settings = global_settings; + SCOPE_EXIT({ + global_settings = old_global_settings; + }); + // change the settings to make it more easy to trigger splitting segments + Settings settings; settings.dt_segment_limit_rows = 11; settings.dt_segment_limit_size = 20; settings.dt_segment_delta_limit_rows = 7; settings.dt_segment_delta_limit_size = 20; settings.dt_segment_force_split_size = 100; settings.dt_segment_delta_cache_limit_size = 20; + + // we need change the settings in both the ctx we get just below and the global_context above. + // because when processing write request, `DeltaMergeStore` will call `checkSegmentUpdate` with the context we just get below. + // and when initialize `DeltaMergeStore`, it will call `checkSegmentUpdate` with the global_context above. + // so we need to make the settings in these two contexts consistent. + global_settings = settings; + Context ctx = DMTestEnv::getContext(settings); std::shared_ptr storage; DataTypes data_types; Names column_names; From e3a44124f17482bab9039f1b60fc275fe5acfbf2 Mon Sep 17 00:00:00 2001 From: JaySon Date: Mon, 6 Jun 2022 18:14:29 +0800 Subject: [PATCH 088/127] Fix broken ut SegmentDeletionRelevantPlaceTest (#4607) close pingcap/tiflash#4606 --- .../Management/tests/gtest_manual_compact.cpp | 2 +- dbms/src/Interpreters/IDAsPathUpgrader.cpp | 1 + .../Storages/DeltaMerge/tests/CMakeLists.txt | 2 +- .../tests/{dm_basic_include.h => DMTestEnv.h} | 0 .../DeltaMerge/tests/MultiSegmentTestUtil.h | 5 +- .../tests/bank/DeltaMergeStoreProxy.h | 2 +- .../Storages/DeltaMerge/tests/bank/main.cpp | 2 +- .../DeltaMerge/tests/gtest_convert_column.cpp | 3 +- .../DeltaMerge/tests/gtest_data_streams.cpp | 2 +- .../tests/gtest_dm_delta_merge_store.cpp | 5 +- .../tests/gtest_dm_delta_value_space.cpp | 3 +- .../DeltaMerge/tests/gtest_dm_file.cpp | 3 +- .../tests/gtest_dm_minmax_index.cpp | 4 +- .../DeltaMerge/tests/gtest_dm_segment.cpp | 215 ++++++++++-------- .../tests/gtest_dm_segment_common_handle.cpp | 3 +- .../tests/gtest_dm_storage_delta_merge.cpp | 4 +- .../DeltaMerge/tests/gtest_dm_utils.cpp | 3 +- .../DeltaMerge/tests/gtest_version_filter.cpp | 2 +- .../DeltaMerge/tests/stress/DMStressProxy.h | 2 +- 19 files changed, 138 insertions(+), 125 deletions(-) rename dbms/src/Storages/DeltaMerge/tests/{dm_basic_include.h => DMTestEnv.h} (100%) diff --git a/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp b/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp index 8ec3eb54406..4527892e353 100644 --- a/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp +++ b/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp @@ -17,8 +17,8 @@ #include #include #include +#include #include -#include #include #include #include diff --git a/dbms/src/Interpreters/IDAsPathUpgrader.cpp b/dbms/src/Interpreters/IDAsPathUpgrader.cpp index 8c807b537e9..9aa3dcb8dd0 100644 --- a/dbms/src/Interpreters/IDAsPathUpgrader.cpp +++ b/dbms/src/Interpreters/IDAsPathUpgrader.cpp @@ -487,6 +487,7 @@ bool IDAsPathUpgrader::needUpgrade() if (db_info.engine != "TiFlash") { has_old_db_engine = true; + LOG_FMT_INFO(log, "Find old style of database engine, doing upgrade [path={}] [engine={}]", database_metadata_file, db_info.engine); } } diff --git a/dbms/src/Storages/DeltaMerge/tests/CMakeLists.txt b/dbms/src/Storages/DeltaMerge/tests/CMakeLists.txt index 8d5854ffb5d..fd02bcebd2f 100644 --- a/dbms/src/Storages/DeltaMerge/tests/CMakeLists.txt +++ b/dbms/src/Storages/DeltaMerge/tests/CMakeLists.txt @@ -21,7 +21,7 @@ macro(grep_gtest_sources BASE_DIR DST_VAR) endmacro() # attach all dm gtest sources grep_gtest_sources(${TiFlash_SOURCE_DIR}/dbms/src/Storages/DeltaMerge/tests dm_gtest_sources) -add_executable(gtests_dm EXCLUDE_FROM_ALL ${dm_gtest_sources} dm_basic_include.h) +add_executable(gtests_dm EXCLUDE_FROM_ALL ${dm_gtest_sources} DMTestEnv.h) target_link_libraries(gtests_dm gtest_main dbms clickhouse_functions) add_check(gtests_dm) diff --git a/dbms/src/Storages/DeltaMerge/tests/dm_basic_include.h b/dbms/src/Storages/DeltaMerge/tests/DMTestEnv.h similarity index 100% rename from dbms/src/Storages/DeltaMerge/tests/dm_basic_include.h rename to dbms/src/Storages/DeltaMerge/tests/DMTestEnv.h diff --git a/dbms/src/Storages/DeltaMerge/tests/MultiSegmentTestUtil.h b/dbms/src/Storages/DeltaMerge/tests/MultiSegmentTestUtil.h index bc6b7d5c3e6..7c5b0b2416d 100644 --- a/dbms/src/Storages/DeltaMerge/tests/MultiSegmentTestUtil.h +++ b/dbms/src/Storages/DeltaMerge/tests/MultiSegmentTestUtil.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -30,7 +31,6 @@ #include #include -#include "dm_basic_include.h" namespace DB { @@ -43,7 +43,6 @@ namespace DM { namespace tests { - /// Helper class to test with multiple segments. /// You can call `prepareSegments` to prepare multiple segments. After that, /// you can use `verifyExpectedRowsForAllSegments` to verify the expectation for each segment. @@ -157,4 +156,4 @@ class MultiSegmentTestUtil : private boost::noncopyable } // namespace tests } // namespace DM -} // namespace DB \ No newline at end of file +} // namespace DB diff --git a/dbms/src/Storages/DeltaMerge/tests/bank/DeltaMergeStoreProxy.h b/dbms/src/Storages/DeltaMerge/tests/bank/DeltaMergeStoreProxy.h index 99264c77dc6..8edbbbc55b4 100644 --- a/dbms/src/Storages/DeltaMerge/tests/bank/DeltaMergeStoreProxy.h +++ b/dbms/src/Storages/DeltaMerge/tests/bank/DeltaMergeStoreProxy.h @@ -18,9 +18,9 @@ #include #include #include +#include #include #include -#include #include #include diff --git a/dbms/src/Storages/DeltaMerge/tests/bank/main.cpp b/dbms/src/Storages/DeltaMerge/tests/bank/main.cpp index 115e170c48b..b90ad132e25 100644 --- a/dbms/src/Storages/DeltaMerge/tests/bank/main.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/bank/main.cpp @@ -17,8 +17,8 @@ #include #include #include +#include #include -#include #include #include diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_convert_column.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_convert_column.cpp index 928a256349b..efc67de611e 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_convert_column.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_convert_column.cpp @@ -18,10 +18,9 @@ #include #include #include +#include #include -#include "dm_basic_include.h" - namespace DB { namespace DM diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_data_streams.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_data_streams.cpp index f0c2f49c30b..00f31bc97e7 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_data_streams.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_data_streams.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include namespace DB { diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp index 35e6c3d00c6..e934f7a2049 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -36,9 +38,6 @@ #include #include -#include "MultiSegmentTestUtil.h" -#include "dm_basic_include.h" - namespace DB { namespace FailPoints diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_value_space.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_value_space.cpp index 2b64fd90c09..40c399353b6 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_value_space.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_value_space.cpp @@ -18,14 +18,13 @@ #include #include #include +#include #include #include #include #include -#include "dm_basic_include.h" - namespace CurrentMetrics { extern const Metric DT_SnapshotOfRead; diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_file.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_file.cpp index dfd4419fe38..23062f4ffdf 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_file.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_file.cpp @@ -20,13 +20,12 @@ #include #include #include +#include #include #include #include -#include "dm_basic_include.h" - namespace DB { namespace FailPoints diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp index 31fd99faf01..96c0070b73b 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include @@ -41,7 +41,7 @@ static const String DEFAULT_COL_NAME = "2020-09-26"; class DMMinMaxIndexTest : public ::testing::Test { public: - DMMinMaxIndexTest() {} + DMMinMaxIndexTest() = default; protected: static void SetUpTestCase() {} diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment.cpp index 5726cfa132d..deec5646d33 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment.cpp @@ -18,16 +18,17 @@ #include #include #include +#include +#include #include #include #include #include +#include #include #include -#include "dm_basic_include.h" - namespace CurrentMetrics { extern const Metric DT_SnapshotOfRead; @@ -50,12 +51,10 @@ extern DMFilePtr writeIntoNewDMFile(DMContext & dm_context, // DMFileBlockOutputStream::Flags flags); namespace tests { -class Segment_test : public DB::base::TiFlashStorageTestBasic +class SegmentTest : public DB::base::TiFlashStorageTestBasic { public: - Segment_test() - : storage_pool() - {} + SegmentTest() = default; public: static void SetUpTestCase() {} @@ -63,7 +62,7 @@ class Segment_test : public DB::base::TiFlashStorageTestBasic void SetUp() override { TiFlashStorageTestBasic::SetUp(); - table_columns_ = std::make_shared(); + table_columns = std::make_shared(); segment = reload(); ASSERT_EQ(segment->segmentId(), DELTA_MERGE_FIRST_SEGMENT_ID); @@ -79,43 +78,43 @@ class Segment_test : public DB::base::TiFlashStorageTestBasic ColumnDefinesPtr cols = (!pre_define_columns) ? DMTestEnv::getDefaultColumns() : pre_define_columns; setColumns(cols); - return Segment::newSegment(*dm_context_, table_columns_, RowKeyRange::newAll(false, 1), storage_pool->newMetaPageId(), 0); + return Segment::newSegment(*dm_context, table_columns, RowKeyRange::newAll(false, 1), storage_pool->newMetaPageId(), 0); } // setColumns should update dm_context at the same time void setColumns(const ColumnDefinesPtr & columns) { - *table_columns_ = *columns; + *table_columns = *columns; - dm_context_ = std::make_unique(*db_context, - *storage_path_pool, - *storage_pool, - 0, - /*min_version_*/ 0, - settings.not_compress_columns, - false, - 1, - db_context->getSettingsRef()); + dm_context = std::make_unique(*db_context, + *storage_path_pool, + *storage_pool, + 0, + /*min_version_*/ 0, + settings.not_compress_columns, + false, + 1, + db_context->getSettingsRef()); } - const ColumnDefinesPtr & tableColumns() const { return table_columns_; } + const ColumnDefinesPtr & tableColumns() const { return table_columns; } - DMContext & dmContext() { return *dm_context_; } + DMContext & dmContext() { return *dm_context; } protected: /// all these var lives as ref in dm_context std::unique_ptr storage_path_pool; std::unique_ptr storage_pool; - ColumnDefinesPtr table_columns_; + ColumnDefinesPtr table_columns; DM::DeltaMergeStore::Settings settings; /// dm_context - std::unique_ptr dm_context_; + std::unique_ptr dm_context; // the segment we are going to test SegmentPtr segment; }; -TEST_F(Segment_test, WriteRead) +TEST_F(SegmentTest, WriteRead) try { const size_t num_rows_write = 100; @@ -124,11 +123,11 @@ try // write to segment segment->write(dmContext(), block); // estimate segment - auto estimatedRows = segment->getEstimatedRows(); - ASSERT_EQ(estimatedRows, block.rows()); + auto estimated_rows = segment->getEstimatedRows(); + ASSERT_EQ(estimated_rows, block.rows()); - auto estimatedBytes = segment->getEstimatedBytes(); - ASSERT_EQ(estimatedBytes, block.bytes()); + auto estimated_bytes = segment->getEstimatedBytes(); + ASSERT_EQ(estimated_bytes, block.bytes()); } { @@ -212,7 +211,7 @@ try } CATCH -TEST_F(Segment_test, WriteRead2) +TEST_F(SegmentTest, WriteRead2) try { const size_t num_rows_write = dmContext().stable_pack_rows; @@ -249,7 +248,7 @@ try } CATCH -TEST_F(Segment_test, WriteReadMultiRange) +TEST_F(SegmentTest, WriteReadMultiRange) try { const size_t num_rows_write = 100; @@ -258,11 +257,11 @@ try // write to segment segment->write(dmContext(), block); // estimate segment - auto estimatedRows = segment->getEstimatedRows(); - ASSERT_EQ(estimatedRows, block.rows()); + auto estimated_rows = segment->getEstimatedRows(); + ASSERT_EQ(estimated_rows, block.rows()); - auto estimatedBytes = segment->getEstimatedBytes(); - ASSERT_EQ(estimatedBytes, block.bytes()); + auto estimated_bytes = segment->getEstimatedBytes(); + ASSERT_EQ(estimated_bytes, block.bytes()); } { @@ -356,7 +355,7 @@ try } CATCH -TEST_F(Segment_test, ReadWithMoreAdvacedDeltaIndex) +TEST_F(SegmentTest, ReadWithMoreAdvacedDeltaIndex) try { // Test the case that reading rows with an advance DeltaIndex @@ -421,27 +420,21 @@ try } CATCH -class SegmentDeletionRelevantPlace_test - : public Segment_test +class SegmentDeletionRelevantPlaceTest + : public SegmentTest , public testing::WithParamInterface { - DB::Settings getSettings() - { - DB::Settings settings; - auto enable_relevant_place = GetParam(); - - if (enable_relevant_place) - settings.set("dt_enable_relevant_place", "1"); - else - settings.set("dt_enable_relevant_place", "0"); - return settings; - } }; -TEST_P(SegmentDeletionRelevantPlace_test, ShareDelteRangeIndex) +TEST_P(SegmentDeletionRelevantPlaceTest, ShareDelteRangeIndex) try { + Settings my_settings; + const auto enable_relevant_place = GetParam(); + my_settings.dt_enable_relevant_place = enable_relevant_place; + this->reload({}, std::move(my_settings)); + const size_t num_rows_write = 300; { // write to segment @@ -467,27 +460,54 @@ try { HandleRange remove(100, 200); - segment->write(dmContext(), {RowKeyRange::fromHandleRange(remove)}); + segment->write(dmContext(), /*delete_range*/ {RowKeyRange::fromHandleRange(remove)}); } // The first call of get_rows below will place the DeleteRange into delta index. + // If relevant place is enabled, the placed deletes in delta-tree-index is not + // pushed forward since we do not fully apply the delete range [100, 200). auto rows1 = get_rows(RowKeyRange::fromHandleRange(HandleRange(0, 150))); + { + auto delta = segment->getDelta(); + auto placed_rows = delta->getPlacedDeltaRows(); + auto placed_deletes = delta->getPlacedDeltaDeletes(); + ASSERT_EQ(placed_rows, num_rows_write); + EXPECT_EQ(placed_deletes, enable_relevant_place ? 0 : 1); + } auto rows2 = get_rows(RowKeyRange::fromHandleRange(HandleRange(150, 300))); + { + auto delta = segment->getDelta(); + auto placed_rows = delta->getPlacedDeltaRows(); + auto placed_deletes = delta->getPlacedDeltaDeletes(); + ASSERT_EQ(placed_rows, num_rows_write); + EXPECT_EQ(placed_deletes, enable_relevant_place ? 0 : 1); + } + // Query with range [0, 300) will push the placed deletes forward no matter + // relevant place is enable or not. + auto rows3 = get_rows(RowKeyRange::fromHandleRange(HandleRange(0, 300))); + { + auto delta = segment->getDelta(); + auto placed_rows = delta->getPlacedDeltaRows(); + auto placed_deletes = delta->getPlacedDeltaDeletes(); + ASSERT_EQ(placed_rows, num_rows_write); + EXPECT_EQ(placed_deletes, 1); + } - ASSERT_EQ(rows1, (size_t)100); - ASSERT_EQ(rows2, (size_t)100); + ASSERT_EQ(rows1, 100); + ASSERT_EQ(rows2, 100); + ASSERT_EQ(rows3, 200); } CATCH -INSTANTIATE_TEST_CASE_P(WhetherEnableRelevantPlace, SegmentDeletionRelevantPlace_test, testing::Values(true, false)); +INSTANTIATE_TEST_CASE_P(WhetherEnableRelevantPlace, SegmentDeletionRelevantPlaceTest, testing::Values(true, false)); -class SegmentDeletion_test - : public Segment_test +class SegmentDeletionTest + : public SegmentTest , public testing::WithParamInterface> { }; -TEST_P(SegmentDeletion_test, DeleteDataInDelta) +TEST_P(SegmentDeletionTest, DeleteDataInDelta) try { const size_t num_rows_write = 100; @@ -565,7 +585,7 @@ try } CATCH -TEST_P(SegmentDeletion_test, DeleteDataInStable) +TEST_P(SegmentDeletionTest, DeleteDataInStable) try { const size_t num_rows_write = 100; @@ -651,7 +671,7 @@ try } CATCH -TEST_P(SegmentDeletion_test, DeleteDataInStableAndDelta) +TEST_P(SegmentDeletionTest, DeleteDataInStableAndDelta) try { const size_t num_rows_write = 100; @@ -738,9 +758,9 @@ try } CATCH -INSTANTIATE_TEST_CASE_P(WhetherReadOrMergeDeltaBeforeDeleteRange, SegmentDeletion_test, testing::Combine(testing::Bool(), testing::Bool())); +INSTANTIATE_TEST_CASE_P(WhetherReadOrMergeDeltaBeforeDeleteRange, SegmentDeletionTest, testing::Combine(testing::Bool(), testing::Bool())); -TEST_F(Segment_test, DeleteRead) +TEST_F(SegmentTest, DeleteRead) try { const size_t num_rows_write = 64; @@ -946,7 +966,7 @@ try } CATCH -TEST_F(Segment_test, Split) +TEST_F(SegmentTest, Split) try { const size_t num_rows_write_per_batch = 100; @@ -1046,7 +1066,7 @@ try } CATCH -TEST_F(Segment_test, SplitFail) +TEST_F(SegmentTest, SplitFail) try { const size_t num_rows_write = 100; @@ -1066,7 +1086,7 @@ try } CATCH -TEST_F(Segment_test, Restore) +TEST_F(SegmentTest, Restore) try { // compare will compares the given segments. @@ -1158,7 +1178,7 @@ try } CATCH -TEST_F(Segment_test, MassiveSplit) +TEST_F(SegmentTest, MassiveSplit) try { Settings settings = dmContext().db_context.getSettings(); @@ -1242,52 +1262,51 @@ try } CATCH -enum Segment_test_Mode +enum SegmentTestMode { V1_BlockOnly, V2_BlockOnly, V2_FileOnly, }; -String testModeToString(const ::testing::TestParamInfo & info) +String testModeToString(const ::testing::TestParamInfo & info) { const auto mode = info.param; switch (mode) { - case Segment_test_Mode::V1_BlockOnly: + case SegmentTestMode::V1_BlockOnly: return "V1_BlockOnly"; - case Segment_test_Mode::V2_BlockOnly: + case SegmentTestMode::V2_BlockOnly: return "V2_BlockOnly"; - case Segment_test_Mode::V2_FileOnly: + case SegmentTestMode::V2_FileOnly: return "V2_FileOnly"; default: return "Unknown"; } } -class Segment_test_2 : public Segment_test - , public testing::WithParamInterface +class SegmentTest2 : public SegmentTest + , public testing::WithParamInterface { public: - Segment_test_2() - : Segment_test() - {} + SegmentTest2() = default; + void SetUp() override { mode = GetParam(); switch (mode) { - case Segment_test_Mode::V1_BlockOnly: + case SegmentTestMode::V1_BlockOnly: setStorageFormat(1); break; - case Segment_test_Mode::V2_BlockOnly: - case Segment_test_Mode::V2_FileOnly: + case SegmentTestMode::V2_BlockOnly: + case SegmentTestMode::V2_FileOnly: setStorageFormat(2); break; } - Segment_test::SetUp(); + SegmentTest::SetUp(); } std::pair genDMFile(DMContext & context, const Block & block) @@ -1305,7 +1324,7 @@ class Segment_test_2 : public Segment_test delegator.addDTFile(file_id, dmfile->getBytesOnDisk(), store_path); - auto & pk_column = block.getByPosition(0).column; + const auto & pk_column = block.getByPosition(0).column; auto min_pk = pk_column->getInt(0); auto max_pk = pk_column->getInt(block.rows() - 1); HandleRange range(min_pk, max_pk + 1); @@ -1313,10 +1332,10 @@ class Segment_test_2 : public Segment_test return {RowKeyRange::fromHandleRange(range), {file_id}}; } - Segment_test_Mode mode; + SegmentTestMode mode; }; -TEST_P(Segment_test_2, FlushDuringSplitAndMerge) +TEST_P(SegmentTest2, FlushDuringSplitAndMerge) try { size_t row_offset = 0; @@ -1327,11 +1346,11 @@ try row_offset += 100; switch (mode) { - case Segment_test_Mode::V1_BlockOnly: - case Segment_test_Mode::V2_BlockOnly: + case SegmentTestMode::V1_BlockOnly: + case SegmentTestMode::V2_BlockOnly: segment->write(dmContext(), std::move(block)); break; - case Segment_test_Mode::V2_FileOnly: + case SegmentTestMode::V2_FileOnly: { auto delegate = dmContext().path_pool.getStableDiskDelegator(); auto file_provider = dmContext().db_context.getFileProvider(); @@ -1430,9 +1449,9 @@ try } CATCH -INSTANTIATE_TEST_CASE_P(Segment_test_Mode, // - Segment_test_2, - testing::Values(Segment_test_Mode::V1_BlockOnly, Segment_test_Mode::V2_BlockOnly, Segment_test_Mode::V2_FileOnly), +INSTANTIATE_TEST_CASE_P(SegmentTestMode, // + SegmentTest2, + testing::Values(SegmentTestMode::V1_BlockOnly, SegmentTestMode::V2_BlockOnly, SegmentTestMode::V2_FileOnly), testModeToString); enum class SegmentWriteType @@ -1440,12 +1459,12 @@ enum class SegmentWriteType ToDisk, ToCache }; -class Segment_DDL_test - : public Segment_test +class SegmentDDLTest + : public SegmentTest , public testing::WithParamInterface> { }; -String paramToString(const ::testing::TestParamInfo & info) +String paramToString(const ::testing::TestParamInfo & info) { const auto [write_type, flush_before_ddl] = info.param; @@ -1455,7 +1474,7 @@ String paramToString(const ::testing::TestParamInfo } /// Mock a col from i8 -> i32 -TEST_P(Segment_DDL_test, AlterInt8ToInt32) +TEST_P(SegmentDDLTest, AlterInt8ToInt32) try { const String column_name_i8_to_i32 = "i8_to_i32"; @@ -1627,7 +1646,7 @@ try } CATCH -TEST_P(Segment_DDL_test, AddColumn) +TEST_P(SegmentDDLTest, AddColumn) try { const String new_column_name = "i8"; @@ -1795,7 +1814,7 @@ try } CATCH -TEST_F(Segment_test, CalculateDTFileProperty) +TEST_F(SegmentTest, CalculateDTFileProperty) try { Settings settings = dmContext().db_context.getSettings(); @@ -1836,7 +1855,7 @@ try } CATCH -TEST_F(Segment_test, CalculateDTFilePropertyWithPropertyFileDeleted) +TEST_F(SegmentTest, CalculateDTFilePropertyWithPropertyFileDeleted) try { Settings settings = dmContext().db_context.getSettings(); @@ -1857,10 +1876,10 @@ try } { - auto & stable = segment->getStable(); - auto & dmfiles = stable->getDMFiles(); + const auto & stable = segment->getStable(); + const auto & dmfiles = stable->getDMFiles(); ASSERT_GT(dmfiles[0]->getPacks(), (size_t)1); - auto & dmfile = dmfiles[0]; + const auto & dmfile = dmfiles[0]; auto file_path = dmfile->path(); // check property file exists and then delete it ASSERT_EQ(Poco::File(file_path + "/property").exists(), true); @@ -1877,7 +1896,7 @@ try // calculate the StableProperty for packs in the key range [0, num_rows_write_every_round) stable->calculateStableProperty(dmContext(), range, false); ASSERT_EQ(stable->isStablePropertyCached(), true); - auto & property = stable->getStableProperty(); + const auto & property = stable->getStableProperty(); ASSERT_EQ(property.gc_hint_version, std::numeric_limits::max()); ASSERT_EQ(property.num_versions, num_rows_write_every_round); ASSERT_EQ(property.num_puts, num_rows_write_every_round); @@ -1887,7 +1906,7 @@ try CATCH INSTANTIATE_TEST_CASE_P(SegmentWriteType, - Segment_DDL_test, + SegmentDDLTest, ::testing::Combine( // ::testing::Values(SegmentWriteType::ToDisk, SegmentWriteType::ToCache), ::testing::Bool()), diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment_common_handle.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment_common_handle.cpp index 1cc61663a2f..6359a3db184 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment_common_handle.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_segment_common_handle.cpp @@ -15,14 +15,13 @@ #include #include #include +#include #include #include #include #include -#include "dm_basic_include.h" - namespace DB { namespace DM diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp index 2dc65c256df..f929e153847 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_storage_delta_merge.cpp @@ -31,6 +31,8 @@ #include #include #include +#include +#include #include #include #include @@ -41,8 +43,6 @@ #include -#include "dm_basic_include.h" - namespace DB { namespace FailPoints diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_utils.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_utils.cpp index 340dad1ff6e..26a5b1132e4 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_utils.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_utils.cpp @@ -14,10 +14,9 @@ #include #include +#include #include -#include "dm_basic_include.h" - namespace DB { namespace DM diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_version_filter.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_version_filter.cpp index 59052e2e8b2..16b1729bea1 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_version_filter.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_version_filter.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include namespace DB { diff --git a/dbms/src/Storages/DeltaMerge/tests/stress/DMStressProxy.h b/dbms/src/Storages/DeltaMerge/tests/stress/DMStressProxy.h index eb4d937e8d4..0571eafae83 100644 --- a/dbms/src/Storages/DeltaMerge/tests/stress/DMStressProxy.h +++ b/dbms/src/Storages/DeltaMerge/tests/stress/DMStressProxy.h @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include From 5847f1c235b996eb6fb970029da926909e1819fd Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Wed, 8 Jun 2022 09:06:30 +0800 Subject: [PATCH 089/127] Add `Precompiled header` for modules & Refine by PImpl to accelerate release build (#5047) ref pingcap/tiflash#4909 --- CMakeLists.txt | 8 ++ cmake/find_ccache.cmake | 14 ++++ dbms/CMakeLists.txt | 32 ++++++++ dbms/pch-common.h | 23 ++++++ dbms/pch-dbms.h | 18 +++++ dbms/pch-kvpb.h | 26 +++++++ dbms/pch-stl.h | 34 ++++++++ dbms/src/Common/FmtUtils.h | 10 +-- dbms/src/Common/TiFlashException.cpp | 77 ++++++++++++++++++- dbms/src/Common/TiFlashException.h | 58 ++++---------- .../DataStreams/TiRemoteBlockInputStream.h | 1 - .../src/Flash/Coprocessor/CoprocessorReader.h | 2 +- .../Coprocessor/DAGStorageInterpreter.cpp | 1 + .../Flash/Coprocessor/DAGStorageInterpreter.h | 2 +- .../Flash/Coprocessor/GenSchemaAndColumn.cpp | 1 + dbms/src/Flash/Coprocessor/RemoteRequest.cpp | 2 + dbms/src/Flash/Coprocessor/RemoteRequest.h | 3 +- .../Flash/Coprocessor/TablesRegionsInfo.cpp | 1 + .../src/Flash/Coprocessor/TablesRegionsInfo.h | 3 +- .../Management/tests/gtest_manual_compact.cpp | 1 + dbms/src/Flash/Mpp/MinTSOScheduler.h | 11 ++- dbms/src/Interpreters/Context.h | 1 - dbms/src/Server/CLIService.h | 1 - dbms/src/Storages/Transaction/KVStore.cpp | 36 +++++---- dbms/src/Storages/Transaction/KVStore.h | 21 +++-- dbms/src/Storages/Transaction/ProxyFFI.cpp | 2 + .../Storages/Transaction/ReadIndexWorker.cpp | 7 +- dbms/src/Storages/Transaction/TMTContext.h | 6 -- dbms/src/TestUtils/ColumnsToTiPBExpr.cpp | 1 + dbms/src/TiDB/Schema/TiDBSchemaSyncer.h | 1 - .../scripts/build-tiflash-release.sh | 2 + 31 files changed, 307 insertions(+), 99 deletions(-) create mode 100644 dbms/pch-common.h create mode 100644 dbms/pch-dbms.h create mode 100644 dbms/pch-kvpb.h create mode 100644 dbms/pch-stl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f1dd740b0b4..4e14c205f18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,14 @@ endif () set(CMAKE_EXPORT_COMPILE_COMMANDS 1) option (USE_CCACHE "Set to OFF to disable ccache" ON) +if (USE_CCACHE) + set(NOT_USE_CCACHE 0) +else() + set(NOT_USE_CCACHE 1) +endif() + +option(ENABLE_PCH "Enable `Precompiled header`" OFF) + include (cmake/find_ccache.cmake) if (NOT CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "None") diff --git a/cmake/find_ccache.cmake b/cmake/find_ccache.cmake index 910caebb046..71fa337a922 100644 --- a/cmake/find_ccache.cmake +++ b/cmake/find_ccache.cmake @@ -24,6 +24,20 @@ if (USE_CCACHE AND CCACHE_FOUND AND NOT CMAKE_CXX_COMPILER_LAUNCHER MATCHES "cca message ("${CCACHE_CONFIG}") set_property (GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_FOUND}) set_property (GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_FOUND}) + + if (ENABLE_PCH) + execute_process (COMMAND ${CCACHE_FOUND} --get-config sloppiness OUTPUT_VARIABLE _CCACHE_SLOPPINESS OUTPUT_STRIP_TRAILING_WHITESPACE) + string (FIND "${_CCACHE_SLOPPINESS}" "pch_defines" _CCACHE_SLOPPINESS_RES) + if (NOT _CCACHE_SLOPPINESS_RES STREQUAL "-1") + string (FIND "${_CCACHE_SLOPPINESS}" "time_macros" _CCACHE_SLOPPINESS_RES) + endif () + + if (_CCACHE_SLOPPINESS_RES STREQUAL "-1") + message(WARNING "`Precompiled header` won't be cached by ccache, sloppiness = `${CCACHE_SLOPPINESS}`,please execute `ccache -o sloppiness=pch_defines,time_macros`") + set (ENABLE_PCH FALSE CACHE BOOL "" FORCE) + endif () + endif () + else () message (STATUS "Not using ccache ${CCACHE_FOUND}, USE_CCACHE=${USE_CCACHE}") endif () diff --git a/dbms/CMakeLists.txt b/dbms/CMakeLists.txt index 21cf06cbe31..cce11bd6997 100644 --- a/dbms/CMakeLists.txt +++ b/dbms/CMakeLists.txt @@ -259,6 +259,16 @@ target_include_directories (clickhouse_common_io BEFORE PRIVATE ${COMMON_INCLUDE # https://cmake.org/pipermail/cmake/2016-May/063400.html target_link_libraries (clickhouse_common_io PUBLIC ${TIFLASH_XXHASH_LIBRARY}) +function(add_target_pch context target) + if (ENABLE_PCH) + message(STATUS "Add PCH `${context}` for target `${target}`") + target_precompile_headers(${target} PRIVATE ${context}) + endif () + if(${ARGC} GREATER 2) + add_target_pch(${context} ${ARGN}) + endif() +endfunction() + if (ENABLE_TESTS) include (${TiFlash_SOURCE_DIR}/cmake/find_gtest.cmake) @@ -297,6 +307,8 @@ if (ENABLE_TESTS) target_compile_options(gtests_dbms PRIVATE -Wno-unknown-pragmas -Wno-deprecated-copy) add_check(gtests_dbms) + add_target_pch("pch-dbms.h" gtests_dbms) + grep_bench_sources(${TiFlash_SOURCE_DIR}/dbms dbms_bench_sources) add_executable(bench_dbms EXCLUDE_FROM_ALL ${dbms_bench_sources} @@ -342,3 +354,23 @@ if (TEST_COVERAGE AND CMAKE_BUILD_TYPE STREQUAL "Debug") ) endif () endif () + +# dbms +add_target_pch("pch-dbms.h" dbms flash_service) +add_target_pch("pch-common.h" clickhouse_common_io clickhouse_functions clickhouse_aggregate_functions) +add_target_pch("pch-common.h" clickhouse_parsers clickhouse_storages_system dt-workload-lib clickhouse-server-lib) + +# common +add_target_pch("pch-kvpb.h" kv_client) + +add_target_pch("pch-stl.h" ${Boost_SYSTEM_LIBRARY} cctz ${RE2_LIBRARY} ${RE2_ST_LIBRARY}) + +# grpc +add_target_pch("$<$:${CMAKE_CURRENT_SOURCE_DIR}/pch-stl.h>" grpc grpc++) + +# pb +add_target_pch("pch-stl.h" libprotobuf kvproto tipb libprotoc) + +# poco +add_target_pch("pch-stl.h" Net Crypto Util Data NetSSL) +add_target_pch("$<$:${CMAKE_CURRENT_SOURCE_DIR}/pch-stl.h>" XML Foundation JSON) diff --git a/dbms/pch-common.h b/dbms/pch-common.h new file mode 100644 index 00000000000..878254a3529 --- /dev/null +++ b/dbms/pch-common.h @@ -0,0 +1,23 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +#include +#include +#include +#include +#include + +#include "pch-stl.h" diff --git a/dbms/pch-dbms.h b/dbms/pch-dbms.h new file mode 100644 index 00000000000..60687073bf8 --- /dev/null +++ b/dbms/pch-dbms.h @@ -0,0 +1,18 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +#include "pch-common.h" +#include "pch-kvpb.h" diff --git a/dbms/pch-kvpb.h b/dbms/pch-kvpb.h new file mode 100644 index 00000000000..d74bfc6bb89 --- /dev/null +++ b/dbms/pch-kvpb.h @@ -0,0 +1,26 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +/** +There may be unexpected behaviors for `ccache` to deal with PCH which includes those header files generated by tools: + 'xxx' has been modified since the precompiled header 'xxx' was built: mtime changed + `Precompiled header includes xxx.h, which has a new mtime` +*/ +#include +#include +#include + +#include "pch-stl.h" diff --git a/dbms/pch-stl.h b/dbms/pch-stl.h new file mode 100644 index 00000000000..01b3123650d --- /dev/null +++ b/dbms/pch-stl.h @@ -0,0 +1,34 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/dbms/src/Common/FmtUtils.h b/dbms/src/Common/FmtUtils.h index 71f93a34078..ab37194ed10 100644 --- a/dbms/src/Common/FmtUtils.h +++ b/dbms/src/Common/FmtUtils.h @@ -14,8 +14,6 @@ #pragma once -#include -#include #include namespace DB @@ -32,9 +30,9 @@ class FmtBuffer return *this; } - FmtBuffer & append(StringRef s) + FmtBuffer & append(std::string_view s) { - buffer.append(s.data, s.data + s.size); + buffer.append(s.data(), s.data() + s.size()); return *this; } @@ -55,7 +53,7 @@ class FmtBuffer FmtBuffer & joinStr( Iter first, Iter end, - StringRef delimiter) + std::string_view delimiter) { auto func = [](const auto & s, FmtBuffer & fb) { fb.append(s); @@ -68,7 +66,7 @@ class FmtBuffer Iter first, Iter end, FF && toStringFunc, // void (const auto &, FmtBuffer &) - StringRef delimiter) + std::string_view delimiter) { if (first == end) return *this; diff --git a/dbms/src/Common/TiFlashException.cpp b/dbms/src/Common/TiFlashException.cpp index bd28a57c093..11ee229ded0 100644 --- a/dbms/src/Common/TiFlashException.cpp +++ b/dbms/src/Common/TiFlashException.cpp @@ -18,6 +18,32 @@ namespace DB { +struct TiFlashErrorRegistry::Errors : std::map, TiFlashError> +{ +}; + +TiFlashErrorRegistry::Errors & TiFlashErrorRegistry::errors() +{ + return *inner_data; +} + +TiFlashErrorRegistry::Errors & TiFlashErrorRegistry::errors() const +{ + return *inner_data; +} + +TiFlashErrorRegistry::TiFlashErrorRegistry() + : inner_data(new Errors{}) +{ + initialize(); +} + +TiFlashErrorRegistry::~TiFlashErrorRegistry() +{ + delete inner_data; + inner_data = nullptr; +} + void TiFlashErrorRegistry::initialize() { // Used to check uniqueness of classes @@ -46,9 +72,9 @@ void TiFlashErrorRegistry::initialize() void TiFlashErrorRegistry::registerError(const std::string & error_class, const std::string & error_code, const std::string & description, const std::string & workaround, const std::string & message_template) { TiFlashError error{error_class, error_code, description, workaround, message_template}; - if (all_errors.find({error_class, error_code}) == all_errors.end()) + if (errors().find({error_class, error_code}) == errors().end()) { - all_errors.emplace(std::make_pair(error_class, error_code), std::move(error)); + errors().emplace(std::make_pair(error_class, error_code), std::move(error)); } else { @@ -77,4 +103,51 @@ std::string TiFlashException::standardText() const return text; } +std::optional TiFlashErrorRegistry::get(const std::string & error_class, const std::string & error_code) const +{ + auto error = errors().find({error_class, error_code}); + if (error != errors().end()) + { + return error->second; + } + else + { + return {}; + } +} +std::optional TiFlashErrorRegistry::get(const std::string & error_class, int error_code) const +{ + return get(error_class, std::to_string(error_code)); +} + +std::vector TiFlashErrorRegistry::allErrors() const +{ + std::vector res; + res.reserve(errors().size()); + for (const auto & error : errors()) + { + res.push_back(error.second); + } + return res; +} + +TiFlashError TiFlashErrorRegistry::simpleGet(const std::string & error_class, const std::string & error_code) +{ + auto & m_instance = instance(); + auto error = m_instance.get(error_class, error_code); + if (error.has_value()) + { + return error.value(); + } + else + { + throw Exception("Unregistered TiFlashError: FLASH:" + error_class + ":" + error_code); + } +} +TiFlashError TiFlashErrorRegistry::simpleGet(const std::string & error_class, int error_code) +{ + return simpleGet(error_class, std::to_string(error_code)); +} + + } // namespace DB diff --git a/dbms/src/Common/TiFlashException.h b/dbms/src/Common/TiFlashException.h index 2026571859e..3b4e3d75813 100644 --- a/dbms/src/Common/TiFlashException.h +++ b/dbms/src/Common/TiFlashException.h @@ -194,56 +194,19 @@ class TiFlashErrorRegistry : public ext::Singleton public: friend ext::Singleton; - static TiFlashError simpleGet(const std::string & error_class, const std::string & error_code) - { - auto & m_instance = instance(); - auto error = m_instance.get(error_class, error_code); - if (error.has_value()) - { - return error.value(); - } - else - { - throw Exception("Unregistered TiFlashError: FLASH:" + error_class + ":" + error_code); - } - } + static TiFlashError simpleGet(const std::string & error_class, const std::string & error_code); - static TiFlashError simpleGet(const std::string & error_class, int error_code) - { - return simpleGet(error_class, std::to_string(error_code)); - } + static TiFlashError simpleGet(const std::string & error_class, int error_code); - std::optional get(const std::string & error_class, const std::string & error_code) const - { - auto error = all_errors.find({error_class, error_code}); - if (error != all_errors.end()) - { - return error->second; - } - else - { - return {}; - } - } + std::optional get(const std::string & error_class, const std::string & error_code) const; - std::optional get(const std::string & error_class, int error_code) const - { - return get(error_class, std::to_string(error_code)); - } + std::optional get(const std::string & error_class, int error_code) const; - std::vector allErrors() const - { - std::vector res; - res.reserve(all_errors.size()); - for (const auto & error : all_errors) - { - res.push_back(error.second); - } - return res; - } + std::vector allErrors() const; protected: - TiFlashErrorRegistry() { initialize(); } + TiFlashErrorRegistry(); + ~TiFlashErrorRegistry(); private: void registerError(const std::string & error_class, const std::string & error_code, const std::string & description, const std::string & workaround, const std::string & message_template = ""); @@ -252,8 +215,13 @@ class TiFlashErrorRegistry : public ext::Singleton void initialize(); + struct Errors; + + Errors & errors(); + Errors & errors() const; + private: - std::map, TiFlashError> all_errors; + Errors * inner_data; // PImpl }; /// TiFlashException implements TiDB's standardized error. diff --git a/dbms/src/DataStreams/TiRemoteBlockInputStream.h b/dbms/src/DataStreams/TiRemoteBlockInputStream.h index f8e313a25be..f249bf1a0dc 100644 --- a/dbms/src/DataStreams/TiRemoteBlockInputStream.h +++ b/dbms/src/DataStreams/TiRemoteBlockInputStream.h @@ -22,7 +22,6 @@ #include #include #include -#include #include #include diff --git a/dbms/src/Flash/Coprocessor/CoprocessorReader.h b/dbms/src/Flash/Coprocessor/CoprocessorReader.h index 8a3eb471e54..25c07cff49c 100644 --- a/dbms/src/Flash/Coprocessor/CoprocessorReader.h +++ b/dbms/src/Flash/Coprocessor/CoprocessorReader.h @@ -20,7 +20,6 @@ #include #include #include -#include #include #include @@ -29,6 +28,7 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #include #include #include diff --git a/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp b/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp index 11a1b7e2d3e..df7e504d2c4 100644 --- a/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp +++ b/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #pragma GCC diagnostic push diff --git a/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.h b/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.h index d86274a1e22..0425abe04db 100644 --- a/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.h +++ b/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.h @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -33,6 +32,7 @@ namespace DB { +class TMTContext; using TablesRegionInfoMap = std::unordered_map>; /// DAGStorageInterpreter encapsulates operations around storage during interprete stage. /// It's only intended to be used by DAGQueryBlockInterpreter. diff --git a/dbms/src/Flash/Coprocessor/GenSchemaAndColumn.cpp b/dbms/src/Flash/Coprocessor/GenSchemaAndColumn.cpp index e7964021709..be3475f714f 100644 --- a/dbms/src/Flash/Coprocessor/GenSchemaAndColumn.cpp +++ b/dbms/src/Flash/Coprocessor/GenSchemaAndColumn.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include #include +#include namespace DB { diff --git a/dbms/src/Flash/Coprocessor/RemoteRequest.cpp b/dbms/src/Flash/Coprocessor/RemoteRequest.cpp index 086cdb43d20..d3b5c202136 100644 --- a/dbms/src/Flash/Coprocessor/RemoteRequest.cpp +++ b/dbms/src/Flash/Coprocessor/RemoteRequest.cpp @@ -13,8 +13,10 @@ // limitations under the License. #include +#include #include #include +#include namespace DB { diff --git a/dbms/src/Flash/Coprocessor/RemoteRequest.h b/dbms/src/Flash/Coprocessor/RemoteRequest.h index 1e42e18a7bd..5af3f66298c 100644 --- a/dbms/src/Flash/Coprocessor/RemoteRequest.h +++ b/dbms/src/Flash/Coprocessor/RemoteRequest.h @@ -17,11 +17,12 @@ #include #include #include -#include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #include +#include #include #pragma GCC diagnostic pop diff --git a/dbms/src/Flash/Coprocessor/TablesRegionsInfo.cpp b/dbms/src/Flash/Coprocessor/TablesRegionsInfo.cpp index 7c4ddedabf4..ab4a0f82e95 100644 --- a/dbms/src/Flash/Coprocessor/TablesRegionsInfo.cpp +++ b/dbms/src/Flash/Coprocessor/TablesRegionsInfo.cpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace DB { diff --git a/dbms/src/Flash/Coprocessor/TablesRegionsInfo.h b/dbms/src/Flash/Coprocessor/TablesRegionsInfo.h index dccb8b95466..f80a44b92a3 100644 --- a/dbms/src/Flash/Coprocessor/TablesRegionsInfo.h +++ b/dbms/src/Flash/Coprocessor/TablesRegionsInfo.h @@ -15,7 +15,7 @@ #pragma once #include #include -#include + #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" @@ -25,6 +25,7 @@ namespace DB { +class TMTContext; struct SingleTableRegions { RegionInfoMap local_regions; diff --git a/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp b/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp index 4527892e353..df6c881c306 100644 --- a/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp +++ b/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/dbms/src/Flash/Mpp/MinTSOScheduler.h b/dbms/src/Flash/Mpp/MinTSOScheduler.h index 17ab1f4dfa3..dbc0cb84cc3 100644 --- a/dbms/src/Flash/Mpp/MinTSOScheduler.h +++ b/dbms/src/Flash/Mpp/MinTSOScheduler.h @@ -15,12 +15,19 @@ #pragma once #include -#include -#include #include namespace DB { +class MinTSOScheduler; +using MPPTaskSchedulerPtr = std::unique_ptr; + +class MPPTaskManager; +using MPPTaskManagerPtr = std::shared_ptr; + +struct MPPQueryTaskSet; +using MPPQueryTaskSetPtr = std::shared_ptr; + /// scheduling tasks in the set according to the tso order under the soft limit of threads, but allow the min_tso query to preempt threads under the hard limit of threads. /// The min_tso query avoids the deadlock resulted from threads competition among nodes. /// schedule tasks under the lock protection of the task manager. diff --git a/dbms/src/Interpreters/Context.h b/dbms/src/Interpreters/Context.h index 63aefcbece9..5d5c39263c6 100644 --- a/dbms/src/Interpreters/Context.h +++ b/dbms/src/Interpreters/Context.h @@ -14,7 +14,6 @@ #pragma once -#include #include #include #include diff --git a/dbms/src/Server/CLIService.h b/dbms/src/Server/CLIService.h index 18c9d61260f..9078fa991f3 100644 --- a/dbms/src/Server/CLIService.h +++ b/dbms/src/Server/CLIService.h @@ -25,7 +25,6 @@ #include #include #include -#include #include #include diff --git a/dbms/src/Storages/Transaction/KVStore.cpp b/dbms/src/Storages/Transaction/KVStore.cpp index 2b82220cbc4..318a04c6ed9 100644 --- a/dbms/src/Storages/Transaction/KVStore.cpp +++ b/dbms/src/Storages/Transaction/KVStore.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -38,15 +39,15 @@ extern const int TABLE_IS_DROPPED; } // namespace ErrorCodes KVStore::KVStore(Context & context, TiDB::SnapshotApplyMethod snapshot_apply_method_) - : region_persister(context, region_manager) + : region_persister(std::make_unique(context, region_manager)) , raft_cmd_res(std::make_unique()) , snapshot_apply_method(snapshot_apply_method_) , log(&Poco::Logger::get("KVStore")) + , region_compact_log_period(120) + , region_compact_log_min_rows(40 * 1024) + , region_compact_log_min_bytes(32 * 1024 * 1024) { // default config about compact-log: period 120s, rows 40k, bytes 32MB. - REGION_COMPACT_LOG_PERIOD = 120; - REGION_COMPACT_LOG_MIN_ROWS = 40 * 1024; - REGION_COMPACT_LOG_MIN_BYTES = 32 * 1024 * 1024; } void KVStore::restore(const TiFlashRaftProxyHelper * proxy_helper) @@ -55,7 +56,7 @@ void KVStore::restore(const TiFlashRaftProxyHelper * proxy_helper) auto manage_lock = genRegionWriteLock(task_lock); this->proxy_helper = proxy_helper; - manage_lock.regions = region_persister.restore(proxy_helper); + manage_lock.regions = region_persister->restore(proxy_helper); LOG_FMT_INFO(log, "Restored {} regions", manage_lock.regions.size()); @@ -166,7 +167,7 @@ void KVStore::tryPersist(RegionID region_id) if (region) { LOG_FMT_INFO(log, "Try to persist {}", region->toString(false)); - region_persister.persist(*region); + region_persister->persist(*region); LOG_FMT_INFO(log, "After persisted {}, cache {} bytes", region->toString(false), region->dataSize()); } } @@ -182,7 +183,7 @@ void KVStore::gcRegionPersistedCache(Seconds gc_persist_period) if (now < (last_gc_time.load() + gc_persist_period)) return; last_gc_time = now; - region_persister.gc(); + region_persister->gc(); } void KVStore::removeRegion(RegionID region_id, bool remove_data, RegionTable & region_table, const KVStoreTaskLock & task_lock, const RegionTaskLock & region_lock) @@ -203,7 +204,7 @@ void KVStore::removeRegion(RegionID region_id, bool remove_data, RegionTable & r } } - region_persister.drop(region_id, region_lock); + region_persister->drop(region_id, region_lock); LOG_FMT_INFO(log, "Persisted [region {}] deleted", region_id); region_table.removeRegion(region_id, remove_data, region_lock); @@ -306,9 +307,9 @@ void KVStore::handleDestroy(UInt64 region_id, TMTContext & tmt, const KVStoreTas void KVStore::setRegionCompactLogConfig(UInt64 sec, UInt64 rows, UInt64 bytes) { - REGION_COMPACT_LOG_PERIOD = sec; - REGION_COMPACT_LOG_MIN_ROWS = rows; - REGION_COMPACT_LOG_MIN_BYTES = bytes; + region_compact_log_period = sec; + region_compact_log_min_rows = rows; + region_compact_log_min_bytes = bytes; LOG_FMT_INFO( log, @@ -321,7 +322,7 @@ void KVStore::setRegionCompactLogConfig(UInt64 sec, UInt64 rows, UInt64 bytes) void KVStore::persistRegion(const Region & region, const RegionTaskLock & region_task_lock, const char * caller) { LOG_FMT_INFO(log, "Start to persist {}, cache size: {} bytes for `{}`", region.toString(true), region.dataSize(), caller); - region_persister.persist(region, region_task_lock); + region_persister->persist(region, region_task_lock); LOG_FMT_DEBUG(log, "Persist {} done", region.toString(false)); } @@ -362,8 +363,8 @@ EngineStoreApplyRes KVStore::handleUselessAdminRaftCmd( LOG_FMT_DEBUG(log, "{} approx mem cache info: rows {}, bytes {}", curr_region.toString(false), rows, size_bytes); - if (rows >= REGION_COMPACT_LOG_MIN_ROWS.load(std::memory_order_relaxed) - || size_bytes >= REGION_COMPACT_LOG_MIN_BYTES.load(std::memory_order_relaxed)) + if (rows >= region_compact_log_min_rows.load(std::memory_order_relaxed) + || size_bytes >= region_compact_log_min_bytes.load(std::memory_order_relaxed)) { // if rows or bytes more than threshold, flush cache and perist mem data. return true; @@ -372,7 +373,7 @@ EngineStoreApplyRes KVStore::handleUselessAdminRaftCmd( { // if thhere is little data in mem, wait until time interval reached threshold. // use random period so that lots of regions will not be persisted at same time. - auto compact_log_period = std::rand() % REGION_COMPACT_LOG_PERIOD.load(std::memory_order_relaxed); // NOLINT + auto compact_log_period = std::rand() % region_compact_log_period.load(std::memory_order_relaxed); // NOLINT return !(curr_region.lastCompactLogTime() + Seconds{compact_log_period} > Clock::now()); } } @@ -765,4 +766,9 @@ KVStore::~KVStore() releaseReadIndexWorkers(); } +FileUsageStatistics KVStore::getFileUsageStatistics() const +{ + return region_persister->getFileUsageStatistics(); +} + } // namespace DB diff --git a/dbms/src/Storages/Transaction/KVStore.h b/dbms/src/Storages/Transaction/KVStore.h index 8673cae3ff3..bb45e65d18b 100644 --- a/dbms/src/Storages/Transaction/KVStore.h +++ b/dbms/src/Storages/Transaction/KVStore.h @@ -16,20 +16,19 @@ #include #include -#include #include - namespace TiDB { struct TableInfo; } namespace DB { +class Context; namespace RegionBench { extern void concurrentBatchInsert(const TiDB::TableInfo &, Int64, Int64, Int64, UInt64, UInt64, Context &); -} +} // namespace RegionBench namespace DM { enum class FileConvertJobType; @@ -40,7 +39,6 @@ namespace tests class RegionKVStoreTest; } -class Context; class IAST; using ASTPtr = std::shared_ptr; using ASTs = std::vector; @@ -71,6 +69,8 @@ using RegionPreDecodeBlockDataPtr = std::unique_ptr; class ReadIndexWorkerManager; using BatchReadIndexRes = std::vector>; class ReadIndexStressTest; +struct FileUsageStatistics; +class RegionPersister; /// TODO: brief design document. class KVStore final : private boost::noncopyable @@ -157,10 +157,7 @@ class KVStore final : private boost::noncopyable ~KVStore(); - FileUsageStatistics getFileUsageStatistics() const - { - return region_persister.getFileUsageStatistics(); - } + FileUsageStatistics getFileUsageStatistics() const; private: friend class MockTiDB; @@ -229,7 +226,7 @@ class KVStore final : private boost::noncopyable private: RegionManager region_manager; - RegionPersister region_persister; + std::unique_ptr region_persister; std::atomic last_gc_time = Timepoint::min(); @@ -242,9 +239,9 @@ class KVStore final : private boost::noncopyable Poco::Logger * log; - std::atomic REGION_COMPACT_LOG_PERIOD; - std::atomic REGION_COMPACT_LOG_MIN_ROWS; - std::atomic REGION_COMPACT_LOG_MIN_BYTES; + std::atomic region_compact_log_period; + std::atomic region_compact_log_min_rows; + std::atomic region_compact_log_min_bytes; mutable std::mutex bg_gc_region_data_mutex; std::list bg_gc_region_data; diff --git a/dbms/src/Storages/Transaction/ProxyFFI.cpp b/dbms/src/Storages/Transaction/ProxyFFI.cpp index cc7d1e10a49..8a40ca9b15e 100644 --- a/dbms/src/Storages/Transaction/ProxyFFI.cpp +++ b/dbms/src/Storages/Transaction/ProxyFFI.cpp @@ -24,6 +24,8 @@ #include #include +#include + #define CHECK_PARSE_PB_BUFF_IMPL(n, a, b, c) \ do \ { \ diff --git a/dbms/src/Storages/Transaction/ReadIndexWorker.cpp b/dbms/src/Storages/Transaction/ReadIndexWorker.cpp index 97a8f4b3e0b..3223c815989 100644 --- a/dbms/src/Storages/Transaction/ReadIndexWorker.cpp +++ b/dbms/src/Storages/Transaction/ReadIndexWorker.cpp @@ -20,6 +20,7 @@ #include #include +#include namespace DB { @@ -174,7 +175,7 @@ struct BlockedReadIndexHelper : BlockedReadIndexHelperTrait return waker.waitFor(tm); } - virtual ~BlockedReadIndexHelper() = default; + ~BlockedReadIndexHelper() override = default; private: AsyncWaker & waker; @@ -193,7 +194,7 @@ struct BlockedReadIndexHelperV3 : BlockedReadIndexHelperTrait return notifier.blockedWaitFor(tm); } - virtual ~BlockedReadIndexHelperV3() = default; + ~BlockedReadIndexHelperV3() override = default; private: AsyncWaker::Notifier & notifier; @@ -342,7 +343,7 @@ struct RegionReadIndexNotifier : AsyncNotifier notify->wake(); } - virtual ~RegionReadIndexNotifier() = default; + ~RegionReadIndexNotifier() override = default; RegionReadIndexNotifier( RegionID region_id_, diff --git a/dbms/src/Storages/Transaction/TMTContext.h b/dbms/src/Storages/Transaction/TMTContext.h index bd592dad315..8e26c0da88c 100644 --- a/dbms/src/Storages/Transaction/TMTContext.h +++ b/dbms/src/Storages/Transaction/TMTContext.h @@ -34,15 +34,9 @@ using SchemaSyncerPtr = std::shared_ptr; class BackgroundService; using BackGroundServicePtr = std::unique_ptr; -class MinTSOScheduler; -using MPPTaskSchedulerPtr = std::unique_ptr; - class MPPTaskManager; using MPPTaskManagerPtr = std::shared_ptr; -struct MPPQueryTaskSet; -using MPPQueryTaskSetPtr = std::shared_ptr; - class GCManager; using GCManagerPtr = std::shared_ptr; diff --git a/dbms/src/TestUtils/ColumnsToTiPBExpr.cpp b/dbms/src/TestUtils/ColumnsToTiPBExpr.cpp index 2c3bf243176..dcf727614b1 100644 --- a/dbms/src/TestUtils/ColumnsToTiPBExpr.cpp +++ b/dbms/src/TestUtils/ColumnsToTiPBExpr.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include namespace DB diff --git a/dbms/src/TiDB/Schema/TiDBSchemaSyncer.h b/dbms/src/TiDB/Schema/TiDBSchemaSyncer.h index 8aab2b3302e..4fdba195acb 100644 --- a/dbms/src/TiDB/Schema/TiDBSchemaSyncer.h +++ b/dbms/src/TiDB/Schema/TiDBSchemaSyncer.h @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include diff --git a/release-centos7-llvm/scripts/build-tiflash-release.sh b/release-centos7-llvm/scripts/build-tiflash-release.sh index bb62e4743f6..42993b51afe 100755 --- a/release-centos7-llvm/scripts/build-tiflash-release.sh +++ b/release-centos7-llvm/scripts/build-tiflash-release.sh @@ -42,6 +42,7 @@ SRCPATH=$( ) NPROC=${NPROC:-$(nproc || grep -c ^processor /proc/cpuinfo)} ENABLE_THINLTO=${ENABLE_THINLTO:-ON} +ENABLE_PCH=${ENABLE_PCH:-ON} INSTALL_DIR="${SRCPATH}/release-centos7-llvm/tiflash" rm -rf ${INSTALL_DIR} && mkdir -p ${INSTALL_DIR} @@ -59,6 +60,7 @@ cmake -S "${SRCPATH}" \ -DRUN_HAVE_STD_REGEX=0 \ -DENABLE_THINLTO=${ENABLE_THINLTO} \ -DTHINLTO_JOBS=${NPROC} \ + -DENABLE_PCH=${ENABLE_PCH} \ -GNinja cmake --build . --target tiflash --parallel ${NPROC} From fdab3f52572abd84e7b00106a20cd2a18554fdec Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Wed, 8 Jun 2022 11:02:30 +0800 Subject: [PATCH 090/127] Test: Mock Input columns for operator tests (#5041) ref pingcap/tiflash#4609 --- .../MockExchangeReceiverInputStream.cpp | 16 ++ .../MockExchangeReceiverInputStream.h | 6 +- dbms/src/Flash/Coprocessor/DAGContext.cpp | 9 + dbms/src/Flash/Coprocessor/DAGContext.h | 9 +- .../Coprocessor/DAGQueryBlockInterpreter.cpp | 49 ++-- dbms/src/Flash/Coprocessor/InterpreterDAG.cpp | 6 +- dbms/src/Flash/Coprocessor/MockSourceStream.h | 60 +++++ dbms/src/Flash/tests/gtest_executor.cpp | 230 ++++++++++++++++++ dbms/src/Flash/tests/gtest_interpreter.cpp | 28 +-- dbms/src/TestUtils/ExecutorTestUtils.cpp | 133 ++++++++++ ...rpreterTestUtils.h => ExecutorTestUtils.h} | 38 ++- dbms/src/TestUtils/InterpreterTestUtils.cpp | 73 ------ dbms/src/TestUtils/mockExecutor.cpp | 92 ++++++- dbms/src/TestUtils/mockExecutor.h | 26 +- .../TestUtils/tests/gtest_mock_executors.cpp | 6 +- 15 files changed, 652 insertions(+), 129 deletions(-) create mode 100644 dbms/src/Flash/Coprocessor/MockSourceStream.h create mode 100644 dbms/src/Flash/tests/gtest_executor.cpp create mode 100644 dbms/src/TestUtils/ExecutorTestUtils.cpp rename dbms/src/TestUtils/{InterpreterTestUtils.h => ExecutorTestUtils.h} (61%) delete mode 100644 dbms/src/TestUtils/InterpreterTestUtils.cpp diff --git a/dbms/src/DataStreams/MockExchangeReceiverInputStream.cpp b/dbms/src/DataStreams/MockExchangeReceiverInputStream.cpp index b1de3e23914..3f46bb46cc8 100644 --- a/dbms/src/DataStreams/MockExchangeReceiverInputStream.cpp +++ b/dbms/src/DataStreams/MockExchangeReceiverInputStream.cpp @@ -30,6 +30,22 @@ MockExchangeReceiverInputStream::MockExchangeReceiverInputStream(const tipb::Exc } } +MockExchangeReceiverInputStream::MockExchangeReceiverInputStream(ColumnsWithTypeAndName columns, size_t max_block_size) + : columns(columns) + , output_index(0) + , max_block_size(max_block_size) +{ + rows = 0; + for (const auto & elem : columns) + { + if (elem.column) + { + assert(rows == 0 || rows == elem.column->size()); + rows = elem.column->size(); + } + } +} + ColumnPtr MockExchangeReceiverInputStream::makeColumn(ColumnWithTypeAndName elem) const { auto column = elem.type->createColumn(); diff --git a/dbms/src/DataStreams/MockExchangeReceiverInputStream.h b/dbms/src/DataStreams/MockExchangeReceiverInputStream.h index 24ae80d4f62..8c0a5b85822 100644 --- a/dbms/src/DataStreams/MockExchangeReceiverInputStream.h +++ b/dbms/src/DataStreams/MockExchangeReceiverInputStream.h @@ -26,7 +26,11 @@ class MockExchangeReceiverInputStream : public IProfilingBlockInputStream { public: MockExchangeReceiverInputStream(const tipb::ExchangeReceiver & receiver, size_t max_block_size, size_t rows_); - Block getHeader() const override { return Block(columns); } + MockExchangeReceiverInputStream(ColumnsWithTypeAndName columns, size_t max_block_size); + Block getHeader() const override + { + return Block(columns); + } String getName() const override { return "MockExchangeReceiver"; } ColumnsWithTypeAndName columns; size_t output_index; diff --git a/dbms/src/Flash/Coprocessor/DAGContext.cpp b/dbms/src/Flash/Coprocessor/DAGContext.cpp index 17fb6553eab..1736e0b6cec 100644 --- a/dbms/src/Flash/Coprocessor/DAGContext.cpp +++ b/dbms/src/Flash/Coprocessor/DAGContext.cpp @@ -271,4 +271,13 @@ const SingleTableRegions & DAGContext::getTableRegionsInfoByTableID(Int64 table_ return tables_regions_info.getTableRegionInfoByTableID(table_id); } +ColumnsWithTypeAndName DAGContext::columnsForTest(String executor_id) +{ + auto it = columns_for_test_map.find(executor_id); + if (unlikely(it == columns_for_test_map.end())) + { + throw DB::Exception("Don't have columns for mock source executors"); + } + return it->second; +} } // namespace DB diff --git a/dbms/src/Flash/Coprocessor/DAGContext.h b/dbms/src/Flash/Coprocessor/DAGContext.h index e3e5efdcbc6..c20eb3a367e 100644 --- a/dbms/src/Flash/Coprocessor/DAGContext.h +++ b/dbms/src/Flash/Coprocessor/DAGContext.h @@ -298,6 +298,10 @@ class DAGContext } bool isTest() const { return is_test; } + void setColumnsForTest(std::unordered_map & columns_for_test_map_) { columns_for_test_map = columns_for_test_map_; } + ColumnsWithTypeAndName columnsForTest(String executor_id); + + bool columnsForTestEmpty() { return columns_for_test_map.empty(); } void cancelAllExchangeReceiver(); @@ -317,8 +321,8 @@ class DAGContext Clock::time_point read_wait_index_end_timestamp{Clock::duration::zero()}; String table_scan_executor_id; String tidb_host = "Unknown"; - bool collect_execution_summaries; - bool return_executor_id; + bool collect_execution_summaries{}; + bool return_executor_id{}; bool is_mpp_task = false; bool is_root_mpp_task = false; bool is_batch_cop = false; @@ -372,6 +376,7 @@ class DAGContext std::vector subqueries; bool is_test = false; /// switch for test, do not use it in production. + std::unordered_map columns_for_test_map; /// , for multiple sources }; } // namespace DB diff --git a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp index 5fac49faaed..86d6428c92a 100644 --- a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp +++ b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -159,13 +160,22 @@ AnalysisResult analyzeExpressions( // for tests, we need to mock tableScan blockInputStream as the source stream. void DAGQueryBlockInterpreter::handleMockTableScan(const TiDBTableScan & table_scan, DAGPipeline & pipeline) { - auto names_and_types = genNamesAndTypes(table_scan); - auto columns_with_type_and_name = getColumnWithTypeAndName(names_and_types); - analyzer = std::make_unique(std::move(names_and_types), context); - for (size_t i = 0; i < max_streams; ++i) + if (context.getDAGContext()->columnsForTestEmpty() || context.getDAGContext()->columnsForTest(table_scan.getTableScanExecutorID()).empty()) + { + auto names_and_types = genNamesAndTypes(table_scan); + auto columns_with_type_and_name = getColumnWithTypeAndName(names_and_types); + analyzer = std::make_unique(std::move(names_and_types), context); + for (size_t i = 0; i < max_streams; ++i) + { + auto mock_table_scan_stream = std::make_shared(columns_with_type_and_name, context.getSettingsRef().max_block_size); + pipeline.streams.emplace_back(mock_table_scan_stream); + } + } + else { - auto mock_table_scan_stream = std::make_shared(columns_with_type_and_name, context.getSettingsRef().max_block_size); - pipeline.streams.emplace_back(mock_table_scan_stream); + auto [names_and_types, mock_table_scan_streams] = mockSourceStream(context, max_streams, log, table_scan.getTableScanExecutorID()); + analyzer = std::make_unique(std::move(names_and_types), context); + pipeline.streams.insert(pipeline.streams.end(), mock_table_scan_streams.begin(), mock_table_scan_streams.end()); } } @@ -266,7 +276,8 @@ void DAGQueryBlockInterpreter::handleJoin(const tipb::Join & join, DAGPipeline & stream->setExtraInfo( fmt::format("join build, build_side_root_executor_id = {}", dagContext().getJoinExecuteInfoMap()[query_block.source_name].build_side_root_executor_id)); }); - executeUnion(build_pipeline, max_streams, log, /*ignore_block=*/true, "for join"); + // for test, join executor need the return blocks to output. + executeUnion(build_pipeline, max_streams, log, /*ignore_block=*/!dagContext().isTest(), "for join"); right_query.source = build_pipeline.firstStream(); right_query.join = join_ptr; @@ -491,19 +502,29 @@ void DAGQueryBlockInterpreter::handleExchangeReceiver(DAGPipeline & pipeline) analyzer = std::make_unique(std::move(source_columns), context); } +// for tests, we need to mock ExchangeReceiver blockInputStream as the source stream. void DAGQueryBlockInterpreter::handleMockExchangeReceiver(DAGPipeline & pipeline) { - for (size_t i = 0; i < max_streams; ++i) + if (context.getDAGContext()->columnsForTestEmpty() || context.getDAGContext()->columnsForTest(query_block.source_name).empty()) { - // use max_block_size / 10 to determine the mock block's size - pipeline.streams.push_back(std::make_shared(query_block.source->exchange_receiver(), context.getSettingsRef().max_block_size, context.getSettingsRef().max_block_size / 10)); + for (size_t i = 0; i < max_streams; ++i) + { + // use max_block_size / 10 to determine the mock block's size + pipeline.streams.push_back(std::make_shared(query_block.source->exchange_receiver(), context.getSettingsRef().max_block_size, context.getSettingsRef().max_block_size / 10)); + } + NamesAndTypes source_columns; + for (const auto & col : pipeline.firstStream()->getHeader()) + { + source_columns.emplace_back(col.name, col.type); + } + analyzer = std::make_unique(std::move(source_columns), context); } - NamesAndTypes source_columns; - for (const auto & col : pipeline.firstStream()->getHeader()) + else { - source_columns.emplace_back(col.name, col.type); + auto [names_and_types, mock_exchange_streams] = mockSourceStream(context, max_streams, log, query_block.source_name); + analyzer = std::make_unique(std::move(names_and_types), context); + pipeline.streams.insert(pipeline.streams.end(), mock_exchange_streams.begin(), mock_exchange_streams.end()); } - analyzer = std::make_unique(std::move(source_columns), context); } void DAGQueryBlockInterpreter::handleProjection(DAGPipeline & pipeline, const tipb::Projection & projection) diff --git a/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp b/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp index 741aa7b5e26..a67ebf20aa5 100644 --- a/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp +++ b/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp @@ -83,11 +83,13 @@ BlockIO InterpreterDAG::execute() DAGPipeline pipeline; pipeline.streams = streams; /// add union to run in parallel if needed - if (dagContext().isMPPTask()) + if (unlikely(dagContext().isTest())) + executeUnion(pipeline, max_streams, dagContext().log, /*ignore_block=*/false, "for test"); + else if (dagContext().isMPPTask()) /// MPPTask do not need the returned blocks. executeUnion(pipeline, max_streams, dagContext().log, /*ignore_block=*/true, "for mpp"); else - executeUnion(pipeline, max_streams, dagContext().log, false, "for non mpp"); + executeUnion(pipeline, max_streams, dagContext().log, /*ignore_block=*/false, "for non mpp"); if (dagContext().hasSubquery()) { const Settings & settings = context.getSettingsRef(); diff --git a/dbms/src/Flash/Coprocessor/MockSourceStream.h b/dbms/src/Flash/Coprocessor/MockSourceStream.h new file mode 100644 index 00000000000..039cba22e3d --- /dev/null +++ b/dbms/src/Flash/Coprocessor/MockSourceStream.h @@ -0,0 +1,60 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. +#pragma once + +#include +#include +#include +#include +#include + +namespace DB +{ +template +std::pair>> mockSourceStream(Context & context, size_t max_streams, DB::LoggerPtr log, String executor_id) +{ + ColumnsWithTypeAndName columns_with_type_and_name; + NamesAndTypes names_and_types; + size_t rows = 0; + std::vector> mock_source_streams; + columns_with_type_and_name = context.getDAGContext()->columnsForTest(executor_id); + for (const auto & col : columns_with_type_and_name) + { + if (rows == 0) + rows = col.column->size(); + RUNTIME_ASSERT(rows == col.column->size(), log, "each column must has same size"); + names_and_types.push_back({col.name, col.type}); + } + size_t row_for_each_stream = rows / max_streams; + size_t rows_left = rows - row_for_each_stream * max_streams; + size_t start = 0; + for (size_t i = 0; i < max_streams; ++i) + { + ColumnsWithTypeAndName columns_for_stream; + size_t row_for_current_stream = row_for_each_stream + (i < rows_left ? 1 : 0); + for (const auto & column_with_type_and_name : columns_with_type_and_name) + { + columns_for_stream.push_back( + ColumnWithTypeAndName( + column_with_type_and_name.column->cut(start, row_for_current_stream), + column_with_type_and_name.type, + column_with_type_and_name.name)); + } + start += row_for_current_stream; + mock_source_streams.emplace_back(std::make_shared(columns_for_stream, context.getSettingsRef().max_block_size)); + } + RUNTIME_ASSERT(start == rows, log, "mock source streams' total size must same as user input"); + return {names_and_types, mock_source_streams}; +} +} // namespace DB \ No newline at end of file diff --git a/dbms/src/Flash/tests/gtest_executor.cpp b/dbms/src/Flash/tests/gtest_executor.cpp new file mode 100644 index 00000000000..64c60f14bb6 --- /dev/null +++ b/dbms/src/Flash/tests/gtest_executor.cpp @@ -0,0 +1,230 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include + +namespace DB +{ +namespace tests +{ +class ExecutorTestRunner : public DB::tests::ExecutorTest +{ +public: + void initializeContext() override + { + ExecutorTest::initializeContext(); + context.addMockTable({"test_db", "test_table"}, + {{"s1", TiDB::TP::TypeString}, {"s2", TiDB::TP::TypeString}}, + {toNullableVec("s1", {"banana", {}, "banana"}), + toNullableVec("s2", {"apple", {}, "banana"})}); + context.addExchangeReceiver("exchange1", + {{"s1", TiDB::TP::TypeString}, {"s2", TiDB::TP::TypeString}}, + {toNullableVec("s1", {"banana", {}, "banana"}), + toNullableVec("s2", {"apple", {}, "banana"})}); + + context.addExchangeReceiver("exchange_r_table", + {{"s1", TiDB::TP::TypeString}, {"join_c", TiDB::TP::TypeString}}, + {toNullableVec("s", {"banana", "banana"}), + toNullableVec("join_c", {"apple", "banana"})}); + + context.addExchangeReceiver("exchange_l_table", + {{"s1", TiDB::TP::TypeString}, {"join_c", TiDB::TP::TypeString}}, + {toNullableVec("s", {"banana", "banana"}), + toNullableVec("join_c", {"apple", "banana"})}); + + context.addMockTable({"test_db", "r_table"}, + {{"s", TiDB::TP::TypeString}, {"join_c", TiDB::TP::TypeString}}, + {toVec("s", {"banana", "banana"}), + toVec("join_c", {"apple", "banana"})}); + + context.addMockTable({"test_db", "r_table_2"}, + {{"s", TiDB::TP::TypeString}, {"join_c", TiDB::TP::TypeString}}, + {toVec("s", {"banana", "banana", "banana"}), + toVec("join_c", {"apple", "apple", "apple"})}); + + context.addMockTable({"test_db", "l_table"}, + {{"s", TiDB::TP::TypeString}, {"join_c", TiDB::TP::TypeString}}, + {toVec("s", {"banana", "banana"}), + toVec("join_c", {"apple", "banana"})}); + } +}; + +TEST_F(ExecutorTestRunner, Filter) +try +{ + auto request = context + .scan("test_db", "test_table") + .filter(eq(col("s1"), col("s2"))) + .build(context); + { + executeStreams(request, + {toNullableVec({"banana"}), + toNullableVec({"banana"})}); + } + + request = context.receive("exchange1") + .filter(eq(col("s1"), col("s2"))) + .build(context); + { + executeStreams(request, + {toNullableVec({"banana"}), + toNullableVec({"banana"})}); + } +} +CATCH + +TEST_F(ExecutorTestRunner, JoinWithTableScan) +try +{ + auto request = context + .scan("test_db", "l_table") + .join(context.scan("test_db", "r_table"), {col("join_c")}, ASTTableJoin::Kind::Left) + .topN("join_c", false, 2) + .build(context); + { + String expected = "topn_3 | order_by: {(<1, String>, desc: false)}, limit: 2\n" + " Join_2 | LeftOuterJoin, HashJoin. left_join_keys: {<0, String>}, right_join_keys: {<0, String>}\n" + " table_scan_0 | {<0, String>, <1, String>}\n" + " table_scan_1 | {<0, String>, <1, String>}\n"; + ASSERT_DAGREQUEST_EQAUL(expected, request); + executeStreams(request, + {toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"}), + toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"})}, + 2); + + executeStreams(request, + {toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"}), + toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"})}, + 5); + + executeStreams(request, + {toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"}), + toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"})}); + } + request = context + .scan("test_db", "l_table") + .join(context.scan("test_db", "r_table"), {col("join_c")}, ASTTableJoin::Kind::Left) + .project({"s", "join_c"}) + .topN("join_c", false, 2) + .build(context); + { + String expected = "topn_4 | order_by: {(<1, String>, desc: false)}, limit: 2\n" + " project_3 | {<0, String>, <1, String>}\n" + " Join_2 | LeftOuterJoin, HashJoin. left_join_keys: {<0, String>}, right_join_keys: {<0, String>}\n" + " table_scan_0 | {<0, String>, <1, String>}\n" + " table_scan_1 | {<0, String>, <1, String>}\n"; + ASSERT_DAGREQUEST_EQAUL(expected, request); + executeStreams(request, + {toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"})}, + 2); + } + + request = context + .scan("test_db", "l_table") + .join(context.scan("test_db", "r_table_2"), {col("join_c")}, ASTTableJoin::Kind::Left) + .topN("join_c", false, 4) + .build(context); + { + String expected = "topn_3 | order_by: {(<1, String>, desc: false)}, limit: 4\n" + " Join_2 | LeftOuterJoin, HashJoin. left_join_keys: {<0, String>}, right_join_keys: {<0, String>}\n" + " table_scan_0 | {<0, String>, <1, String>}\n" + " table_scan_1 | {<0, String>, <1, String>}\n"; + ASSERT_DAGREQUEST_EQAUL(expected, request); + executeStreams(request, + {toNullableVec({"banana", "banana", "banana", "banana"}), + toNullableVec({"apple", "apple", "apple", "banana"}), + toNullableVec({"banana", "banana", "banana", {}}), + toNullableVec({"apple", "apple", "apple", {}})}, + 2); + executeStreams(request, + {toNullableVec({"banana", "banana", "banana", "banana"}), + toNullableVec({"apple", "apple", "apple", "banana"}), + toNullableVec({"banana", "banana", "banana", {}}), + toNullableVec({"apple", "apple", "apple", {}})}, + 3); + } +} +CATCH + +TEST_F(ExecutorTestRunner, JoinWithExchangeReceiver) +try +{ + auto request = context + .receive("exchange_l_table") + .join(context.receive("exchange_r_table"), {col("join_c")}, ASTTableJoin::Kind::Left) + .topN("join_c", false, 2) + .build(context); + { + String expected = "topn_3 | order_by: {(<1, String>, desc: false)}, limit: 2\n" + " Join_2 | LeftOuterJoin, HashJoin. left_join_keys: {<0, String>}, right_join_keys: {<0, String>}\n" + " exchange_receiver_0 | type:PassThrough, {<0, String>, <1, String>}\n" + " exchange_receiver_1 | type:PassThrough, {<0, String>, <1, String>}\n"; + ASSERT_DAGREQUEST_EQAUL(expected, request); + executeStreams(request, + {toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"}), + toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"})}, + 2); + + executeStreams(request, + {toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"}), + toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"})}, + 5); + + executeStreams(request, + {toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"}), + toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"})}); + } +} +CATCH + +TEST_F(ExecutorTestRunner, JoinWithTableScanAndReceiver) +try +{ + auto request = context + .scan("test_db", "l_table") + .join(context.receive("exchange_r_table"), {col("join_c")}, ASTTableJoin::Kind::Left) + .topN("join_c", false, 2) + .build(context); + { + String expected = "topn_3 | order_by: {(<1, String>, desc: false)}, limit: 2\n" + " Join_2 | LeftOuterJoin, HashJoin. left_join_keys: {<0, String>}, right_join_keys: {<0, String>}\n" + " table_scan_0 | {<0, String>, <1, String>}\n" + " exchange_receiver_1 | type:PassThrough, {<0, String>, <1, String>}\n"; + ASSERT_DAGREQUEST_EQAUL(expected, request); + executeStreams(request, + {toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"}), + toNullableVec({"banana", "banana"}), + toNullableVec({"apple", "banana"})}, + 2); + } +} +CATCH + +} // namespace tests +} // namespace DB \ No newline at end of file diff --git a/dbms/src/Flash/tests/gtest_interpreter.cpp b/dbms/src/Flash/tests/gtest_interpreter.cpp index aed9d9e90f9..a6bb8ff1702 100644 --- a/dbms/src/Flash/tests/gtest_interpreter.cpp +++ b/dbms/src/Flash/tests/gtest_interpreter.cpp @@ -12,19 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include namespace DB { namespace tests { -class InterpreterExecuteTest : public DB::tests::InterpreterTest +class InterpreterExecuteTest : public DB::tests::ExecutorTest { public: void initializeContext() override { - InterpreterTest::initializeContext(); + ExecutorTest::initializeContext(); context.addMockTable({"test_db", "test_table"}, {{"s1", TiDB::TP::TypeString}, {"s2", TiDB::TP::TypeString}}); context.addMockTable({"test_db", "test_table_1"}, {{"s1", TiDB::TP::TypeString}, {"s2", TiDB::TP::TypeString}, {"s3", TiDB::TP::TypeString}}); @@ -47,7 +47,7 @@ try .build(context); { String expected = R"( -Union: +Union: SharedQuery x 10: Expression: MergeSorting, limit = 10 @@ -72,7 +72,7 @@ Union: { String expected = R"( -Union: +Union: SharedQuery x 10: Limit, limit = 10 Union: @@ -100,7 +100,7 @@ try .build(context); { String expected = R"( -Union: +Union: Expression x 10: Expression: Expression: @@ -122,7 +122,7 @@ Union: .build(context); { String expected = R"( -Union: +Union: Expression x 10: Expression: Expression: @@ -147,7 +147,7 @@ Union: .build(context); { String expected = R"( -Union: +Union: Expression x 10: Expression: Expression: @@ -181,7 +181,7 @@ Union: .build(context); { String expected = R"( -Union: +Union: SharedQuery x 10: Limit, limit = 10 Union: @@ -244,7 +244,7 @@ CreatingSets HashJoinProbe: Expression: MockTableScan - Union: + Union: Expression x 10: Expression: HashJoinProbe: @@ -260,7 +260,7 @@ CreatingSets .build(context); { String expected = R"( -Union: +Union: Expression x 10: Expression: Expression: @@ -283,7 +283,7 @@ Union: .build(context); { String expected = R"( -Union: +Union: MockExchangeSender x 10 Expression: Expression: @@ -331,7 +331,7 @@ CreatingSets HashJoinProbe: Expression: MockExchangeReceiver - Union: + Union: Expression x 10: Expression: HashJoinProbe: @@ -373,7 +373,7 @@ CreatingSets HashJoinProbe: Expression: MockExchangeReceiver - Union: + Union: MockExchangeSender x 10 Expression: Expression: diff --git a/dbms/src/TestUtils/ExecutorTestUtils.cpp b/dbms/src/TestUtils/ExecutorTestUtils.cpp new file mode 100644 index 00000000000..67a21d12286 --- /dev/null +++ b/dbms/src/TestUtils/ExecutorTestUtils.cpp @@ -0,0 +1,133 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include +#include +#include +#include +namespace DB::tests +{ +DAGContext & ExecutorTest::getDAGContext() +{ + assert(dag_context_ptr != nullptr); + return *dag_context_ptr; +} + +void ExecutorTest::initializeContext() +{ + dag_context_ptr = std::make_unique(1024); + context = MockDAGRequestContext(TiFlashTestEnv::getContext()); + dag_context_ptr->log = Logger::get("executorTest"); +} + +void ExecutorTest::SetUpTestCase() +{ + try + { + DB::registerFunctions(); + DB::registerAggregateFunctions(); + } + catch (DB::Exception &) + { + // Maybe another test has already registered, ignore exception here. + } +} + +void ExecutorTest::initializeClientInfo() +{ + context.context.setCurrentQueryId("test"); + ClientInfo & client_info = context.context.getClientInfo(); + client_info.query_kind = ClientInfo::QueryKind::INITIAL_QUERY; + client_info.interface = ClientInfo::Interface::GRPC; +} + +void ExecutorTest::executeInterpreter(const String & expected_string, const std::shared_ptr & request, size_t concurrency) +{ + DAGContext dag_context(*request, "interpreter_test", concurrency); + context.context.setDAGContext(&dag_context); + // Currently, don't care about regions information in interpreter tests. + DAGQuerySource dag(context.context); + auto res = executeQuery(dag, context.context, false, QueryProcessingStage::Complete); + FmtBuffer fb; + res.in->dumpTree(fb); + ASSERT_EQ(Poco::trim(expected_string), Poco::trim(fb.toString())); +} + +namespace +{ +Block mergeBlocks(Blocks blocks) +{ + if (blocks.empty()) + return {}; + + Block sample_block = blocks.back(); + std::vector actual_cols; + for (const auto & column : sample_block.getColumnsWithTypeAndName()) + { + actual_cols.push_back(column.type->createColumn()); + } + for (const auto & block : blocks) + { + for (size_t i = 0; i < block.columns(); ++i) + { + for (size_t j = 0; j < block.rows(); ++j) + { + actual_cols[i]->insert((*(block.getColumnsWithTypeAndName())[i].column)[j]); + } + } + } + + ColumnsWithTypeAndName actual_columns; + for (size_t i = 0; i < actual_cols.size(); ++i) + actual_columns.push_back({std::move(actual_cols[i]), sample_block.getColumnsWithTypeAndName()[i].type, sample_block.getColumnsWithTypeAndName()[i].name, sample_block.getColumnsWithTypeAndName()[i].column_id}); + return Block(actual_columns); +} + +void readBlock(BlockInputStreamPtr stream, const ColumnsWithTypeAndName & expect_columns) +{ + Blocks actual_blocks; + Block except_block(expect_columns); + stream->readPrefix(); + while (auto block = stream->read()) + { + actual_blocks.push_back(block); + } + stream->readSuffix(); + Block actual_block = mergeBlocks(actual_blocks); + ASSERT_BLOCK_EQ(except_block, actual_block); +} +} // namespace + +void ExecutorTest::executeStreams(const std::shared_ptr & request, std::unordered_map & source_columns_map, const ColumnsWithTypeAndName & expect_columns, size_t concurrency) +{ + DAGContext dag_context(*request, "executor_test", concurrency); + dag_context.setColumnsForTest(source_columns_map); + context.context.setDAGContext(&dag_context); + // Currently, don't care about regions information in tests. + DAGQuerySource dag(context.context); + readBlock(executeQuery(dag, context.context, false, QueryProcessingStage::Complete).in, expect_columns); +} + +void ExecutorTest::executeStreams(const std::shared_ptr & request, const ColumnsWithTypeAndName & expect_columns, size_t concurrency) +{ + executeStreams(request, context.executorIdColumnsMap(), expect_columns, concurrency); +} + +void ExecutorTest::dagRequestEqual(const String & expected_string, const std::shared_ptr & actual) +{ + ASSERT_EQ(Poco::trim(expected_string), Poco::trim(ExecutorSerializer().serialize(actual.get()))); +} + +} // namespace DB::tests diff --git a/dbms/src/TestUtils/InterpreterTestUtils.h b/dbms/src/TestUtils/ExecutorTestUtils.h similarity index 61% rename from dbms/src/TestUtils/InterpreterTestUtils.h rename to dbms/src/TestUtils/ExecutorTestUtils.h index 28d44d3a5f2..977b46abbd2 100644 --- a/dbms/src/TestUtils/InterpreterTestUtils.h +++ b/dbms/src/TestUtils/ExecutorTestUtils.h @@ -27,7 +27,7 @@ namespace DB::tests { void executeInterpreter(const std::shared_ptr & request, Context & context); -class InterpreterTest : public ::testing::Test +class ExecutorTest : public ::testing::Test { protected: void SetUp() override @@ -37,7 +37,7 @@ class InterpreterTest : public ::testing::Test } public: - InterpreterTest() + ExecutorTest() : context(TiFlashTestEnv::getContext()) {} static void SetUpTestCase(); @@ -52,6 +52,40 @@ class InterpreterTest : public ::testing::Test void executeInterpreter(const String & expected_string, const std::shared_ptr & request, size_t concurrency); + void executeStreams( + const std::shared_ptr & request, + std::unordered_map & source_columns_map, + const ColumnsWithTypeAndName & expect_columns, + size_t concurrency = 1); + void executeStreams( + const std::shared_ptr & request, + const ColumnsWithTypeAndName & expect_columns, + size_t concurrency = 1); + + template + ColumnWithTypeAndName toNullableVec(const std::vector::FieldType>> & v) + { + return createColumn>(v); + } + + template + ColumnWithTypeAndName toVec(const std::vector::FieldType> & v) + { + return createColumn(v); + } + + template + ColumnWithTypeAndName toNullableVec(String name, const std::vector::FieldType>> & v) + { + return createColumn>(v, name); + } + + template + ColumnWithTypeAndName toVec(String name, const std::vector::FieldType> & v) + { + return createColumn(v, name); + } + protected: MockDAGRequestContext context; std::unique_ptr dag_context_ptr; diff --git a/dbms/src/TestUtils/InterpreterTestUtils.cpp b/dbms/src/TestUtils/InterpreterTestUtils.cpp deleted file mode 100644 index 2cc096d4095..00000000000 --- a/dbms/src/TestUtils/InterpreterTestUtils.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include -namespace DB::tests -{ -DAGContext & InterpreterTest::getDAGContext() -{ - assert(dag_context_ptr != nullptr); - return *dag_context_ptr; -} - -void InterpreterTest::initializeContext() -{ - dag_context_ptr = std::make_unique(1024); - context = MockDAGRequestContext(TiFlashTestEnv::getContext()); - dag_context_ptr->log = Logger::get("interpreterTest"); -} - -void InterpreterTest::SetUpTestCase() -{ - try - { - DB::registerFunctions(); - DB::registerAggregateFunctions(); - } - catch (DB::Exception &) - { - // Maybe another test has already registered, ignore exception here. - } -} - -void InterpreterTest::initializeClientInfo() -{ - context.context.setCurrentQueryId("test"); - ClientInfo & client_info = context.context.getClientInfo(); - client_info.query_kind = ClientInfo::QueryKind::INITIAL_QUERY; - client_info.interface = ClientInfo::Interface::GRPC; -} - -void InterpreterTest::executeInterpreter(const String & expected_string, const std::shared_ptr & request, size_t concurrency) -{ - DAGContext dag_context(*request, "interpreter_test", concurrency); - context.context.setDAGContext(&dag_context); - // Currently, don't care about regions information in interpreter tests. - DAGQuerySource dag(context.context); - auto res = executeQuery(dag, context.context, false, QueryProcessingStage::Complete); - FmtBuffer fb; - res.in->dumpTree(fb); - ASSERT_EQ(Poco::trim(expected_string), Poco::trim(fb.toString())); -} - -void InterpreterTest::dagRequestEqual(const String & expected_string, const std::shared_ptr & actual) -{ - ASSERT_EQ(Poco::trim(expected_string), Poco::trim(ExecutorSerializer().serialize(actual.get()))); -} - -} // namespace DB::tests diff --git a/dbms/src/TestUtils/mockExecutor.cpp b/dbms/src/TestUtils/mockExecutor.cpp index 3313aae6a93..af939002cff 100644 --- a/dbms/src/TestUtils/mockExecutor.cpp +++ b/dbms/src/TestUtils/mockExecutor.cpp @@ -23,6 +23,8 @@ #include #include +#include + namespace DB::tests { ASTPtr buildColumn(const String & column_name) @@ -274,52 +276,116 @@ DAGRequestBuilder & DAGRequestBuilder::buildAggregation(ASTPtr agg_funcs, ASTPtr return *this; } -void MockDAGRequestContext::addMockTable(const MockTableName & name, const MockColumnInfoList & columns) +void MockDAGRequestContext::addMockTable(const MockTableName & name, const MockColumnInfoList & columnInfos) { - std::vector v_column_info(columns.size()); + std::vector v_column_info(columnInfos.size()); size_t i = 0; - for (const auto & info : columns) + for (const auto & info : columnInfos) { v_column_info[i++] = std::move(info); } mock_tables[name.first + "." + name.second] = v_column_info; } -void MockDAGRequestContext::addMockTable(const String & db, const String & table, const MockColumnInfos & columns) +void MockDAGRequestContext::addMockTable(const String & db, const String & table, const MockColumnInfos & columnInfos) { - mock_tables[db + "." + table] = columns; + mock_tables[db + "." + table] = columnInfos; } -void MockDAGRequestContext::addMockTable(const MockTableName & name, const MockColumnInfos & columns) +void MockDAGRequestContext::addMockTable(const MockTableName & name, const MockColumnInfos & columnInfos) { - mock_tables[name.first + "." + name.second] = columns; + mock_tables[name.first + "." + name.second] = columnInfos; } -void MockDAGRequestContext::addExchangeRelationSchema(String name, const MockColumnInfos & columns) +void MockDAGRequestContext::addExchangeRelationSchema(String name, const MockColumnInfos & columnInfos) { - exchange_schemas[name] = columns; + exchange_schemas[name] = columnInfos; } -void MockDAGRequestContext::addExchangeRelationSchema(String name, const MockColumnInfoList & columns) +void MockDAGRequestContext::addExchangeRelationSchema(String name, const MockColumnInfoList & columnInfos) { - std::vector v_column_info(columns.size()); + std::vector v_column_info(columnInfos.size()); size_t i = 0; - for (const auto & info : columns) + for (const auto & info : columnInfos) { v_column_info[i++] = std::move(info); } exchange_schemas[name] = v_column_info; } +void MockDAGRequestContext::addMockTableColumnData(const String & db, const String & table, ColumnsWithTypeAndName columns) +{ + mock_table_columns[db + "." + table] = columns; +} + +void MockDAGRequestContext::addMockTableColumnData(const MockTableName & name, ColumnsWithTypeAndName columns) +{ + mock_table_columns[name.first + "." + name.second] = columns; +} + +void MockDAGRequestContext::addExchangeReceiverColumnData(const String & name, ColumnsWithTypeAndName columns) +{ + mock_exchange_columns[name] = columns; +} + +void MockDAGRequestContext::addMockTable(const String & db, const String & table, const MockColumnInfoList & columnInfos, ColumnsWithTypeAndName columns) +{ + addMockTable(db, table, columnInfos); + addMockTableColumnData(db, table, columns); +} + +void MockDAGRequestContext::addMockTable(const String & db, const String & table, const MockColumnInfos & columnInfos, ColumnsWithTypeAndName columns) +{ + addMockTable(db, table, columnInfos); + addMockTableColumnData(db, table, columns); +} + +void MockDAGRequestContext::addMockTable(const MockTableName & name, const MockColumnInfoList & columnInfos, ColumnsWithTypeAndName columns) +{ + addMockTable(name, columnInfos); + addMockTableColumnData(name, columns); +} + +void MockDAGRequestContext::addMockTable(const MockTableName & name, const MockColumnInfos & columnInfos, ColumnsWithTypeAndName columns) +{ + addMockTable(name, columnInfos); + addMockTableColumnData(name, columns); +} + +void MockDAGRequestContext::addExchangeReceiver(const String & name, MockColumnInfos columnInfos, ColumnsWithTypeAndName columns) +{ + addExchangeRelationSchema(name, columnInfos); + addExchangeReceiverColumnData(name, columns); +} + +void MockDAGRequestContext::addExchangeReceiver(const String & name, MockColumnInfoList columnInfos, ColumnsWithTypeAndName columns) +{ + addExchangeRelationSchema(name, columnInfos); + addExchangeReceiverColumnData(name, columns); +} + DAGRequestBuilder MockDAGRequestContext::scan(String db_name, String table_name) { - return DAGRequestBuilder(index).mockTable({db_name, table_name}, mock_tables[db_name + "." + table_name]); + auto builder = DAGRequestBuilder(index).mockTable({db_name, table_name}, mock_tables[db_name + "." + table_name]); + // If don't have related columns, user must pass input columns as argument of executeStreams in order to run Executors Tests. + // If user don't want to test executors, it will be safe to run Interpreter Tests. + if (mock_table_columns.find(db_name + "." + table_name) != mock_table_columns.end()) + { + executor_id_columns_map[builder.getRoot()->name] = mock_table_columns[db_name + "." + table_name]; + } + return builder; } DAGRequestBuilder MockDAGRequestContext::receive(String exchange_name) { auto builder = DAGRequestBuilder(index).exchangeReceiver(exchange_schemas[exchange_name]); receiver_source_task_ids_map[builder.getRoot()->name] = {}; + // If don't have related columns, user must pass input columns as argument of executeStreams in order to run Executors Tests. + // If user don't want to test executors, it will be safe to run Interpreter Tests. + if (mock_exchange_columns.find(exchange_name) != mock_exchange_columns.end()) + { + executor_id_columns_map[builder.getRoot()->name] = mock_exchange_columns[exchange_name]; + } return builder; } } // namespace DB::tests \ No newline at end of file diff --git a/dbms/src/TestUtils/mockExecutor.h b/dbms/src/TestUtils/mockExecutor.h index 2f6d3542ebb..88d98158b74 100644 --- a/dbms/src/TestUtils/mockExecutor.h +++ b/dbms/src/TestUtils/mockExecutor.h @@ -14,6 +14,7 @@ #pragma once +#include #include #include #include @@ -122,11 +123,23 @@ class MockDAGRequestContext return DAGRequestBuilder(index); } - void addMockTable(const MockTableName & name, const MockColumnInfoList & columns); - void addMockTable(const String & db, const String & table, const MockColumnInfos & columns); - void addMockTable(const MockTableName & name, const MockColumnInfos & columns); - void addExchangeRelationSchema(String name, const MockColumnInfos & columns); - void addExchangeRelationSchema(String name, const MockColumnInfoList & columns); + void addMockTable(const MockTableName & name, const MockColumnInfoList & columnInfos); + void addMockTable(const String & db, const String & table, const MockColumnInfos & columnInfos); + void addMockTable(const MockTableName & name, const MockColumnInfos & columnInfos); + void addExchangeRelationSchema(String name, const MockColumnInfos & columnInfos); + void addExchangeRelationSchema(String name, const MockColumnInfoList & columnInfos); + void addMockTableColumnData(const String & db, const String & table, ColumnsWithTypeAndName columns); + void addMockTable(const String & db, const String & table, const MockColumnInfoList & columnInfos, ColumnsWithTypeAndName columns); + void addMockTable(const String & db, const String & table, const MockColumnInfos & columnInfos, ColumnsWithTypeAndName columns); + void addMockTable(const MockTableName & name, const MockColumnInfoList & columnInfos, ColumnsWithTypeAndName columns); + void addMockTable(const MockTableName & name, const MockColumnInfos & columnInfos, ColumnsWithTypeAndName columns); + void addMockTableColumnData(const MockTableName & name, ColumnsWithTypeAndName columns); + void addExchangeReceiverColumnData(const String & name, ColumnsWithTypeAndName columns); + void addExchangeReceiver(const String & name, MockColumnInfos columnInfos, ColumnsWithTypeAndName columns); + void addExchangeReceiver(const String & name, MockColumnInfoList columnInfos, ColumnsWithTypeAndName columns); + + std::unordered_map & executorIdColumnsMap() { return executor_id_columns_map; } + DAGRequestBuilder scan(String db_name, String table_name); DAGRequestBuilder receive(String exchange_name); @@ -134,6 +147,9 @@ class MockDAGRequestContext size_t index; std::unordered_map mock_tables; std::unordered_map exchange_schemas; + std::unordered_map mock_table_columns; + std::unordered_map mock_exchange_columns; + std::unordered_map executor_id_columns_map; /// public: // Currently don't support task_id, so the following to structure is useless, diff --git a/dbms/src/TestUtils/tests/gtest_mock_executors.cpp b/dbms/src/TestUtils/tests/gtest_mock_executors.cpp index 6dbf791669f..214148fe47f 100644 --- a/dbms/src/TestUtils/tests/gtest_mock_executors.cpp +++ b/dbms/src/TestUtils/tests/gtest_mock_executors.cpp @@ -12,19 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include namespace DB { namespace tests { -class MockDAGRequestTest : public DB::tests::InterpreterTest +class MockDAGRequestTest : public DB::tests::ExecutorTest { public: void initializeContext() override { - InterpreterTest::initializeContext(); + ExecutorTest::initializeContext(); context.addMockTable({"test_db", "test_table"}, {{"s1", TiDB::TP::TypeString}, {"s2", TiDB::TP::TypeString}}); context.addMockTable({"test_db", "test_table_1"}, {{"s1", TiDB::TP::TypeLong}, {"s2", TiDB::TP::TypeString}, {"s3", TiDB::TP::TypeString}}); From 5b61ae70550624d3bf0b5ca6bac89013ed5a6a4b Mon Sep 17 00:00:00 2001 From: bestwoody <89765764+bestwoody@users.noreply.github.com> Date: Wed, 8 Jun 2022 14:36:30 +0800 Subject: [PATCH 091/127] Improve the performance of partition table in extreme case (#4988) close pingcap/tiflash#4474 --- dbms/src/DataStreams/MultiplexInputStream.h | 246 ++++++++++++++++++ .../Coprocessor/DAGStorageInterpreter.cpp | 23 +- 2 files changed, 264 insertions(+), 5 deletions(-) create mode 100644 dbms/src/DataStreams/MultiplexInputStream.h diff --git a/dbms/src/DataStreams/MultiplexInputStream.h b/dbms/src/DataStreams/MultiplexInputStream.h new file mode 100644 index 00000000000..4fa33262e66 --- /dev/null +++ b/dbms/src/DataStreams/MultiplexInputStream.h @@ -0,0 +1,246 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +#include + +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ +extern const int LOGICAL_ERROR; +} // namespace ErrorCodes + +class MultiPartitionStreamPool +{ +public: + MultiPartitionStreamPool() = default; + + void addPartitionStreams(const BlockInputStreams & cur_streams) + { + if (cur_streams.empty()) + return; + std::unique_lock lk(mu); + streams_queue_by_partition.push_back( + std::make_shared>>()); + for (const auto & stream : cur_streams) + streams_queue_by_partition.back()->push(stream); + added_streams.insert(added_streams.end(), cur_streams.begin(), cur_streams.end()); + } + + std::shared_ptr pickOne() + { + std::unique_lock lk(mu); + if (streams_queue_by_partition.empty()) + return nullptr; + if (streams_queue_id >= static_cast(streams_queue_by_partition.size())) + streams_queue_id = 0; + + auto & q = *streams_queue_by_partition[streams_queue_id]; + std::shared_ptr ret = nullptr; + assert(!q.empty()); + ret = q.front(); + q.pop(); + if (q.empty()) + streams_queue_id = removeQueue(streams_queue_id); + else + streams_queue_id = nextQueueId(streams_queue_id); + return ret; + } + + int exportAddedStreams(BlockInputStreams & ret_streams) + { + std::unique_lock lk(mu); + for (auto & stream : added_streams) + ret_streams.push_back(stream); + return added_streams.size(); + } + + int addedStreamsCnt() + { + std::unique_lock lk(mu); + return added_streams.size(); + } + +private: + int removeQueue(int queue_id) + { + streams_queue_by_partition[queue_id] = nullptr; + if (queue_id != static_cast(streams_queue_by_partition.size()) - 1) + { + swap(streams_queue_by_partition[queue_id], streams_queue_by_partition.back()); + streams_queue_by_partition.pop_back(); + return queue_id; + } + else + { + streams_queue_by_partition.pop_back(); + return 0; + } + } + + int nextQueueId(int queue_id) const + { + if (queue_id + 1 < static_cast(streams_queue_by_partition.size())) + return queue_id + 1; + else + return 0; + } + + static void swap(std::shared_ptr>> & a, + std::shared_ptr>> & b) + { + a.swap(b); + } + + std::vector< + std::shared_ptr>>> + streams_queue_by_partition; + std::vector> added_streams; + int streams_queue_id = 0; + std::mutex mu; +}; + +class MultiplexInputStream final : public IProfilingBlockInputStream +{ +private: + static constexpr auto NAME = "Multiplex"; + +public: + MultiplexInputStream( + std::shared_ptr & shared_pool, + const String & req_id) + : log(Logger::get(NAME, req_id)) + , shared_pool(shared_pool) + { + shared_pool->exportAddedStreams(children); + size_t num_children = children.size(); + if (num_children > 1) + { + Block header = children.at(0)->getHeader(); + for (size_t i = 1; i < num_children; ++i) + assertBlocksHaveEqualStructure( + children[i]->getHeader(), + header, + "MULTIPLEX"); + } + } + + String getName() const override { return NAME; } + + ~MultiplexInputStream() override + { + try + { + if (!all_read) + cancel(false); + } + catch (...) + { + tryLogCurrentException(log, __PRETTY_FUNCTION__); + } + } + + /** Different from the default implementation by trying to stop all sources, + * skipping failed by execution. + */ + void cancel(bool kill) override + { + if (kill) + is_killed = true; + + bool old_val = false; + if (!is_cancelled.compare_exchange_strong( + old_val, + true, + std::memory_order_seq_cst, + std::memory_order_relaxed)) + return; + + if (cur_stream) + { + if (IProfilingBlockInputStream * child = dynamic_cast(&*cur_stream)) + { + child->cancel(kill); + } + } + } + + Block getHeader() const override { return children.at(0)->getHeader(); } + +protected: + /// Do nothing, to make the preparation when underlying InputStream is picked from the pool + void readPrefix() override + { + } + + /** The following options are possible: + * 1. `readImpl` function is called until it returns an empty block. + * Then `readSuffix` function is called and then destructor. + * 2. `readImpl` function is called. At some point, `cancel` function is called perhaps from another thread. + * Then `readSuffix` function is called and then destructor. + * 3. At any time, the object can be destroyed (destructor called). + */ + + Block readImpl() override + { + if (all_read) + return {}; + + Block ret; + while (!cur_stream || !(ret = cur_stream->read())) + { + if (cur_stream) + cur_stream->readSuffix(); // release old inputstream + cur_stream = shared_pool->pickOne(); + if (!cur_stream) + { // shared_pool is empty + all_read = true; + return {}; + } + cur_stream->readPrefix(); + } + return ret; + } + + /// Called either after everything is read, or after cancel. + void readSuffix() override + { + if (!all_read && !is_cancelled) + throw Exception("readSuffix called before all data is read", ErrorCodes::LOGICAL_ERROR); + + if (cur_stream) + { + cur_stream->readSuffix(); + cur_stream = nullptr; + } + } + +private: + LoggerPtr log; + + std::shared_ptr shared_pool; + std::shared_ptr cur_stream; + + bool all_read = false; +}; + +} // namespace DB diff --git a/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp b/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp index df7e504d2c4..14cddd94730 100644 --- a/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp +++ b/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -634,6 +635,9 @@ void DAGStorageInterpreter::buildLocalStreams(DAGPipeline & pipeline, size_t max if (total_local_region_num == 0) return; const auto table_query_infos = generateSelectQueryInfos(); + bool has_multiple_partitions = table_query_infos.size() > 1; + // MultiPartitionStreamPool will be disabled in no partition mode or single-partition case + std::shared_ptr stream_pool = has_multiple_partitions ? std::make_shared() : nullptr; for (const auto & table_query_info : table_query_infos) { DAGPipeline current_pipeline; @@ -642,9 +646,6 @@ void DAGStorageInterpreter::buildLocalStreams(DAGPipeline & pipeline, size_t max size_t region_num = query_info.mvcc_query_info->regions_query_info.size(); if (region_num == 0) continue; - /// calculate weighted max_streams for each partition, note at least 1 stream is needed for each partition - size_t current_max_streams = table_query_infos.size() == 1 ? max_streams : (max_streams * region_num + total_local_region_num - 1) / total_local_region_num; - QueryProcessingStage::Enum from_stage = QueryProcessingStage::FetchColumns; assert(storages_with_structure_lock.find(table_id) != storages_with_structure_lock.end()); auto & storage = storages_with_structure_lock[table_id].storage; @@ -654,7 +655,7 @@ void DAGStorageInterpreter::buildLocalStreams(DAGPipeline & pipeline, size_t max { try { - current_pipeline.streams = storage->read(required_columns, query_info, context, from_stage, max_block_size, current_max_streams); + current_pipeline.streams = storage->read(required_columns, query_info, context, from_stage, max_block_size, max_streams); // After getting streams from storage, we need to validate whether Regions have changed or not after learner read. // (by calling `validateQueryInfo`). In case the key ranges of Regions have changed (Region merge/split), those `streams` @@ -778,7 +779,19 @@ void DAGStorageInterpreter::buildLocalStreams(DAGPipeline & pipeline, size_t max throw; } } - pipeline.streams.insert(pipeline.streams.end(), current_pipeline.streams.begin(), current_pipeline.streams.end()); + if (has_multiple_partitions) + stream_pool->addPartitionStreams(current_pipeline.streams); + else + pipeline.streams.insert(pipeline.streams.end(), current_pipeline.streams.begin(), current_pipeline.streams.end()); + } + if (has_multiple_partitions) + { + String req_info = dag_context.isMPPTask() ? dag_context.getMPPTaskId().toString() : ""; + int exposed_streams_cnt = std::min(static_cast(max_streams), stream_pool->addedStreamsCnt()); + for (int i = 0; i < exposed_streams_cnt; ++i) + { + pipeline.streams.push_back(std::make_shared(stream_pool, req_info)); + } } } From 167d39ff16696ec5d62f3a738b2f60c8c1db8563 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Thu, 9 Jun 2022 17:34:31 +0800 Subject: [PATCH 092/127] Fix some fail cases when enable TASN (#5086) close pingcap/tiflash#5085 --- .../Management/tests/gtest_manual_compact.cpp | 8 +- dbms/src/Storages/DeltaMerge/StoragePool.cpp | 4 +- .../DeltaMerge/tests/MultiSegmentTestUtil.h | 3 + .../tests/gtest_dm_delta_merge_store.cpp | 3 - .../Page/V3/tests/gtest_blob_store.cpp | 3 + .../Page/V3/tests/gtest_page_directory.cpp | 102 +++++++++--------- .../Page/V3/tests/gtest_wal_store.cpp | 48 ++++----- dbms/src/TestUtils/ColumnsToTiPBExpr.cpp | 2 + 8 files changed, 87 insertions(+), 86 deletions(-) diff --git a/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp b/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp index df6c881c306..1e9da93ffe3 100644 --- a/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp +++ b/dbms/src/Flash/Management/tests/gtest_manual_compact.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -48,7 +47,6 @@ class BasicManualCompactTest BasicManualCompactTest() { - log = &Poco::Logger::get(DB::base::TiFlashStorageTestBasic::getCurrentFullTestName()); pk_type = GetParam(); } @@ -63,7 +61,7 @@ class BasicManualCompactTest setupStorage(); // In tests let's only compact one segment. - db_context->setSetting("manual_compact_more_until_ms", UInt64(0)); + db_context->setSetting("manual_compact_more_until_ms", Field(UInt64(0))); // Split into 4 segments, and prepare some delta data for first 3 segments. helper = std::make_unique(*db_context); @@ -116,8 +114,6 @@ class BasicManualCompactTest std::unique_ptr manager; DM::tests::DMTestEnv::PkType pk_type; - - [[maybe_unused]] Poco::Logger * log; }; @@ -315,7 +311,7 @@ CATCH TEST_P(BasicManualCompactTest, CompactMultiple) try { - db_context->setSetting("manual_compact_more_until_ms", UInt64(60 * 1000)); // Hope it's long enough! + db_context->setSetting("manual_compact_more_until_ms", Field(UInt64(60 * 1000))); // Hope it's long enough! auto request = ::kvrpcpb::CompactRequest(); request.set_physical_table_id(TABLE_ID); diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.cpp b/dbms/src/Storages/DeltaMerge/StoragePool.cpp index 752898f9c75..2791a74e9e3 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.cpp +++ b/dbms/src/Storages/DeltaMerge/StoragePool.cpp @@ -624,8 +624,8 @@ PageId StoragePool::newDataPageIdForDTFile(StableDiskDelegator & delegator, cons auto existed_path = delegator.getDTFilePath(dtfile_id, /*throw_on_not_exist=*/false); fiu_do_on(FailPoints::force_set_dtfile_exist_when_acquire_id, { - static size_t fail_point_called = 0; - if (existed_path.empty() && fail_point_called % 10 == 0) + static std::atomic fail_point_called(0); + if (existed_path.empty() && fail_point_called.load() % 10 == 0) { existed_path = ""; } diff --git a/dbms/src/Storages/DeltaMerge/tests/MultiSegmentTestUtil.h b/dbms/src/Storages/DeltaMerge/tests/MultiSegmentTestUtil.h index 7c5b0b2416d..787a521ded3 100644 --- a/dbms/src/Storages/DeltaMerge/tests/MultiSegmentTestUtil.h +++ b/dbms/src/Storages/DeltaMerge/tests/MultiSegmentTestUtil.h @@ -88,6 +88,7 @@ class MultiSegmentTestUtil : private boost::noncopyable // Check there is only one segment ASSERT_EQ(store->segments.size(), 1); const auto & [_key, seg] = *store->segments.begin(); + (void)_key; ASSERT_EQ(seg->getDelta()->getRows(), n_avg_rows_per_segment * 4); ASSERT_EQ(seg->getStable()->getRows(), 0); @@ -108,6 +109,7 @@ class MultiSegmentTestUtil : private boost::noncopyable auto segment_idx = 0; for (auto & [_key, seg] : store->segments) { + (void)_key; LOG_FMT_INFO(log, "Segment #{}: Range = {}", segment_idx, seg->getRowKeyRange().toDebugString()); ASSERT_EQ(seg->getDelta()->getRows(), 0); ASSERT_GT(seg->getStable()->getRows(), 0); // We don't check the exact rows of each segment. @@ -147,6 +149,7 @@ class MultiSegmentTestUtil : private boost::noncopyable auto segment_idx = 0; for (auto & [_key, seg] : store->segments) { + (void)_key; ASSERT_EQ(seg->getDelta()->getRows(), expected_delta_rows[segment_idx]) << "Assert failed for segment #" << segment_idx; ASSERT_EQ(seg->getStable()->getRows(), expected_stable_rows[segment_idx]) << "Assert failed for segment #" << segment_idx; segment_idx++; diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp index e934f7a2049..d46e1b7aa36 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_delta_merge_store.cpp @@ -3564,7 +3564,6 @@ class DeltaMergeStoreMergeDeltaBySegmentTest public: DeltaMergeStoreMergeDeltaBySegmentTest() { - log = &Poco::Logger::get(DB::base::TiFlashStorageTestBasic::getCurrentFullTestName()); std::tie(ps_ver, pk_type) = GetParam(); } @@ -3607,8 +3606,6 @@ class DeltaMergeStoreMergeDeltaBySegmentTest UInt64 ps_ver; DMTestEnv::PkType pk_type; - - [[maybe_unused]] Poco::Logger * log; }; INSTANTIATE_TEST_CASE_P( diff --git a/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp b/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp index 048140ed04f..94bb69045ba 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp @@ -82,6 +82,7 @@ try stats.restoreByEntry(PageEntryV3{ .file_id = file_id1, .size = 128, + .padded_size = 0, .tag = 0, .offset = 1024, .checksum = 0x4567, @@ -89,6 +90,7 @@ try stats.restoreByEntry(PageEntryV3{ .file_id = file_id1, .size = 512, + .padded_size = 0, .tag = 0, .offset = 2048, .checksum = 0x4567, @@ -96,6 +98,7 @@ try stats.restoreByEntry(PageEntryV3{ .file_id = file_id2, .size = 512, + .padded_size = 0, .tag = 0, .offset = 2048, .checksum = 0x4567, diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp index 6e2b0efa1ea..83e07f75d37 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp @@ -75,7 +75,7 @@ try auto snap0 = dir->createSnapshot(); EXPECT_ENTRY_NOT_EXIST(dir, 1, snap0); - PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry1{.file_id = 1, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(1, entry1); @@ -85,7 +85,7 @@ try auto snap1 = dir->createSnapshot(); EXPECT_ENTRY_EQ(entry1, dir, 1, snap1); - PageEntryV3 entry2{.file_id = 2, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry2{.file_id = 2, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(2, entry2); @@ -102,7 +102,7 @@ try EXPECT_ENTRIES_EQ(expected_entries, dir, ids, snap2); } - PageEntryV3 entry2_v2{.file_id = 2 + 102, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry2_v2{.file_id = 2 + 102, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.del(2); @@ -123,7 +123,7 @@ try auto snap0 = dir->createSnapshot(); EXPECT_ENTRY_NOT_EXIST(dir, page_id, snap0); - PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry1{.file_id = 1, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(page_id, entry1); @@ -133,7 +133,7 @@ try auto snap1 = dir->createSnapshot(); EXPECT_ENTRY_EQ(entry1, dir, page_id, snap1); - PageEntryV3 entry2{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x1234, .checksum = 0x4567}; + PageEntryV3 entry2{.file_id = 1, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x1234, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(page_id, entry2); @@ -151,7 +151,7 @@ try // Put identical page within one `edit` page_id++; - PageEntryV3 entry3{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x12345, .checksum = 0x4567}; + PageEntryV3 entry3{.file_id = 1, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x12345, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(page_id, entry1); @@ -172,8 +172,8 @@ CATCH TEST_F(PageDirectoryTest, ApplyPutDelRead) try { - PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry2{.file_id = 2, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry1{.file_id = 1, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry2{.file_id = 2, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(1, entry1); @@ -185,8 +185,8 @@ try EXPECT_ENTRY_EQ(entry1, dir, 1, snap1); EXPECT_ENTRY_EQ(entry2, dir, 2, snap1); - PageEntryV3 entry3{.file_id = 3, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry4{.file_id = 4, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry3{.file_id = 3, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry4{.file_id = 4, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.del(2); @@ -217,8 +217,8 @@ CATCH TEST_F(PageDirectoryTest, ApplyUpdateOnRefEntries) try { - PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry2{.file_id = 2, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry1{.file_id = 1, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry2{.file_id = 2, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(1, entry1); @@ -236,14 +236,14 @@ try EXPECT_ENTRY_EQ(entry2, dir, 3, snap1); // Update on ref page is not allowed - PageEntryV3 entry_updated{.file_id = 999, .size = 16, .tag = 0, .offset = 0x123, .checksum = 0x123}; + PageEntryV3 entry_updated{.file_id = 999, .size = 16, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x123}; { PageEntriesEdit edit; edit.put(3, entry_updated); ASSERT_ANY_THROW(dir->apply(std::move(edit))); } - PageEntryV3 entry_updated2{.file_id = 777, .size = 16, .tag = 0, .offset = 0x123, .checksum = 0x123}; + PageEntryV3 entry_updated2{.file_id = 777, .size = 16, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x123}; { PageEntriesEdit edit; edit.put(2, entry_updated2); @@ -255,8 +255,8 @@ CATCH TEST_F(PageDirectoryTest, ApplyDeleteOnRefEntries) try { - PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry2{.file_id = 2, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry1{.file_id = 1, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry2{.file_id = 2, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(1, entry1); @@ -305,8 +305,8 @@ CATCH TEST_F(PageDirectoryTest, ApplyRefOnRefEntries) try { - PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry2{.file_id = 2, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry1{.file_id = 1, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry2{.file_id = 2, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(1, entry1); @@ -343,8 +343,8 @@ CATCH TEST_F(PageDirectoryTest, ApplyDuplicatedRefEntries) try { - PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry2{.file_id = 2, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry1{.file_id = 1, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry2{.file_id = 2, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(1, entry1); @@ -410,8 +410,8 @@ CATCH TEST_F(PageDirectoryTest, ApplyCollapseDuplicatedRefEntries) try { - PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry2{.file_id = 2, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry1{.file_id = 1, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry2{.file_id = 2, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(1, entry1); @@ -447,9 +447,9 @@ CATCH TEST_F(PageDirectoryTest, ApplyRefToNotExistEntry) try { - PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry2{.file_id = 2, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry3{.file_id = 3, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry1{.file_id = 1, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry2{.file_id = 2, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry3{.file_id = 3, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(1, entry1); @@ -628,12 +628,12 @@ try } CATCH -#define INSERT_BLOBID_ENTRY(BLOBID, VERSION) \ - PageEntryV3 entry_v##VERSION{.file_id = (BLOBID), .size = (VERSION), .tag = 0, .offset = 0x123, .checksum = 0x4567}; \ +#define INSERT_BLOBID_ENTRY(BLOBID, VERSION) \ + PageEntryV3 entry_v##VERSION{.file_id = (BLOBID), .size = (VERSION), .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; \ entries.createNewEntry(PageVersion(VERSION), entry_v##VERSION); #define INSERT_ENTRY(VERSION) INSERT_BLOBID_ENTRY(1, VERSION) -#define INSERT_GC_ENTRY(VERSION, EPOCH) \ - PageEntryV3 entry_gc_v##VERSION##_##EPOCH{.file_id = 2, .size = 100 * (VERSION) + (EPOCH), .tag = 0, .offset = 0x234, .checksum = 0x5678}; \ +#define INSERT_GC_ENTRY(VERSION, EPOCH) \ + PageEntryV3 entry_gc_v##VERSION##_##EPOCH{.file_id = 2, .size = 100 * (VERSION) + (EPOCH), .padded_size = 0, .tag = 0, .offset = 0x234, .checksum = 0x5678}; \ entries.createNewEntry(PageVersion((VERSION), (EPOCH)), entry_gc_v##VERSION##_##EPOCH); class VersionedEntriesTest : public ::testing::Test @@ -1271,12 +1271,12 @@ class PageDirectoryGCTest : public PageDirectoryTest { }; -#define INSERT_ENTRY_TO(PAGE_ID, VERSION, BLOB_FILE_ID) \ - PageEntryV3 entry_v##VERSION{.file_id = (BLOB_FILE_ID), .size = (VERSION), .tag = 0, .offset = 0x123, .checksum = 0x4567}; \ - { \ - PageEntriesEdit edit; \ - edit.put((PAGE_ID), entry_v##VERSION); \ - dir->apply(std::move(edit)); \ +#define INSERT_ENTRY_TO(PAGE_ID, VERSION, BLOB_FILE_ID) \ + PageEntryV3 entry_v##VERSION{.file_id = (BLOB_FILE_ID), .size = (VERSION), .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; \ + { \ + PageEntriesEdit edit; \ + edit.put((PAGE_ID), entry_v##VERSION); \ + dir->apply(std::move(edit)); \ } // Insert an entry into mvcc directory #define INSERT_ENTRY(PAGE_ID, VERSION) INSERT_ENTRY_TO(PAGE_ID, VERSION, 1) @@ -1566,7 +1566,7 @@ try INSERT_ENTRY_ACQ_SNAP(page_id, 5); INSERT_ENTRY(another_page_id, 6); INSERT_ENTRY(another_page_id, 7); - PageEntryV3 entry_v8{.file_id = 1, .size = 8, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_v8{.file_id = 1, .size = 8, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.del(page_id); @@ -1756,7 +1756,7 @@ TEST_F(PageDirectoryGCTest, GCOnRefedEntries) try { // 10->entry1, 11->10=>11->entry1; del 10->entry1 - PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry1{.file_id = 1, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(10, entry1); @@ -1793,7 +1793,7 @@ TEST_F(PageDirectoryGCTest, GCOnRefedEntries2) try { // 10->entry1, 11->10=>11->entry1; del 10->entry1 - PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry1{.file_id = 1, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(10, entry1); @@ -1836,7 +1836,7 @@ TEST_F(PageDirectoryGCTest, UpsertOnRefedEntries) try { // 10->entry1, 11->10, 12->10 - PageEntryV3 entry1{.file_id = 1, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry1{.file_id = 1, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(10, entry1); @@ -1860,7 +1860,7 @@ try } // upsert 10->entry2 - PageEntryV3 entry2{.file_id = 2, .size = 1024, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry2{.file_id = 2, .size = 1024, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; auto full_gc_entries = dir->getEntriesByBlobIds({1}); @@ -2024,10 +2024,10 @@ try return d; }; - PageEntryV3 entry_1_v1{.file_id = 1, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_1_v2{.file_id = 1, .size = 2, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_2_v1{.file_id = 2, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_2_v2{.file_id = 2, .size = 2, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_1_v1{.file_id = 1, .size = 1, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_1_v2{.file_id = 1, .size = 2, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_2_v1{.file_id = 2, .size = 1, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_2_v2{.file_id = 2, .size = 2, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(1, entry_1_v1); @@ -2055,8 +2055,8 @@ try // 10->ext, 11->10, del 10->ext // 50->entry, 51->50, 52->51=>50, del 50 - PageEntryV3 entry_50{.file_id = 1, .size = 50, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_60{.file_id = 1, .size = 90, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_50{.file_id = 1, .size = 50, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_60{.file_id = 1, .size = 90, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; { PageEntriesEdit edit; edit.del(2); @@ -2218,9 +2218,9 @@ try Poco::File(fmt::format("{}/{}{}", path, BlobFile::BLOB_PREFIX_NAME, file_id1)).createFile(); Poco::File(fmt::format("{}/{}{}", path, BlobFile::BLOB_PREFIX_NAME, file_id2)).createFile(); - PageEntryV3 entry_1_v1{.file_id = file_id1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_5_v1{.file_id = file_id2, .size = 255, .tag = 0, .offset = 0x100, .checksum = 0x4567}; - PageEntryV3 entry_5_v2{.file_id = file_id2, .size = 255, .tag = 0, .offset = 0x400, .checksum = 0x4567}; + PageEntryV3 entry_1_v1{.file_id = file_id1, .size = 7890, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_5_v1{.file_id = file_id2, .size = 255, .padded_size = 0, .tag = 0, .offset = 0x100, .checksum = 0x4567}; + PageEntryV3 entry_5_v2{.file_id = file_id2, .size = 255, .padded_size = 0, .tag = 0, .offset = 0x400, .checksum = 0x4567}; { PageEntriesEdit edit; edit.put(1, entry_1_v1); @@ -2275,8 +2275,8 @@ CATCH TEST_F(PageDirectoryGCTest, CleanAfterDecreaseRef) try { - PageEntryV3 entry_50_1{.file_id = 1, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_50_2{.file_id = 2, .size = 7890, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_50_1{.file_id = 1, .size = 7890, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_50_2{.file_id = 2, .size = 7890, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; auto restore_from_edit = [](const PageEntriesEdit & edit) { auto ctx = ::DB::tests::TiFlashTestEnv::getContext(); diff --git a/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp b/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp index 6d47adabbc5..b4e6c2d9204 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_wal_store.cpp @@ -34,8 +34,8 @@ namespace DB::PS::V3::tests { TEST(WALSeriTest, AllPuts) { - PageEntryV3 entry_p1{.file_id = 1, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_p2{.file_id = 1, .size = 2, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p1{.file_id = 1, .size = 1, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p2{.file_id = 1, .size = 2, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageVersion ver20(/*seq=*/20); PageEntriesEdit edit; edit.put(1, entry_p1); @@ -56,8 +56,8 @@ TEST(WALSeriTest, AllPuts) TEST(WALSeriTest, PutsAndRefsAndDels) try { - PageEntryV3 entry_p3{.file_id = 1, .size = 3, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_p5{.file_id = 1, .size = 5, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p3{.file_id = 1, .size = 3, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p5{.file_id = 1, .size = 5, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageVersion ver21(/*seq=*/21); PageEntriesEdit edit; edit.put(3, entry_p3); @@ -104,9 +104,9 @@ CATCH TEST(WALSeriTest, Upserts) { - PageEntryV3 entry_p1_2{.file_id = 2, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_p3_2{.file_id = 2, .size = 3, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_p5_2{.file_id = 2, .size = 5, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p1_2{.file_id = 2, .size = 1, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p3_2{.file_id = 2, .size = 3, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p5_2{.file_id = 2, .size = 5, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageVersion ver20_1(/*seq=*/20, /*epoch*/ 1); PageVersion ver21_1(/*seq=*/21, /*epoch*/ 1); PageEntriesEdit edit; @@ -164,7 +164,7 @@ TEST(WALSeriTest, RefExternalAndEntry) { PageEntriesEdit edit; - PageEntryV3 entry_p1_2{.file_id = 2, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p1_2{.file_id = 2, .size = 1, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; edit.varEntry(1, ver1_0, entry_p1_2, 2); edit.varDel(1, ver2_0); edit.varRef(2, ver3_0, 1); @@ -405,8 +405,8 @@ try ASSERT_NE(wal, nullptr); // Stage 2. Apply with only puts - PageEntryV3 entry_p1{.file_id = 1, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_p2{.file_id = 1, .size = 2, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p1{.file_id = 1, .size = 1, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p2{.file_id = 1, .size = 2, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageVersion ver20(/*seq=*/20); { PageEntriesEdit edit; @@ -435,8 +435,8 @@ try } // Stage 3. Apply with puts and refs - PageEntryV3 entry_p3{.file_id = 1, .size = 3, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_p5{.file_id = 1, .size = 5, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p3{.file_id = 1, .size = 3, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p5{.file_id = 1, .size = 5, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageVersion ver21(/*seq=*/21); { PageEntriesEdit edit; @@ -468,9 +468,9 @@ try // Stage 4. Apply with delete and upsert - PageEntryV3 entry_p1_2{.file_id = 2, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_p3_2{.file_id = 2, .size = 3, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_p5_2{.file_id = 2, .size = 5, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p1_2{.file_id = 2, .size = 1, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p3_2{.file_id = 2, .size = 3, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p5_2{.file_id = 2, .size = 5, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageVersion ver20_1(/*seq=*/20, /*epoch*/ 1); PageVersion ver21_1(/*seq=*/21, /*epoch*/ 1); { @@ -514,8 +514,8 @@ try std::vector size_each_edit; // Stage 1. Apply with only puts - PageEntryV3 entry_p1{.file_id = 1, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_p2{.file_id = 1, .size = 2, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p1{.file_id = 1, .size = 1, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p2{.file_id = 1, .size = 2, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageVersion ver20(/*seq=*/20); { PageEntriesEdit edit; @@ -526,8 +526,8 @@ try } // Stage 2. Apply with puts and refs - PageEntryV3 entry_p3{.file_id = 1, .size = 3, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_p5{.file_id = 1, .size = 5, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p3{.file_id = 1, .size = 3, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p5{.file_id = 1, .size = 5, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageVersion ver21(/*seq=*/21); { PageEntriesEdit edit; @@ -540,9 +540,9 @@ try } // Stage 3. Apply with delete and upsert - PageEntryV3 entry_p1_2{.file_id = 2, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_p3_2{.file_id = 2, .size = 3, .tag = 0, .offset = 0x123, .checksum = 0x4567}; - PageEntryV3 entry_p5_2{.file_id = 2, .size = 5, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p1_2{.file_id = 2, .size = 1, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p3_2{.file_id = 2, .size = 3, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry_p5_2{.file_id = 2, .size = 5, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageVersion ver20_1(/*seq=*/20, /*epoch*/ 1); PageVersion ver21_1(/*seq=*/21, /*epoch*/ 1); { @@ -615,7 +615,7 @@ try PageVersion ver(/*seq*/ 32); for (size_t i = 0; i < num_edits_test; ++i) { - PageEntryV3 entry{.file_id = 2, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry{.file_id = 2, .size = 1, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; PageEntriesEdit edit; const size_t num_pages_put = d_20(rd); for (size_t p = 0; p < num_pages_put; ++p) @@ -660,7 +660,7 @@ try .persisted_log_files = persisted_log_files}; PageEntriesEdit snap_edit; - PageEntryV3 entry{.file_id = 2, .size = 1, .tag = 0, .offset = 0x123, .checksum = 0x4567}; + PageEntryV3 entry{.file_id = 2, .size = 1, .padded_size = 0, .tag = 0, .offset = 0x123, .checksum = 0x4567}; std::uniform_int_distribution<> d_10000(0, 10000); // just fill in some random entry for (size_t i = 0; i < 70; ++i) diff --git a/dbms/src/TestUtils/ColumnsToTiPBExpr.cpp b/dbms/src/TestUtils/ColumnsToTiPBExpr.cpp index dcf727614b1..ea19ff08dd3 100644 --- a/dbms/src/TestUtils/ColumnsToTiPBExpr.cpp +++ b/dbms/src/TestUtils/ColumnsToTiPBExpr.cpp @@ -36,6 +36,7 @@ void columnToTiPBExpr(tipb::Expr * expr, const ColumnWithTypeAndName column, siz if (column.column->isColumnNullable()) { auto [col, null_map] = removeNullable(column.column.get()); + (void)null_map; is_const = col->isColumnConst(); } } @@ -97,6 +98,7 @@ void columnsToTiPBExprForTiDBCast( if (type_column.column->isColumnNullable()) { auto [col, null_map] = removeNullable(type_column.column.get()); + (void)null_map; is_const = col->isColumnConst(); } } From ba725cc09fe85074b663e8fbafa117d7c3b7af87 Mon Sep 17 00:00:00 2001 From: JaySon Date: Thu, 9 Jun 2022 20:18:30 +0800 Subject: [PATCH 093/127] PageStorage: Fix entry.tag after full gc && add more debug message (#5094) ref pingcap/tiflash#5076, close pingcap/tiflash#5093 --- .../Storages/DeltaMerge/DeltaMergeStore.cpp | 5 +- dbms/src/Storages/Page/PageUtil.h | 2 +- .../Page/V2/tests/gtest_page_util.cpp | 3 ++ dbms/src/Storages/Page/V3/BlobStore.cpp | 42 +++++++++------- dbms/src/Storages/Page/V3/BlobStore.h | 2 +- .../Page/V3/tests/gtest_blob_store.cpp | 9 ++-- .../Page/V3/tests/gtest_page_storage.cpp | 49 +++++++++++++++++++ 7 files changed, 87 insertions(+), 25 deletions(-) diff --git a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp index a74404f3dbb..195ed5c53c2 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp +++ b/dbms/src/Storages/DeltaMerge/DeltaMergeStore.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -1137,9 +1138,11 @@ BlockInputStreams DeltaMergeStore::readRaw(const Context & db_context, } fiu_do_on(FailPoints::force_slow_page_storage_snapshot_release, { - std::thread thread_hold_snapshots([tasks]() { + std::thread thread_hold_snapshots([this, tasks]() { + LOG_FMT_WARNING(log, "failpoint force_slow_page_storage_snapshot_release begin"); std::this_thread::sleep_for(std::chrono::seconds(5 * 60)); (void)tasks; + LOG_FMT_WARNING(log, "failpoint force_slow_page_storage_snapshot_release end"); }); thread_hold_snapshots.detach(); }); diff --git a/dbms/src/Storages/Page/PageUtil.h b/dbms/src/Storages/Page/PageUtil.h index cebcbdb27f2..b0d8f0f88c8 100644 --- a/dbms/src/Storages/Page/PageUtil.h +++ b/dbms/src/Storages/Page/PageUtil.h @@ -281,7 +281,7 @@ void readFile(T & file, } if (unlikely(bytes_read != expected_bytes)) - throw DB::TiFlashException(fmt::format("No enough data in file {}, read bytes: {} , expected bytes: {}", file->getFileName(), bytes_read, expected_bytes), + throw DB::TiFlashException(fmt::format("No enough data in file {}, read bytes: {}, expected bytes: {}, offset: {}", file->getFileName(), bytes_read, expected_bytes, offset), Errors::PageStorage::FileSizeNotMatch); } diff --git a/dbms/src/Storages/Page/V2/tests/gtest_page_util.cpp b/dbms/src/Storages/Page/V2/tests/gtest_page_util.cpp index e72c7a87541..c4dd2178eb9 100644 --- a/dbms/src/Storages/Page/V2/tests/gtest_page_util.cpp +++ b/dbms/src/Storages/Page/V2/tests/gtest_page_util.cpp @@ -17,6 +17,7 @@ #include #include #include +#include namespace DB { @@ -30,6 +31,7 @@ namespace tests static const std::string FileName = "page_util_test"; TEST(PageUtilsTest, ReadWriteFile) +try { ::remove(FileName.c_str()); @@ -52,6 +54,7 @@ TEST(PageUtilsTest, ReadWriteFile) ::remove(FileName.c_str()); } +CATCH TEST(PageUtilsTest, FileNotExists) { diff --git a/dbms/src/Storages/Page/V3/BlobStore.cpp b/dbms/src/Storages/Page/V3/BlobStore.cpp index d5f71841b91..37a4fd429f4 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.cpp +++ b/dbms/src/Storages/Page/V3/BlobStore.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -555,7 +556,7 @@ void BlobStore::read(PageIDAndEntriesV3 & entries, const PageHandler & handler, for (const auto & [page_id_v3, entry] : entries) { - auto blob_file = read(entry.file_id, entry.offset, data_buf, entry.size, read_limiter); + auto blob_file = read(page_id_v3, entry.file_id, entry.offset, data_buf, entry.size, read_limiter); if constexpr (BLOBSTORE_CHECKSUM_ON_READ) { @@ -635,7 +636,7 @@ PageMap BlobStore::read(FieldReadInfos & to_read, const ReadLimiterPtr & read_li // TODO: Continuously fields can read by one system call. const auto [beg_offset, end_offset] = entry.getFieldOffsets(field_index); const auto size_to_read = end_offset - beg_offset; - auto blob_file = read(entry.file_id, entry.offset + beg_offset, write_offset, size_to_read, read_limiter); + auto blob_file = read(page_id_v3, entry.file_id, entry.offset + beg_offset, write_offset, size_to_read, read_limiter); fields_offset_in_page.emplace(field_index, read_size_this_entry); if constexpr (BLOBSTORE_CHECKSUM_ON_READ) @@ -732,7 +733,7 @@ PageMap BlobStore::read(PageIDAndEntriesV3 & entries, const ReadLimiterPtr & rea PageMap page_map; for (const auto & [page_id_v3, entry] : entries) { - auto blob_file = read(entry.file_id, entry.offset, pos, entry.size, read_limiter); + auto blob_file = read(page_id_v3, entry.file_id, entry.offset, pos, entry.size, read_limiter); if constexpr (BLOBSTORE_CHECKSUM_ON_READ) { @@ -797,7 +798,7 @@ Page BlobStore::read(const PageIDAndEntryV3 & id_entry, const ReadLimiterPtr & r free(p, buf_size); }); - auto blob_file = read(entry.file_id, entry.offset, data_buf, buf_size, read_limiter); + auto blob_file = read(page_id_v3, entry.file_id, entry.offset, data_buf, buf_size, read_limiter); if constexpr (BLOBSTORE_CHECKSUM_ON_READ) { ChecksumClass digest; @@ -824,11 +825,20 @@ Page BlobStore::read(const PageIDAndEntryV3 & id_entry, const ReadLimiterPtr & r return page; } -BlobFilePtr BlobStore::read(BlobFileId blob_id, BlobFileOffset offset, char * buffers, size_t size, const ReadLimiterPtr & read_limiter, bool background) +BlobFilePtr BlobStore::read(const PageIdV3Internal & page_id_v3, BlobFileId blob_id, BlobFileOffset offset, char * buffers, size_t size, const ReadLimiterPtr & read_limiter, bool background) { assert(buffers != nullptr); - auto blob_file = getBlobFile(blob_id); - blob_file->read(buffers, offset, size, read_limiter, background); + BlobFilePtr blob_file = getBlobFile(blob_id); + try + { + blob_file->read(buffers, offset, size, read_limiter, background); + } + catch (DB::Exception & e) + { + // add debug message + e.addMessage(fmt::format("(error while reading page data [page_id={}] [blob_id={}] [offset={}] [size={}] [background={}])", page_id_v3, blob_id, offset, size, background)); + e.rethrow(); + } return blob_file; } @@ -1117,21 +1127,15 @@ PageEntriesEdit BlobStore::gc(std::map & std::tie(blobfile_id, file_offset_beg) = getPosFromStats(next_alloc_size); } - PageEntryV3 new_entry; - - read(file_id, entry.offset, data_pos, entry.size, read_limiter, /*background*/ true); - - // No need do crc again, crc won't be changed. - new_entry.checksum = entry.checksum; - - // Need copy the field_offsets - new_entry.field_offsets = entry.field_offsets; - - // Entry size won't be changed. - new_entry.size = entry.size; + // Read the data into buffer by old entry + read(page_id, file_id, entry.offset, data_pos, entry.size, read_limiter, /*background*/ true); + // Most vars of the entry is not changed, but the file id and offset + // need to be updated. + PageEntryV3 new_entry = entry; new_entry.file_id = blobfile_id; new_entry.offset = file_offset_beg + offset_in_data; + new_entry.padded_size = 0; // reset padded size to be zero offset_in_data += new_entry.size; data_pos += new_entry.size; diff --git a/dbms/src/Storages/Page/V3/BlobStore.h b/dbms/src/Storages/Page/V3/BlobStore.h index 24bf4652123..6b139b98557 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.h +++ b/dbms/src/Storages/Page/V3/BlobStore.h @@ -296,7 +296,7 @@ class BlobStore : private Allocator PageEntriesEdit handleLargeWrite(DB::WriteBatch & wb, const WriteLimiterPtr & write_limiter = nullptr); - BlobFilePtr read(BlobFileId blob_id, BlobFileOffset offset, char * buffers, size_t size, const ReadLimiterPtr & read_limiter = nullptr, bool background = false); + BlobFilePtr read(const PageIdV3Internal & page_id_v3, BlobFileId blob_id, BlobFileOffset offset, char * buffers, size_t size, const ReadLimiterPtr & read_limiter = nullptr, bool background = false); /** * Ask BlobStats to get a span from BlobStat. diff --git a/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp b/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp index 94bb69045ba..fdd08c7cb8e 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp @@ -534,7 +534,8 @@ TEST_F(BlobStoreTest, testWriteRead) ASSERT_EQ(record.entry.file_id, 1); // Read directly from the file - blob_store.read(record.entry.file_id, + blob_store.read(buildV3Id(TEST_NAMESPACE_ID, page_id), + record.entry.file_id, record.entry.offset, c_buff_read + index * buff_size, record.entry.size, @@ -634,7 +635,8 @@ TEST_F(BlobStoreTest, testWriteReadWithIOLimiter) { for (const auto & record : edits[i].getRecords()) { - blob_store.read(record.entry.file_id, + blob_store.read(buildV3Id(TEST_NAMESPACE_ID, page_id), + record.entry.file_id, record.entry.offset, c_buff_read + i * buff_size, record.entry.size, @@ -812,7 +814,8 @@ TEST_F(BlobStoreTest, testFeildOffsetWriteRead) ASSERT_EQ(check_field_sizes, offsets); // Read - blob_store.read(record.entry.file_id, + blob_store.read(buildV3Id(TEST_NAMESPACE_ID, page_id), + record.entry.file_id, record.entry.offset, c_buff_read + index * buff_size, record.entry.size, diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp index f7ba33c46c8..f9ef25cb973 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp @@ -1441,6 +1441,55 @@ try } CATCH +TEST_F(PageStorageTest, EntryTagAfterFullGC) +try +{ + { + PageStorage::Config config; + config.blob_heavy_gc_valid_rate = 1.0; /// always run full gc + page_storage = reopenWithConfig(config); + } + + const size_t buf_sz = 1024; + char c_buff[buf_sz]; + + for (size_t i = 0; i < buf_sz; ++i) + { + c_buff[i] = i % 0xff; + } + + PageId page_id = 120; + UInt64 tag = 12345; + { + WriteBatch batch; + batch.putPage(page_id, tag, std::make_shared(c_buff, buf_sz), buf_sz, {}); + page_storage->write(std::move(batch)); + } + + { + auto entry = page_storage->getEntry(page_id); + ASSERT_EQ(entry.tag, tag); + auto page = page_storage->read(page_id); + for (size_t i = 0; i < buf_sz; ++i) + { + EXPECT_EQ(*(page.data.begin() + i), static_cast(i % 0xff)); + } + } + + auto done_full_gc = page_storage->gc(); + EXPECT_TRUE(done_full_gc); + + { + auto entry = page_storage->getEntry(page_id); + ASSERT_EQ(entry.tag, tag); + auto page = page_storage->read(page_id); + for (size_t i = 0; i < buf_sz; ++i) + { + EXPECT_EQ(*(page.data.begin() + i), static_cast(i % 0xff)); + } + } +} +CATCH } // namespace PS::V3::tests } // namespace DB From a9b322ab2a467827d0ce0574336fb214468362d8 Mon Sep 17 00:00:00 2001 From: Lloyd-Pottiger <60744015+Lloyd-Pottiger@users.noreply.github.com> Date: Fri, 10 Jun 2022 10:54:31 +0800 Subject: [PATCH 094/127] feat: add page_stress_testing as a subcommand of tiflash (#5038) close pingcap/tiflash#5037 --- CMakeLists.txt | 3 - dbms/CMakeLists.txt | 13 ++-- dbms/src/Server/CMakeLists.txt | 4 ++ dbms/src/Server/config_tools.h.in | 1 + dbms/src/Server/main.cpp | 8 ++- dbms/src/Storages/CMakeLists.txt | 9 ++- .../DeltaMerge/tools/workload/Main.cpp | 22 ------ .../{tools => }/workload/CMakeLists.txt | 3 - .../{tools => }/workload/DTWorkload.cpp | 20 +++--- .../{tools => }/workload/DTWorkload.h | 2 +- .../{tools => }/workload/DataGenerator.cpp | 16 +++-- .../{tools => }/workload/DataGenerator.h | 2 +- .../DeltaMerge/{tools => }/workload/Handle.h | 18 ++--- .../{tools => }/workload/KeyGenerator.cpp | 10 +-- .../{tools => }/workload/KeyGenerator.h | 4 +- .../{tools => }/workload/Limiter.cpp | 10 +-- .../DeltaMerge/{tools => }/workload/Limiter.h | 2 +- .../{tools => }/workload/MainEntry.cpp | 8 +-- .../{tools => }/workload/Options.cpp | 4 +- .../DeltaMerge/{tools => }/workload/Options.h | 0 .../workload/ReadColumnsGenerator.h | 4 +- .../{tools => }/workload/TableGenerator.cpp | 8 +-- .../{tools => }/workload/TableGenerator.h | 2 +- .../{tools => }/workload/TimestampGenerator.h | 0 .../DeltaMerge/{tools => }/workload/Utils.cpp | 8 +-- .../DeltaMerge/{tools => }/workload/Utils.h | 0 dbms/src/Storages/Page/CMakeLists.txt | 10 --- .../Page/stress/stress_page_storage.cpp | 46 ------------ .../tools => Page/workload}/CMakeLists.txt | 7 +- .../workload/HeavyMemoryCostInGC.cpp | 5 +- .../Page/{stress => }/workload/HeavyRead.cpp | 7 +- .../workload/HeavySkewWriteRead.cpp | 7 +- .../Page/{stress => }/workload/HeavyWrite.cpp | 7 +- .../workload/HighValidBigFileGC.cpp | 5 +- .../workload/HoldSnapshotsLongTime.cpp | 7 +- dbms/src/Storages/Page/workload/MainEntry.cpp | 70 +++++++++++++++++++ .../Page/{stress => }/workload/Normal.cpp | 5 +- .../{stress => workload}/PSBackground.cpp | 6 +- .../Page/{stress => workload}/PSBackground.h | 5 +- .../Page/{stress => workload}/PSRunnable.cpp | 13 ++-- .../Page/{stress => workload}/PSRunnable.h | 37 +++++----- .../Page/{stress => workload}/PSStressEnv.cpp | 7 +- .../Page/{stress => workload}/PSStressEnv.h | 3 + .../Page/{stress => workload}/PSWorkload.cpp | 5 +- .../Page/{stress => workload}/PSWorkload.h | 30 ++++---- .../workload/PageStorageInMemoryCapacity.cpp | 12 ++-- .../workload/ThousandsOfOffset.cpp | 7 +- 47 files changed, 270 insertions(+), 212 deletions(-) delete mode 100644 dbms/src/Storages/DeltaMerge/tools/workload/Main.cpp rename dbms/src/Storages/DeltaMerge/{tools => }/workload/CMakeLists.txt (86%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/DTWorkload.cpp (94%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/DTWorkload.h (97%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/DataGenerator.cpp (95%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/DataGenerator.h (96%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/Handle.h (90%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/KeyGenerator.cpp (92%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/KeyGenerator.h (92%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/Limiter.cpp (77%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/Limiter.h (96%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/MainEntry.cpp (97%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/Options.cpp (98%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/Options.h (100%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/ReadColumnsGenerator.h (93%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/TableGenerator.cpp (97%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/TableGenerator.h (96%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/TimestampGenerator.h (100%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/Utils.cpp (94%) rename dbms/src/Storages/DeltaMerge/{tools => }/workload/Utils.h (100%) delete mode 100644 dbms/src/Storages/Page/stress/stress_page_storage.cpp rename dbms/src/Storages/{DeltaMerge/tools => Page/workload}/CMakeLists.txt (53%) rename dbms/src/Storages/Page/{stress => }/workload/HeavyMemoryCostInGC.cpp (96%) rename dbms/src/Storages/Page/{stress => }/workload/HeavyRead.cpp (94%) rename dbms/src/Storages/Page/{stress => }/workload/HeavySkewWriteRead.cpp (95%) rename dbms/src/Storages/Page/{stress => }/workload/HeavyWrite.cpp (94%) rename dbms/src/Storages/Page/{stress => }/workload/HighValidBigFileGC.cpp (97%) rename dbms/src/Storages/Page/{stress => }/workload/HoldSnapshotsLongTime.cpp (95%) create mode 100644 dbms/src/Storages/Page/workload/MainEntry.cpp rename dbms/src/Storages/Page/{stress => }/workload/Normal.cpp (95%) rename dbms/src/Storages/Page/{stress => workload}/PSBackground.cpp (96%) rename dbms/src/Storages/Page/{stress => workload}/PSBackground.h (97%) rename dbms/src/Storages/Page/{stress => workload}/PSRunnable.cpp (97%) rename dbms/src/Storages/Page/{stress => workload}/PSRunnable.h (90%) rename dbms/src/Storages/Page/{stress => workload}/PSStressEnv.cpp (97%) rename dbms/src/Storages/Page/{stress => workload}/PSStressEnv.h (98%) rename dbms/src/Storages/Page/{stress => workload}/PSWorkload.cpp (98%) rename dbms/src/Storages/Page/{stress => workload}/PSWorkload.h (92%) rename dbms/src/Storages/Page/{stress => }/workload/PageStorageInMemoryCapacity.cpp (96%) rename dbms/src/Storages/Page/{stress => }/workload/ThousandsOfOffset.cpp (97%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e14c205f18..f2ec9f3316b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -432,9 +432,6 @@ else (ENABLE_FAILPOINTS) message (STATUS "Failpoints are disabled") endif (ENABLE_FAILPOINTS) -# Enable PageStorage V3 test. -option (ENABLE_V3_PAGESTORAGE "Enables V3 PageStorage" ON) - # Flags for test coverage option (TEST_COVERAGE "Enables flags for test coverage" OFF) option (TEST_COVERAGE_XML "Output XML report for test coverage" OFF) diff --git a/dbms/CMakeLists.txt b/dbms/CMakeLists.txt index cce11bd6997..e1e52fab73b 100644 --- a/dbms/CMakeLists.txt +++ b/dbms/CMakeLists.txt @@ -91,12 +91,10 @@ add_headers_and_sources(dbms src/Storages/Page/V2/VersionSet) add_headers_and_sources(dbms src/Storages/Page/V2/gc) add_headers_and_sources(dbms src/WindowFunctions) add_headers_and_sources(dbms src/TiDB/Schema) -if (ENABLE_V3_PAGESTORAGE) - add_headers_and_sources(dbms src/Storages/Page/V3) - add_headers_and_sources(dbms src/Storages/Page/V3/LogFile) - add_headers_and_sources(dbms src/Storages/Page/V3/WAL) - add_headers_and_sources(dbms src/Storages/Page/V3/spacemap) -endif() +add_headers_and_sources(dbms src/Storages/Page/V3) +add_headers_and_sources(dbms src/Storages/Page/V3/LogFile) +add_headers_and_sources(dbms src/Storages/Page/V3/WAL) +add_headers_and_sources(dbms src/Storages/Page/V3/spacemap) add_headers_and_sources(dbms src/Storages/Page/) add_headers_and_sources(dbms src/TiDB) add_headers_and_sources(dbms src/Client) @@ -323,6 +321,9 @@ if (ENABLE_TESTS) if (ENABLE_TIFLASH_DTWORKLOAD) target_link_libraries(bench_dbms dt-workload-lib) endif () + if (ENABLE_TIFLASH_PAGEWORKLOAD) + target_link_libraries(bench_dbms page-workload-lib) + endif () add_check(bench_dbms) endif () diff --git a/dbms/src/Server/CMakeLists.txt b/dbms/src/Server/CMakeLists.txt index 63cf6d0e1f9..104b4f34e4a 100644 --- a/dbms/src/Server/CMakeLists.txt +++ b/dbms/src/Server/CMakeLists.txt @@ -22,6 +22,7 @@ option(ENABLE_CLICKHOUSE_SERVER "Enable server" ${ENABLE_CLICKHOUSE_ALL}) option(ENABLE_CLICKHOUSE_CLIENT "Enable client" ${ENABLE_CLICKHOUSE_ALL}) option(ENABLE_TIFLASH_DTTOOL "Enable dttool: tools to manage dmfile" ${ENABLE_CLICKHOUSE_ALL}) option(ENABLE_TIFLASH_DTWORKLOAD "Enable dtworkload: tools to test and stress DeltaTree" ${ENABLE_CLICKHOUSE_ALL}) +option(ENABLE_TIFLASH_PAGEWORKLOAD "Enable pageworkload: tools to test and stress PageStorage" ${ENABLE_CLICKHOUSE_ALL}) option(ENABLE_TIFLASH_PAGECTL "Enable pagectl: tools to debug page storage" ${ENABLE_CLICKHOUSE_ALL}) configure_file (config_tools.h.in ${CMAKE_CURRENT_BINARY_DIR}/config_tools.h) @@ -136,6 +137,9 @@ endif () if (ENABLE_TIFLASH_DTWORKLOAD) target_link_libraries(tiflash dt-workload-lib) endif () +if (ENABLE_TIFLASH_PAGEWORKLOAD) + target_link_libraries(tiflash page-workload-lib) +endif() if (ENABLE_TIFLASH_PAGECTL) target_link_libraries(tiflash page-ctl-lib) endif () diff --git a/dbms/src/Server/config_tools.h.in b/dbms/src/Server/config_tools.h.in index 61aa3f41591..03a478a6473 100644 --- a/dbms/src/Server/config_tools.h.in +++ b/dbms/src/Server/config_tools.h.in @@ -6,4 +6,5 @@ #cmakedefine01 ENABLE_CLICKHOUSE_CLIENT #cmakedefine01 ENABLE_TIFLASH_DTTOOL #cmakedefine01 ENABLE_TIFLASH_DTWORKLOAD +#cmakedefine01 ENABLE_TIFLASH_PAGEWORKLOAD #cmakedefine01 ENABLE_TIFLASH_PAGECTL diff --git a/dbms/src/Server/main.cpp b/dbms/src/Server/main.cpp index 11cccf84729..dbcaa4f38fc 100644 --- a/dbms/src/Server/main.cpp +++ b/dbms/src/Server/main.cpp @@ -36,7 +36,10 @@ #include #endif #if ENABLE_TIFLASH_DTWORKLOAD -#include +#include +#endif +#if ENABLE_TIFLASH_PAGEWORKLOAD +#include #endif #if ENABLE_TIFLASH_PAGECTL #include @@ -107,6 +110,9 @@ std::pair clickhouse_applications[] = { #if ENABLE_TIFLASH_DTWORKLOAD {"dtworkload", DB::DM::tests::DTWorkload::mainEntry}, #endif +#if ENABLE_TIFLASH_PAGEWORKLOAD + {"pageworkload", DB::PS::tests::StressWorkload::mainEntry}, +#endif #if ENABLE_TIFLASH_PAGECTL {"pagectl", DB::PageStorageCtl::mainEntry}, #endif diff --git a/dbms/src/Storages/CMakeLists.txt b/dbms/src/Storages/CMakeLists.txt index 90cc7a01d5b..68a2e6c9a74 100644 --- a/dbms/src/Storages/CMakeLists.txt +++ b/dbms/src/Storages/CMakeLists.txt @@ -15,16 +15,15 @@ add_subdirectory (System) add_subdirectory (Page) add_subdirectory (DeltaMerge/File/dtpb) -add_subdirectory (DeltaMerge/tools) +add_subdirectory (DeltaMerge/workload) +add_subdirectory (Page/workload) if (ENABLE_TESTS) add_subdirectory (tests EXCLUDE_FROM_ALL) add_subdirectory (Transaction/tests EXCLUDE_FROM_ALL) add_subdirectory (Page/V2/tests EXCLUDE_FROM_ALL) - if (ENABLE_V3_PAGESTORAGE) - add_subdirectory (Page/V3 EXCLUDE_FROM_ALL) - add_subdirectory (Page/V3/tests EXCLUDE_FROM_ALL) - endif () + add_subdirectory (Page/V3 EXCLUDE_FROM_ALL) + add_subdirectory (Page/V3/tests EXCLUDE_FROM_ALL) add_subdirectory (DeltaMerge/tests EXCLUDE_FROM_ALL) endif () diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/Main.cpp b/dbms/src/Storages/DeltaMerge/tools/workload/Main.cpp deleted file mode 100644 index 092c8a89a42..00000000000 --- a/dbms/src/Storages/DeltaMerge/tools/workload/Main.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include - -using namespace DB::DM::tests; - -int main(int argc, char ** argv) -{ - return DTWorkload::mainEntry(argc, argv); -} diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/CMakeLists.txt b/dbms/src/Storages/DeltaMerge/workload/CMakeLists.txt similarity index 86% rename from dbms/src/Storages/DeltaMerge/tools/workload/CMakeLists.txt rename to dbms/src/Storages/DeltaMerge/workload/CMakeLists.txt index 7227f1cf563..7a83cbec57c 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/CMakeLists.txt +++ b/dbms/src/Storages/DeltaMerge/workload/CMakeLists.txt @@ -18,6 +18,3 @@ set(dt-workload-src MainEntry.cpp DTWorkload.cpp KeyGenerator.cpp TableGenerator add_library(dt-workload-lib ${dt-workload-src}) target_link_libraries(dt-workload-lib dbms clickhouse_functions clickhouse-server-lib) - -add_executable(dt-workload Main.cpp ${dt-workload-src}) -target_link_libraries(dt-workload dbms gtest clickhouse_functions clickhouse-server-lib) diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/DTWorkload.cpp b/dbms/src/Storages/DeltaMerge/workload/DTWorkload.cpp similarity index 94% rename from dbms/src/Storages/DeltaMerge/tools/workload/DTWorkload.cpp rename to dbms/src/Storages/DeltaMerge/workload/DTWorkload.cpp index a6113f91d91..a53a1b9ebbd 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/DTWorkload.cpp +++ b/dbms/src/Storages/DeltaMerge/workload/DTWorkload.cpp @@ -19,16 +19,16 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/DTWorkload.h b/dbms/src/Storages/DeltaMerge/workload/DTWorkload.h similarity index 97% rename from dbms/src/Storages/DeltaMerge/tools/workload/DTWorkload.h rename to dbms/src/Storages/DeltaMerge/workload/DTWorkload.h index 26cc5b6e07c..1ee5ba6b871 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/DTWorkload.h +++ b/dbms/src/Storages/DeltaMerge/workload/DTWorkload.h @@ -73,7 +73,7 @@ class ThreadStat class Statistics { public: - Statistics(int write_thread_count = 0, int read_thread_count = 0) + explicit Statistics(int write_thread_count = 0, int read_thread_count = 0) : init_ms(0) , write_stats(write_thread_count) , read_stats(read_thread_count) diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/DataGenerator.cpp b/dbms/src/Storages/DeltaMerge/workload/DataGenerator.cpp similarity index 95% rename from dbms/src/Storages/DeltaMerge/tools/workload/DataGenerator.cpp rename to dbms/src/Storages/DeltaMerge/workload/DataGenerator.cpp index be6ff1dcbbe..479977d46d1 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/DataGenerator.cpp +++ b/dbms/src/Storages/DeltaMerge/workload/DataGenerator.cpp @@ -13,11 +13,11 @@ // limitations under the License. #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include @@ -33,7 +33,7 @@ class RandomDataGenerator : public DataGenerator , rand_gen(std::random_device()()) {} - virtual std::tuple get(uint64_t key) override + std::tuple get(uint64_t key) override { Block block; // Generate 'rowkeys'. @@ -227,7 +227,9 @@ class RandomDataGenerator : public DataGenerator struct tm randomLocalTime() { time_t t = randomUTCTimestamp(); - struct tm res; + struct tm res + { + }; if (localtime_r(&t, &res) == nullptr) { throw std::invalid_argument(fmt::format("localtime_r({}) ret {}", t, strerror(errno))); diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/DataGenerator.h b/dbms/src/Storages/DeltaMerge/workload/DataGenerator.h similarity index 96% rename from dbms/src/Storages/DeltaMerge/tools/workload/DataGenerator.h rename to dbms/src/Storages/DeltaMerge/workload/DataGenerator.h index e32de4591e6..cd29f1a3a80 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/DataGenerator.h +++ b/dbms/src/Storages/DeltaMerge/workload/DataGenerator.h @@ -27,7 +27,7 @@ class DataGenerator public: static std::unique_ptr create(const WorkloadOptions & opts, const TableInfo & table_info, TimestampGenerator & ts_gen); virtual std::tuple get(uint64_t key) = 0; - virtual ~DataGenerator() {} + virtual ~DataGenerator() = default; }; std::string blockToString(const Block & block); diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/Handle.h b/dbms/src/Storages/DeltaMerge/workload/Handle.h similarity index 90% rename from dbms/src/Storages/DeltaMerge/tools/workload/Handle.h rename to dbms/src/Storages/DeltaMerge/workload/Handle.h index eb117a4fddd..c4949c15a1f 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/Handle.h +++ b/dbms/src/Storages/DeltaMerge/workload/Handle.h @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include @@ -40,7 +40,7 @@ class HandleLock static constexpr uint64_t default_lock_count = 4096; static std::unique_ptr create(const TableInfo & table_info); - HandleLock(uint64_t lock_count = default_lock_count) + explicit HandleLock(uint64_t lock_count = default_lock_count) : rmtxs(lock_count) {} @@ -51,14 +51,14 @@ class HandleLock std::vector> getLocks(const std::vector & handles) { - std::vector indexes; + std::vector indexes(handles.size()); for (const auto & h : handles) { indexes.push_back(index(h)); } // Sort mutex indexes to avoid dead lock. sort(indexes.begin(), indexes.end()); - std::vector> locks; + std::vector> locks(indexes.size()); for (auto i : indexes) { locks.push_back(getLockByIndex(i)); @@ -105,7 +105,7 @@ class HandleTable std::lock_guard lock(mtx); handle_to_ts[handle] = ts; Record r{handle, ts}; - if (wal != nullptr && wal->write((char *)&r, sizeof(r)) != sizeof(r)) + if (wal != nullptr && wal->write(reinterpret_cast(&r), sizeof(r)) != sizeof(r)) { throw std::runtime_error(fmt::format("write ret {}", strerror(errno))); } @@ -134,8 +134,8 @@ class HandleTable try { PosixRandomAccessFile f(fname, -1); - Record r; - while (f.read((char *)&r, sizeof(r)) == sizeof(r)) + Record r{}; + while (f.read(reinterpret_cast(&r), sizeof(r)) == sizeof(r)) { handle_to_ts[r.handle] = r.ts; } @@ -156,7 +156,7 @@ class HandleTable for (const auto & pa : handle_to_ts) { Record r{pa.first, pa.second}; - if (f.write((char *)&r, sizeof(r)) != sizeof(r)) + if (f.write(reinterpret_cast(&r), sizeof(r)) != sizeof(r)) { throw std::runtime_error(fmt::format("write ret {}", strerror(errno))); } @@ -191,7 +191,7 @@ class SharedHandleTable public: static constexpr uint64_t default_shared_count = 4096; - SharedHandleTable(uint64_t max_key_count, const std::string & waldir = "", uint64_t shared_cnt = default_shared_count) + explicit SharedHandleTable(uint64_t max_key_count, const std::string & waldir = "", uint64_t shared_cnt = default_shared_count) : tables(shared_cnt) { uint64_t max_key_count_per_shared = max_key_count / default_shared_count + 1; diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/KeyGenerator.cpp b/dbms/src/Storages/DeltaMerge/workload/KeyGenerator.cpp similarity index 92% rename from dbms/src/Storages/DeltaMerge/tools/workload/KeyGenerator.cpp rename to dbms/src/Storages/DeltaMerge/workload/KeyGenerator.cpp index bb2f2253279..f899ec71b4b 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/KeyGenerator.cpp +++ b/dbms/src/Storages/DeltaMerge/workload/KeyGenerator.cpp @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#include +#include #include #include @@ -31,7 +31,7 @@ class IncrementalKeyGenerator : public KeyGenerator , key(0) {} - virtual uint64_t get64() override + uint64_t get64() override { return key.fetch_add(1, std::memory_order_relaxed) % key_count + start_key; } @@ -54,7 +54,7 @@ class UniformDistributionKeyGenerator : public KeyGenerator , uniform_dist(0, key_count) {} - virtual uint64_t get64() override + uint64_t get64() override { std::lock_guard lock(mtx); return uniform_dist(rand_gen); @@ -78,7 +78,7 @@ class NormalDistributionKeyGenerator : public KeyGenerator , normal_dist(key_count / 2.0, key_count / 20.0) {} - virtual uint64_t get64() override + uint64_t get64() override { std::lock_guard lock(mtx); return normal_dist(rand_gen); diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/KeyGenerator.h b/dbms/src/Storages/DeltaMerge/workload/KeyGenerator.h similarity index 92% rename from dbms/src/Storages/DeltaMerge/tools/workload/KeyGenerator.h rename to dbms/src/Storages/DeltaMerge/workload/KeyGenerator.h index 447f3ffc27a..7c8b8fd0080 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/KeyGenerator.h +++ b/dbms/src/Storages/DeltaMerge/workload/KeyGenerator.h @@ -23,8 +23,8 @@ class KeyGenerator public: static std::unique_ptr create(const WorkloadOptions & opts); - KeyGenerator() {} - virtual ~KeyGenerator() {} + KeyGenerator() = default; + virtual ~KeyGenerator() = default; virtual uint64_t get64() = 0; }; diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/Limiter.cpp b/dbms/src/Storages/DeltaMerge/workload/Limiter.cpp similarity index 77% rename from dbms/src/Storages/DeltaMerge/tools/workload/Limiter.cpp rename to dbms/src/Storages/DeltaMerge/workload/Limiter.cpp index 73764d27bc5..65f9e3ce72c 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/Limiter.cpp +++ b/dbms/src/Storages/DeltaMerge/workload/Limiter.cpp @@ -13,8 +13,8 @@ // limitations under the License. #include -#include -#include +#include +#include #include #include @@ -24,10 +24,10 @@ namespace DB::DM::tests class ConstantLimiter : public Limiter { public: - ConstantLimiter(uint64_t rate_per_sec) + explicit ConstantLimiter(uint64_t rate_per_sec) : limiter(rate_per_sec, LimiterType::UNKNOW) {} - virtual void request() override + void request() override { limiter.request(1); } @@ -38,7 +38,7 @@ class ConstantLimiter : public Limiter std::unique_ptr Limiter::create(const WorkloadOptions & opts) { - uint64_t per_sec = std::ceil(static_cast(opts.max_write_per_sec / opts.write_thread_count)); + uint64_t per_sec = std::ceil(opts.max_write_per_sec * 1.0 / opts.write_thread_count); return std::make_unique(per_sec); } diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/Limiter.h b/dbms/src/Storages/DeltaMerge/workload/Limiter.h similarity index 96% rename from dbms/src/Storages/DeltaMerge/tools/workload/Limiter.h rename to dbms/src/Storages/DeltaMerge/workload/Limiter.h index e2892b178a2..da2d31c7915 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/Limiter.h +++ b/dbms/src/Storages/DeltaMerge/workload/Limiter.h @@ -23,6 +23,6 @@ class Limiter public: static std::unique_ptr create(const WorkloadOptions & opts); virtual void request() = 0; - virtual ~Limiter() {} + virtual ~Limiter() = default; }; } // namespace DB::DM::tests \ No newline at end of file diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/MainEntry.cpp b/dbms/src/Storages/DeltaMerge/workload/MainEntry.cpp similarity index 97% rename from dbms/src/Storages/DeltaMerge/tools/workload/MainEntry.cpp rename to dbms/src/Storages/DeltaMerge/workload/MainEntry.cpp index f79d414f20b..88cf0b6322f 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/MainEntry.cpp +++ b/dbms/src/Storages/DeltaMerge/workload/MainEntry.cpp @@ -14,10 +14,10 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/Options.cpp b/dbms/src/Storages/DeltaMerge/workload/Options.cpp similarity index 98% rename from dbms/src/Storages/DeltaMerge/tools/workload/Options.cpp rename to dbms/src/Storages/DeltaMerge/workload/Options.cpp index 1c6409f3c53..8545d22ca8d 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/Options.cpp +++ b/dbms/src/Storages/DeltaMerge/workload/Options.cpp @@ -13,8 +13,8 @@ // limitations under the License. #include -#include -#include +#include +#include #include #include diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/Options.h b/dbms/src/Storages/DeltaMerge/workload/Options.h similarity index 100% rename from dbms/src/Storages/DeltaMerge/tools/workload/Options.h rename to dbms/src/Storages/DeltaMerge/workload/Options.h diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/ReadColumnsGenerator.h b/dbms/src/Storages/DeltaMerge/workload/ReadColumnsGenerator.h similarity index 93% rename from dbms/src/Storages/DeltaMerge/tools/workload/ReadColumnsGenerator.h rename to dbms/src/Storages/DeltaMerge/workload/ReadColumnsGenerator.h index 180409f89e1..c881bb148a2 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/ReadColumnsGenerator.h +++ b/dbms/src/Storages/DeltaMerge/workload/ReadColumnsGenerator.h @@ -14,7 +14,7 @@ #pragma once -#include +#include #include @@ -28,7 +28,7 @@ class ReadColumnsGenerator return std::make_unique(table_info); } - ReadColumnsGenerator(const TableInfo & table_info_) + explicit ReadColumnsGenerator(const TableInfo & table_info_) : table_info(table_info_) , rand_gen(std::random_device()()) , uniform_dist(0, table_info_.columns->size() - 1) diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/TableGenerator.cpp b/dbms/src/Storages/DeltaMerge/workload/TableGenerator.cpp similarity index 97% rename from dbms/src/Storages/DeltaMerge/tools/workload/TableGenerator.cpp rename to dbms/src/Storages/DeltaMerge/workload/TableGenerator.cpp index cf52e808ab1..ec29a476d6a 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/TableGenerator.cpp +++ b/dbms/src/Storages/DeltaMerge/workload/TableGenerator.cpp @@ -15,8 +15,8 @@ #include #include #include -#include -#include +#include +#include #include #include @@ -237,7 +237,7 @@ class RandomTableGenerator : public TableGenerator , rand_gen(std::random_device()()) {} - virtual TableInfo get(int64_t table_id, std::string table_name) override + TableInfo get(int64_t table_id, std::string table_name) override { TableInfo table_info; @@ -293,7 +293,7 @@ class RandomTableGenerator : public TableGenerator class ConstantTableGenerator : public TableGenerator { - virtual TableInfo get(int64_t table_id, std::string table_name) override + TableInfo get(int64_t table_id, std::string table_name) override { TableInfo table_info; diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/TableGenerator.h b/dbms/src/Storages/DeltaMerge/workload/TableGenerator.h similarity index 96% rename from dbms/src/Storages/DeltaMerge/tools/workload/TableGenerator.h rename to dbms/src/Storages/DeltaMerge/workload/TableGenerator.h index aba5c1590b7..b88bf2b72e2 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/TableGenerator.h +++ b/dbms/src/Storages/DeltaMerge/workload/TableGenerator.h @@ -38,6 +38,6 @@ class TableGenerator virtual TableInfo get(int64_t table_id, std::string table_name) = 0; - virtual ~TableGenerator() {} + virtual ~TableGenerator() = default; }; } // namespace DB::DM::tests \ No newline at end of file diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/TimestampGenerator.h b/dbms/src/Storages/DeltaMerge/workload/TimestampGenerator.h similarity index 100% rename from dbms/src/Storages/DeltaMerge/tools/workload/TimestampGenerator.h rename to dbms/src/Storages/DeltaMerge/workload/TimestampGenerator.h diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/Utils.cpp b/dbms/src/Storages/DeltaMerge/workload/Utils.cpp similarity index 94% rename from dbms/src/Storages/DeltaMerge/tools/workload/Utils.cpp rename to dbms/src/Storages/DeltaMerge/workload/Utils.cpp index 1cefae724c6..80d9f788016 100644 --- a/dbms/src/Storages/DeltaMerge/tools/workload/Utils.cpp +++ b/dbms/src/Storages/DeltaMerge/workload/Utils.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include #include @@ -83,7 +83,7 @@ std::string fieldToString(const DataTypePtr & data_type, const Field & f) } else if (t == Field::Types::Which::Decimal256) { - auto i = f.get(); + const auto & i = f.get(); auto scale = dynamic_cast(data_type.get())->getScale(); return i.toString(scale); } @@ -105,8 +105,8 @@ std::vector colToVec(const DataTypePtr & data_type, const ColumnPtr std::string blockToString(const Block & block) { std::string s = "id name type values\n"; - auto & cols = block.getColumnsWithTypeAndName(); - for (auto & col : cols) + const auto & cols = block.getColumnsWithTypeAndName(); + for (const auto & col : cols) { s += fmt::format("{} {} {} {}\n", col.column_id, col.name, col.type->getFamilyName(), colToVec(col.type, col.column)); } diff --git a/dbms/src/Storages/DeltaMerge/tools/workload/Utils.h b/dbms/src/Storages/DeltaMerge/workload/Utils.h similarity index 100% rename from dbms/src/Storages/DeltaMerge/tools/workload/Utils.h rename to dbms/src/Storages/DeltaMerge/workload/Utils.h diff --git a/dbms/src/Storages/Page/CMakeLists.txt b/dbms/src/Storages/Page/CMakeLists.txt index cead83fa126..f208dc84be2 100644 --- a/dbms/src/Storages/Page/CMakeLists.txt +++ b/dbms/src/Storages/Page/CMakeLists.txt @@ -14,13 +14,3 @@ add_subdirectory(V2) add_subdirectory(tools) - -# PageStorage Stress test -if (ENABLE_V3_PAGESTORAGE) - add_headers_and_sources(page_stress_testing stress) - add_headers_and_sources(page_stress_testing stress/workload) - add_executable(page_stress_testing EXCLUDE_FROM_ALL ${page_stress_testing_sources}) - target_link_libraries(page_stress_testing dbms page_storage_v3) - target_include_directories(page_stress_testing PRIVATE stress) - target_compile_options(page_stress_testing PRIVATE -Wno-format -lc++) # turn off printf format check -endif() \ No newline at end of file diff --git a/dbms/src/Storages/Page/stress/stress_page_storage.cpp b/dbms/src/Storages/Page/stress/stress_page_storage.cpp deleted file mode 100644 index 818be710363..00000000000 --- a/dbms/src/Storages/Page/stress/stress_page_storage.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include - -namespace DB -{ -// Define is_background_thread for this binary -// It is required for `RateLimiter` but we do not link with `BackgroundProcessingPool`. -#if __APPLE__ && __clang__ -__thread bool is_background_thread = false; -#else -thread_local bool is_background_thread = false; -#endif -} // namespace DB - -int main(int argc, char ** argv) -try -{ - StressEnv::initGlobalLogger(); - auto env = StressEnv::parse(argc, argv); - env.setup(); - - auto & mamager = StressWorkloadManger::getInstance(); - mamager.setEnv(env); - mamager.runWorkload(); - - return StressEnvStatus::getInstance().isSuccess(); -} -catch (...) -{ - DB::tryLogCurrentException(""); - exit(-1); -} diff --git a/dbms/src/Storages/DeltaMerge/tools/CMakeLists.txt b/dbms/src/Storages/Page/workload/CMakeLists.txt similarity index 53% rename from dbms/src/Storages/DeltaMerge/tools/CMakeLists.txt rename to dbms/src/Storages/Page/workload/CMakeLists.txt index 36270a0c8e4..5c8ecb34d97 100644 --- a/dbms/src/Storages/DeltaMerge/tools/CMakeLists.txt +++ b/dbms/src/Storages/Page/workload/CMakeLists.txt @@ -14,4 +14,9 @@ include_directories (${CMAKE_CURRENT_BINARY_DIR}) -add_subdirectory (workload EXCLUDE_FROM_ALL) +set (page-workload-src HeavyMemoryCostInGC.cpp HeavyRead.cpp HeavySkewWriteRead.cpp HeavyWrite.cpp HighValidBigFileGC.cpp HoldSnapshotsLongTime.cpp Normal.cpp + PageStorageInMemoryCapacity.cpp ThousandsOfOffset.cpp MainEntry.cpp Normal.cpp PageStorageInMemoryCapacity.cpp PSBackground.cpp PSRunnable.cpp PSStressEnv.cpp PSWorkload.cpp) + +add_library (page-workload-lib ${page-workload-src}) +target_link_libraries (page-workload-lib dbms clickhouse_functions clickhouse-server-lib) +target_compile_options (page-workload-lib PRIVATE -Wno-format -lc++) \ No newline at end of file diff --git a/dbms/src/Storages/Page/stress/workload/HeavyMemoryCostInGC.cpp b/dbms/src/Storages/Page/workload/HeavyMemoryCostInGC.cpp similarity index 96% rename from dbms/src/Storages/Page/stress/workload/HeavyMemoryCostInGC.cpp rename to dbms/src/Storages/Page/workload/HeavyMemoryCostInGC.cpp index 40595f0cb59..7e745e29fc2 100644 --- a/dbms/src/Storages/Page/stress/workload/HeavyMemoryCostInGC.cpp +++ b/dbms/src/Storages/Page/workload/HeavyMemoryCostInGC.cpp @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +namespace DB::PS::tests +{ class HeavyMemoryCostInGC : public StressWorkload , public StressWorkloadFunc @@ -81,3 +83,4 @@ class HeavyMemoryCostInGC }; REGISTER_WORKLOAD(HeavyMemoryCostInGC) +} // namespace DB::PS::tests diff --git a/dbms/src/Storages/Page/stress/workload/HeavyRead.cpp b/dbms/src/Storages/Page/workload/HeavyRead.cpp similarity index 94% rename from dbms/src/Storages/Page/stress/workload/HeavyRead.cpp rename to dbms/src/Storages/Page/workload/HeavyRead.cpp index 15aeb1320cf..a67c435e84c 100644 --- a/dbms/src/Storages/Page/stress/workload/HeavyRead.cpp +++ b/dbms/src/Storages/Page/workload/HeavyRead.cpp @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +namespace DB::PS::tests +{ class HeavyRead : public StressWorkload , public StressWorkloadFunc { @@ -69,4 +71,5 @@ class HeavyRead : public StressWorkload } }; -REGISTER_WORKLOAD(HeavyRead) \ No newline at end of file +REGISTER_WORKLOAD(HeavyRead) +} // namespace DB::PS::tests \ No newline at end of file diff --git a/dbms/src/Storages/Page/stress/workload/HeavySkewWriteRead.cpp b/dbms/src/Storages/Page/workload/HeavySkewWriteRead.cpp similarity index 95% rename from dbms/src/Storages/Page/stress/workload/HeavySkewWriteRead.cpp rename to dbms/src/Storages/Page/workload/HeavySkewWriteRead.cpp index 78ffa5b60e0..805bf105358 100644 --- a/dbms/src/Storages/Page/stress/workload/HeavySkewWriteRead.cpp +++ b/dbms/src/Storages/Page/workload/HeavySkewWriteRead.cpp @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +namespace DB::PS::tests +{ class HeavySkewWriteRead : public StressWorkload , public StressWorkloadFunc { @@ -84,4 +86,5 @@ class HeavySkewWriteRead : public StressWorkload } }; -REGISTER_WORKLOAD(HeavySkewWriteRead) \ No newline at end of file +REGISTER_WORKLOAD(HeavySkewWriteRead) +} // namespace DB::PS::tests \ No newline at end of file diff --git a/dbms/src/Storages/Page/stress/workload/HeavyWrite.cpp b/dbms/src/Storages/Page/workload/HeavyWrite.cpp similarity index 94% rename from dbms/src/Storages/Page/stress/workload/HeavyWrite.cpp rename to dbms/src/Storages/Page/workload/HeavyWrite.cpp index 265b289db56..8dfd7f810f7 100644 --- a/dbms/src/Storages/Page/stress/workload/HeavyWrite.cpp +++ b/dbms/src/Storages/Page/workload/HeavyWrite.cpp @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +namespace DB::PS::tests +{ class HeavyWrite : public StressWorkload , public StressWorkloadFunc { @@ -71,4 +73,5 @@ class HeavyWrite : public StressWorkload } }; -REGISTER_WORKLOAD(HeavyWrite) \ No newline at end of file +REGISTER_WORKLOAD(HeavyWrite) +} // namespace DB::PS::tests \ No newline at end of file diff --git a/dbms/src/Storages/Page/stress/workload/HighValidBigFileGC.cpp b/dbms/src/Storages/Page/workload/HighValidBigFileGC.cpp similarity index 97% rename from dbms/src/Storages/Page/stress/workload/HighValidBigFileGC.cpp rename to dbms/src/Storages/Page/workload/HighValidBigFileGC.cpp index 866782c9578..a9af6aebb76 100644 --- a/dbms/src/Storages/Page/stress/workload/HighValidBigFileGC.cpp +++ b/dbms/src/Storages/Page/workload/HighValidBigFileGC.cpp @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +namespace DB::PS::tests +{ class HighValidBigFileGCWorkload : public StressWorkload , public StressWorkloadFunc @@ -129,3 +131,4 @@ class HighValidBigFileGCWorkload }; REGISTER_WORKLOAD(HighValidBigFileGCWorkload) +} // namespace DB::PS::tests \ No newline at end of file diff --git a/dbms/src/Storages/Page/stress/workload/HoldSnapshotsLongTime.cpp b/dbms/src/Storages/Page/workload/HoldSnapshotsLongTime.cpp similarity index 95% rename from dbms/src/Storages/Page/stress/workload/HoldSnapshotsLongTime.cpp rename to dbms/src/Storages/Page/workload/HoldSnapshotsLongTime.cpp index b49347fc858..f02fbf65bcd 100644 --- a/dbms/src/Storages/Page/stress/workload/HoldSnapshotsLongTime.cpp +++ b/dbms/src/Storages/Page/workload/HoldSnapshotsLongTime.cpp @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +namespace DB::PS::tests +{ class HoldSnapshotsLongTime : public StressWorkload , public StressWorkloadFunc { @@ -93,4 +95,5 @@ class HoldSnapshotsLongTime : public StressWorkload } }; -REGISTER_WORKLOAD(HoldSnapshotsLongTime) \ No newline at end of file +REGISTER_WORKLOAD(HoldSnapshotsLongTime) +} // namespace DB::PS::tests \ No newline at end of file diff --git a/dbms/src/Storages/Page/workload/MainEntry.cpp b/dbms/src/Storages/Page/workload/MainEntry.cpp new file mode 100644 index 00000000000..ac82e1ea4bc --- /dev/null +++ b/dbms/src/Storages/Page/workload/MainEntry.cpp @@ -0,0 +1,70 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. +#include +#include + +using namespace DB::PS::tests; + +int StressWorkload::mainEntry(int argc, char ** argv) +{ + { + // maybe due to sequence of linking, REGISTER_WORKLOAD is not visible to main function in dbms/src/Server/main.cpp + // cause that REGISTER_WORKLOAD will not be triggered before mainEntry + // we do this to trigger REGISTER_WORKLOAD explicitly. + void _work_load_register_named_HeavyMemoryCostInGC(); + void (*f)() = _work_load_register_named_HeavyMemoryCostInGC; + (void)f; + void _work_load_register_named_HeavyRead(); + f = _work_load_register_named_HeavyRead; + (void)f; + void _work_load_register_named_HeavySkewWriteRead(); + f = _work_load_register_named_HeavySkewWriteRead; + (void)f; + void _work_load_register_named_HeavyWrite(); + f = _work_load_register_named_HeavyWrite; + (void)f; + void _work_load_register_named_HighValidBigFileGCWorkload(); + f = _work_load_register_named_HighValidBigFileGCWorkload; + (void)f; + void _work_load_register_named_HoldSnapshotsLongTime(); + f = _work_load_register_named_HoldSnapshotsLongTime; + (void)f; + void _work_load_register_named_PageStorageInMemoryCapacity(); + f = _work_load_register_named_PageStorageInMemoryCapacity; + (void)f; + void _work_load_register_named_NormalWorkload(); + f = _work_load_register_named_NormalWorkload; + (void)f; + void _work_load_register_named_ThousandsOfOffset(); + f = _work_load_register_named_ThousandsOfOffset; + (void)f; + } + try + { + StressEnv::initGlobalLogger(); + auto env = StressEnv::parse(argc, argv); + env.setup(); + + auto & mamager = StressWorkloadManger::getInstance(); + mamager.setEnv(env); + mamager.runWorkload(); + + return StressEnvStatus::getInstance().isSuccess(); + } + catch (...) + { + DB::tryLogCurrentException(""); + exit(-1); + } +} \ No newline at end of file diff --git a/dbms/src/Storages/Page/stress/workload/Normal.cpp b/dbms/src/Storages/Page/workload/Normal.cpp similarity index 95% rename from dbms/src/Storages/Page/stress/workload/Normal.cpp rename to dbms/src/Storages/Page/workload/Normal.cpp index 0323b857613..57229395809 100644 --- a/dbms/src/Storages/Page/stress/workload/Normal.cpp +++ b/dbms/src/Storages/Page/workload/Normal.cpp @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +namespace DB::PS::tests +{ class NormalWorkload : public StressWorkload , public StressWorkloadFunc @@ -77,3 +79,4 @@ class NormalWorkload }; REGISTER_WORKLOAD(NormalWorkload) +} // namespace DB::PS::tests diff --git a/dbms/src/Storages/Page/stress/PSBackground.cpp b/dbms/src/Storages/Page/workload/PSBackground.cpp similarity index 96% rename from dbms/src/Storages/Page/stress/PSBackground.cpp rename to dbms/src/Storages/Page/workload/PSBackground.cpp index af7329e8348..247bea23dcc 100644 --- a/dbms/src/Storages/Page/stress/PSBackground.cpp +++ b/dbms/src/Storages/Page/workload/PSBackground.cpp @@ -13,11 +13,14 @@ // limitations under the License. #include -#include #include #include +#include #include + +namespace DB::PS::tests +{ void PSMetricsDumper::onTime(Poco::Timer & /*timer*/) { for (auto & metric : metrics) @@ -107,3 +110,4 @@ void StressTimeout::start() { timeout_timer.start(Poco::TimerCallback(*this, &StressTimeout::onTime)); } +} // namespace DB::PS::tests diff --git a/dbms/src/Storages/Page/stress/PSBackground.h b/dbms/src/Storages/Page/workload/PSBackground.h similarity index 97% rename from dbms/src/Storages/Page/stress/PSBackground.h rename to dbms/src/Storages/Page/workload/PSBackground.h index 8c22458c5e8..c91dad1361f 100644 --- a/dbms/src/Storages/Page/stress/PSBackground.h +++ b/dbms/src/Storages/Page/workload/PSBackground.h @@ -15,14 +15,16 @@ #pragma once #include #include -#include #include +#include namespace CurrentMetrics { extern const Metric PSMVCCSnapshotsList; } +namespace DB::PS::tests +{ class PSMetricsDumper { public: @@ -162,3 +164,4 @@ class StressTimeout Poco::Timer timeout_timer; }; using StressTimeoutPtr = std::shared_ptr; +} // namespace DB::PS::tests diff --git a/dbms/src/Storages/Page/stress/PSRunnable.cpp b/dbms/src/Storages/Page/workload/PSRunnable.cpp similarity index 97% rename from dbms/src/Storages/Page/stress/PSRunnable.cpp rename to dbms/src/Storages/Page/workload/PSRunnable.cpp index 5d6c8ecc5c6..5e9774ccc99 100644 --- a/dbms/src/Storages/Page/stress/PSRunnable.cpp +++ b/dbms/src/Storages/Page/workload/PSRunnable.cpp @@ -16,14 +16,16 @@ #include #include #include -#include #include #include +#include #include #include #include +namespace DB::PS::tests +{ void PSRunnable::run() try { @@ -69,7 +71,7 @@ DB::ReadBufferPtr PSWriter::genRandomData(const DB::PageId pageId, DB::MemHolder std::uniform_int_distribution<> dist(0, 3000); const size_t buff_sz = approx_page_mb * DB::MB + dist(size_gen); - char * buff = static_cast(malloc(buff_sz)); + char * buff = static_cast(malloc(buff_sz)); // NOLINT if (buff == nullptr) { throw DB::Exception("Alloc fix memory failed.", DB::ErrorCodes::LOGICAL_ERROR); @@ -78,7 +80,7 @@ DB::ReadBufferPtr PSWriter::genRandomData(const DB::PageId pageId, DB::MemHolder const char buff_ch = pageId % 0xFF; memset(buff, buff_ch, buff_sz); - holder = DB::createMemHolder(buff, [&](char * p) { free(p); }); + holder = DB::createMemHolder(buff, [&](char * p) { free(p); }); // NOLINT return std::make_shared(const_cast(buff), buff_sz); } @@ -88,7 +90,7 @@ void PSWriter::updatedRandomData() size_t memory_size = approx_page_mb * DB::MB * 2; if (memory == nullptr) { - memory = static_cast(malloc(memory_size)); + memory = static_cast(malloc(memory_size)); // NOLINT if (memory == nullptr) { throw DB::Exception("Alloc fix memory failed.", DB::ErrorCodes::LOGICAL_ERROR); @@ -147,7 +149,7 @@ void PSCommonWriter::updatedRandomData() if (memory == nullptr) { - memory = static_cast(malloc(memory_size)); + memory = static_cast(malloc(memory_size)); // NOLINT if (memory == nullptr) { throw DB::Exception("Alloc fix memory failed.", DB::ErrorCodes::LOGICAL_ERROR); @@ -415,3 +417,4 @@ DB::PageId PSIncreaseWriter::genRandomPageId() { return static_cast(begin_page_id++); } +} // namespace DB::PS::tests diff --git a/dbms/src/Storages/Page/stress/PSRunnable.h b/dbms/src/Storages/Page/workload/PSRunnable.h similarity index 90% rename from dbms/src/Storages/Page/stress/PSRunnable.h rename to dbms/src/Storages/Page/workload/PSRunnable.h index 3ddcd73c093..b723236391d 100644 --- a/dbms/src/Storages/Page/stress/PSRunnable.h +++ b/dbms/src/Storages/Page/workload/PSRunnable.h @@ -13,12 +13,14 @@ // limitations under the License. #pragma once -#include #include #include +#include const DB::PageId MAX_PAGE_ID_DEFAULT = 1000; +namespace DB::PS::tests +{ class PSRunnable : public Poco::Runnable { public: @@ -46,7 +48,7 @@ class PSWriter : public PSRunnable gen.seed(time(nullptr)); } - virtual ~PSWriter() + ~PSWriter() override { if (memory != nullptr) { @@ -54,7 +56,7 @@ class PSWriter : public PSRunnable } } - virtual String description() override + String description() override { return fmt::format("(Stress Test Writer {})", index); } @@ -67,7 +69,7 @@ class PSWriter : public PSRunnable static void fillAllPages(const PSPtr & ps); - virtual bool runImpl() override; + bool runImpl() override; protected: virtual DB::PageId genRandomPageId(); @@ -91,11 +93,11 @@ class PSCommonWriter : public PSWriter : PSWriter(ps_, index_) {} - virtual void updatedRandomData() override; + void updatedRandomData() override; - virtual String description() override { return fmt::format("(Stress Test Common Writer {})", index); } + String description() override { return fmt::format("(Stress Test Common Writer {})", index); } - virtual bool runImpl() override; + bool runImpl() override; void setBatchBufferNums(size_t numbers); @@ -120,7 +122,7 @@ class PSCommonWriter : public PSWriter DB::PageFieldSizes data_sizes = {}; - virtual DB::PageId genRandomPageId() override; + DB::PageId genRandomPageId() override; virtual size_t genBufferSize(); }; @@ -154,7 +156,7 @@ class PSWindowWriter : public PSCommonWriter void setNormalDistributionSigma(size_t sigma); protected: - virtual DB::PageId genRandomPageId() override; + DB::PageId genRandomPageId() override; protected: size_t window_size = 100; @@ -170,12 +172,12 @@ class PSIncreaseWriter : public PSCommonWriter String description() override { return fmt::format("(Stress Test Increase Writer {})", index); } - virtual bool runImpl() override; + bool runImpl() override; void setPageRange(size_t page_range); protected: - virtual DB::PageId genRandomPageId() override; + DB::PageId genRandomPageId() override; protected: size_t begin_page_id = 1; @@ -192,9 +194,9 @@ class PSReader : public PSRunnable gen.seed(time(nullptr)); } - virtual String description() override { return fmt::format("(Stress Test PSReader {})", index); } + String description() override { return fmt::format("(Stress Test PSReader {})", index); } - virtual bool runImpl() override; + bool runImpl() override; void setPageReadOnce(size_t page_read_once); @@ -242,7 +244,7 @@ class PSWindowReader : public PSReader void setWriterNums(size_t writer_nums); protected: - virtual DB::PageIds genRandomPageIds() override; + DB::PageIds genRandomPageIds() override; protected: size_t window_size = 100; @@ -261,12 +263,13 @@ class PSSnapshotReader : public PSReader : PSReader(ps_, index_) {} - virtual bool runImpl() override; + bool runImpl() override; void setSnapshotGetIntervalMs(size_t snapshot_get_interval_ms_); protected: - size_t snapshots_hold_num; + size_t snapshots_hold_num = 0; size_t snapshot_get_interval_ms = 0; std::list snapshots; -}; \ No newline at end of file +}; +} // namespace DB::PS::tests diff --git a/dbms/src/Storages/Page/stress/PSStressEnv.cpp b/dbms/src/Storages/Page/workload/PSStressEnv.cpp similarity index 97% rename from dbms/src/Storages/Page/stress/PSStressEnv.cpp rename to dbms/src/Storages/Page/workload/PSStressEnv.cpp index 7d680cd43c0..f5cead0a158 100644 --- a/dbms/src/Storages/Page/stress/PSStressEnv.cpp +++ b/dbms/src/Storages/Page/workload/PSStressEnv.cpp @@ -16,18 +16,20 @@ #include #include #include -#include -#include #include #include #include #include #include #include +#include +#include #include #include +namespace DB::PS::tests +{ Poco::Logger * StressEnv::logger; void StressEnv::initGlobalLogger() { @@ -146,3 +148,4 @@ void StressEnv::setup() init_pages = true; setupSignal(); } +} // namespace DB::PS::tests diff --git a/dbms/src/Storages/Page/stress/PSStressEnv.h b/dbms/src/Storages/Page/workload/PSStressEnv.h similarity index 98% rename from dbms/src/Storages/Page/stress/PSStressEnv.h rename to dbms/src/Storages/Page/workload/PSStressEnv.h index 1c7d8ee761f..e67cb325430 100644 --- a/dbms/src/Storages/Page/stress/PSStressEnv.h +++ b/dbms/src/Storages/Page/workload/PSStressEnv.h @@ -25,6 +25,8 @@ namespace Poco class Logger; } +namespace DB::PS::tests +{ using PSPtr = std::shared_ptr; enum StressEnvStat @@ -124,3 +126,4 @@ struct StressEnv void setup(); }; +} // namespace DB::PS::tests diff --git a/dbms/src/Storages/Page/stress/PSWorkload.cpp b/dbms/src/Storages/Page/workload/PSWorkload.cpp similarity index 98% rename from dbms/src/Storages/Page/stress/PSWorkload.cpp rename to dbms/src/Storages/Page/workload/PSWorkload.cpp index ce1f8d92ce0..81f13527f48 100644 --- a/dbms/src/Storages/Page/stress/PSWorkload.cpp +++ b/dbms/src/Storages/Page/workload/PSWorkload.cpp @@ -14,12 +14,14 @@ #include #include -#include #include #include #include +#include #include +namespace DB::PS::tests +{ void StressWorkload::onDumpResult() { UInt64 time_interval = stop_watch.elapsedMilliseconds(); @@ -177,3 +179,4 @@ void StressWorkloadManger::runWorkload() } } } +} // namespace DB::PS::tests diff --git a/dbms/src/Storages/Page/stress/PSWorkload.h b/dbms/src/Storages/Page/workload/PSWorkload.h similarity index 92% rename from dbms/src/Storages/Page/stress/PSWorkload.h rename to dbms/src/Storages/Page/workload/PSWorkload.h index cb099b4203a..eaaaf4eba5b 100644 --- a/dbms/src/Storages/Page/stress/PSWorkload.h +++ b/dbms/src/Storages/Page/workload/PSWorkload.h @@ -16,15 +16,17 @@ #include #include -#include -#include -#include #include #include #include +#include +#include +#include #include #define NORMAL_WORKLOAD 0 +namespace DB::PS::tests +{ template class StressWorkloadFunc { @@ -45,6 +47,8 @@ class StressWorkloadFunc class StressWorkload { public: + static int mainEntry(int argc, char ** argv); + explicit StressWorkload(StressEnv options_) : options(options_) {} @@ -189,13 +193,15 @@ class StressWorkloadManger StressEnv options; }; -#define REGISTER_WORKLOAD(WORKLOAD) \ - static void __attribute__((constructor)) _work_load_register_named_##WORKLOAD(void) \ - { \ - StressWorkloadManger::getInstance().reg( \ - WORKLOAD::nameFunc(), \ - WORKLOAD::maskFunc(), \ - [](const StressEnv & opts) -> std::shared_ptr { \ - return std::make_shared(opts); \ - }); \ +#define REGISTER_WORKLOAD(WORKLOAD) \ + void __attribute__((constructor)) _work_load_register_named_##WORKLOAD(void) \ + { \ + StressWorkloadManger::getInstance().reg( \ + WORKLOAD::nameFunc(), \ + WORKLOAD::maskFunc(), \ + [](const StressEnv & opts) -> std::shared_ptr { \ + return std::make_shared(opts); \ + }); \ } + +} // namespace DB::PS::tests diff --git a/dbms/src/Storages/Page/stress/workload/PageStorageInMemoryCapacity.cpp b/dbms/src/Storages/Page/workload/PageStorageInMemoryCapacity.cpp similarity index 96% rename from dbms/src/Storages/Page/stress/workload/PageStorageInMemoryCapacity.cpp rename to dbms/src/Storages/Page/workload/PageStorageInMemoryCapacity.cpp index 190cbf6b323..6ab321d1a10 100644 --- a/dbms/src/Storages/Page/stress/workload/PageStorageInMemoryCapacity.cpp +++ b/dbms/src/Storages/Page/workload/PageStorageInMemoryCapacity.cpp @@ -12,13 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include #include #include #include - #ifdef __APPLE__ #include @@ -27,6 +26,8 @@ #include #endif +namespace DB::PS::tests +{ class PageStorageInMemoryCapacity : public StressWorkload , public StressWorkloadFunc { @@ -89,14 +90,14 @@ class PageStorageInMemoryCapacity : public StressWorkload } FILE * file = fopen("/proc/meminfo", "r"); - if (file != NULL) + if (file != nullptr) { char buffer[128]; #define MEMORY_TOTAL_LABEL "MemTotal:" while (fgets(buffer, 128, file)) { if ((strncmp((buffer), (MEMORY_TOTAL_LABEL), strlen(MEMORY_TOTAL_LABEL)) == 0) - && sscanf(buffer + strlen(MEMORY_TOTAL_LABEL), " %32llu kB", &total_mem)) + && sscanf(buffer + strlen(MEMORY_TOTAL_LABEL), " %32llu kB", &total_mem)) // NOLINT { break; } @@ -174,4 +175,5 @@ class PageStorageInMemoryCapacity : public StressWorkload } }; -REGISTER_WORKLOAD(PageStorageInMemoryCapacity) \ No newline at end of file +REGISTER_WORKLOAD(PageStorageInMemoryCapacity) +} // namespace DB::PS::tests \ No newline at end of file diff --git a/dbms/src/Storages/Page/stress/workload/ThousandsOfOffset.cpp b/dbms/src/Storages/Page/workload/ThousandsOfOffset.cpp similarity index 97% rename from dbms/src/Storages/Page/stress/workload/ThousandsOfOffset.cpp rename to dbms/src/Storages/Page/workload/ThousandsOfOffset.cpp index 3a215f76769..5a02ef48d68 100644 --- a/dbms/src/Storages/Page/stress/workload/ThousandsOfOffset.cpp +++ b/dbms/src/Storages/Page/workload/ThousandsOfOffset.cpp @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include +namespace DB::PS::tests +{ class ThousandsOfOffset : public StressWorkload , public StressWorkloadFunc { @@ -168,4 +170,5 @@ class ThousandsOfOffset : public StressWorkload } }; -REGISTER_WORKLOAD(ThousandsOfOffset) \ No newline at end of file +REGISTER_WORKLOAD(ThousandsOfOffset) +} // namespace DB::PS::tests \ No newline at end of file From 1e3207d3794d8870fc8afad895017191851e4cca Mon Sep 17 00:00:00 2001 From: Lloyd-Pottiger <60744015+Lloyd-Pottiger@users.noreply.github.com> Date: Fri, 10 Jun 2022 12:32:30 +0800 Subject: [PATCH 095/127] feat: add hardware information of server as an attribute (#5090) close pingcap/tiflash#4879 --- dbms/src/Server/CMakeLists.txt | 1 + dbms/src/Server/Server.cpp | 30 +++-- dbms/src/Server/Server.h | 8 +- dbms/src/Server/ServerInfo.cpp | 199 +++++++++++++++++++++++++++++++++ dbms/src/Server/ServerInfo.h | 99 ++++++++++++++++ 5 files changed, 327 insertions(+), 10 deletions(-) create mode 100644 dbms/src/Server/ServerInfo.cpp create mode 100644 dbms/src/Server/ServerInfo.h diff --git a/dbms/src/Server/CMakeLists.txt b/dbms/src/Server/CMakeLists.txt index 104b4f34e4a..2948bb076db 100644 --- a/dbms/src/Server/CMakeLists.txt +++ b/dbms/src/Server/CMakeLists.txt @@ -34,6 +34,7 @@ add_library (clickhouse-server-lib NotFoundHandler.cpp PingRequestHandler.cpp RootRequestHandler.cpp + ServerInfo.cpp Server.cpp StatusFile.cpp TCPHandler.cpp diff --git a/dbms/src/Server/Server.cpp b/dbms/src/Server/Server.cpp index 04676ef969d..3e2c29de76c 100644 --- a/dbms/src/Server/Server.cpp +++ b/dbms/src/Server/Server.cpp @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "Server.h" - #include #include #include @@ -56,6 +54,8 @@ #include #include #include +#include +#include #include #include #include @@ -72,7 +72,6 @@ #include #include #include -#include #include #include @@ -1049,6 +1048,23 @@ int Server::main(const std::vector & /*args*/) LOG_FMT_INFO(log, "tiflash proxy thread is joined"); }); + /// get CPU/memory/disk info of this server + if (tiflash_instance_wrap.proxy_helper) + { + diagnosticspb::ServerInfoRequest request; + request.set_tp(static_cast(1)); + diagnosticspb::ServerInfoResponse response; + std::string req = request.SerializeAsString(); + auto * helper = tiflash_instance_wrap.proxy_helper; + helper->fn_server_info(helper->proxy_ptr, strIntoView(&req), &response); + server_info.parseSysInfo(response); + LOG_FMT_INFO(log, "ServerInfo: {}", server_info.debugString()); + } + else + { + LOG_FMT_INFO(log, "TiFlashRaftProxyHelper is null, failed to get server info"); + } + CurrentMetrics::set(CurrentMetrics::Revision, ClickHouseRevision::get()); // print necessary grpc log. @@ -1410,10 +1426,10 @@ int Server::main(const std::vector & /*args*/) // on ARM processors it can show only enabled at current moment cores LOG_FMT_INFO( log, - "Available RAM = {}; physical cores = {}; threads = {}.", - formatReadableSizeWithBinarySuffix(getMemoryAmount()), - getNumberOfPhysicalCPUCores(), - std::thread::hardware_concurrency()); + "Available RAM = {}; physical cores = {}; logical cores = {}.", + server_info.memory_info.capacity, + server_info.cpu_info.physical_cores, + server_info.cpu_info.logical_cores); } LOG_FMT_INFO(log, "Ready for connections."); diff --git a/dbms/src/Server/Server.h b/dbms/src/Server/Server.h index 278349f2aa4..07c5b955a92 100644 --- a/dbms/src/Server/Server.h +++ b/dbms/src/Server/Server.h @@ -14,10 +14,10 @@ #pragma once +#include +#include #include -#include "IServer.h" - /** Server provides three interfaces: * 1. HTTP - simple interface for any applications. * 2. TCP - interface for native clickhouse-client and for server to server internal communications. @@ -39,7 +39,7 @@ class Server : public BaseDaemon return BaseDaemon::config(); } - virtual const TiFlashSecurityConfig & securityConfig() const override { return security_config; }; + const TiFlashSecurityConfig & securityConfig() const override { return security_config; }; Poco::Logger & logger() const override { @@ -70,6 +70,8 @@ class Server : public BaseDaemon TiFlashSecurityConfig security_config; + ServerInfo server_info; + class FlashGrpcServerHolder; class TcpHttpServersHolder; }; diff --git a/dbms/src/Server/ServerInfo.cpp b/dbms/src/Server/ServerInfo.cpp new file mode 100644 index 00000000000..9cba40c4775 --- /dev/null +++ b/dbms/src/Server/ServerInfo.cpp @@ -0,0 +1,199 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include + +#include + +namespace DB +{ +using diagnosticspb::ServerInfoItem; +using diagnosticspb::ServerInfoResponse; + +void ServerInfo::parseCPUInfo(const diagnosticspb::ServerInfoItem & cpu_info_item) +{ + for (const auto & pair : cpu_info_item.pairs()) + { + const auto & key = pair.key(); + if (key == "cpu-logical-cores") + { + cpu_info.logical_cores = static_cast(std::stoi(pair.value())); + } + else if (key == "cpu-physical-cores") + { + cpu_info.physical_cores = static_cast(std::stoi(pair.value())); + } + else if (key == "cpu-frequency") + { + cpu_info.frequency = pair.value(); + } + else if (key == "l1-cache-size") + { + cpu_info.l1_cache_size = static_cast(std::stoull(pair.value())); + } + else if (key == "l1-cache-line-size") + { + cpu_info.l1_cache_line_size = static_cast(std::stoi(pair.value())); + } + else if (key == "l2-cache-size") + { + cpu_info.l2_cache_size = static_cast(std::stoull(pair.value())); + } + else if (key == "l2-cache-line-size") + { + cpu_info.l2_cache_line_size = static_cast(std::stoi(pair.value())); + } + else if (key == "l3-cache-size") + { + cpu_info.l3_cache_size = static_cast(std::stoull(pair.value())); + } + else if (key == "l3-cache-line-size") + { + cpu_info.l3_cache_line_size = static_cast(std::stoi(pair.value())); + } + else if (key == "cpu-arch") + { + cpu_info.arch = pair.value(); + } + } +} + +void ServerInfo::parseDiskInfo(const diagnosticspb::ServerInfoItem & disk_info_item) +{ + Disk disk; + disk.name = disk_info_item.name(); + for (const auto & pair : disk_info_item.pairs()) + { + const auto & key = pair.key(); + if (key == "type") + { + if (pair.value() == "HDD") + { + disk.disk_type = Disk::DiskType::HDD; + } + else if (pair.value() == "SSD") + { + disk.disk_type = Disk::DiskType::SSD; + } + else + { + disk.disk_type = Disk::DiskType::UNKNOWN; + } + } + else if (key == "total") + { + disk.total_space = static_cast(std::stoull(pair.value())); + } + else if (key == "free") + { + disk.free_space = static_cast(std::stoull(pair.value())); + } + else if (key == "path") + { + disk.mount_point = pair.value(); + } + else if (key == "fstype") + { + disk.fs_type = pair.value(); + } + } + disk_infos.push_back(disk); +} + +void ServerInfo::parseMemoryInfo(const diagnosticspb::ServerInfoItem & memory_info_item) +{ + for (const auto & pair : memory_info_item.pairs()) + { + if (pair.key() == "capacity") + { + memory_info.capacity = std::stoull(pair.value()); + } + } +} + +void ServerInfo::parseSysInfo(const diagnosticspb::ServerInfoResponse & sys_info_response) +{ + for (const auto & item : sys_info_response.items()) + { + const auto & tp = item.tp(); + if (tp == "cpu") + { + parseCPUInfo(item); + } + else if (tp == "disk") + { + parseDiskInfo(item); + } + else if (tp == "memory") + { + parseMemoryInfo(item); + } + } +} + +String ServerInfo::debugString() const +{ + FmtBuffer fmt_buf; + // append cpu info + fmt_buf.fmtAppend("CPU: \n" + " logical cores: {}\n" + " physical cores: {}\n" + " frequency: {}\n" + " l1 cache size: {}\n" + " l1 cache line size: {}\n" + " l2 cache size: {}\n" + " l2 cache line size: {}\n" + " l3 cache size: {}\n" + " l3 cache line size: {}\n" + " arch: {}\n", + cpu_info.logical_cores, + cpu_info.physical_cores, + cpu_info.frequency, + cpu_info.l1_cache_size, + cpu_info.l1_cache_line_size, + cpu_info.l2_cache_size, + cpu_info.l2_cache_line_size, + cpu_info.l3_cache_size, + cpu_info.l3_cache_line_size, + cpu_info.arch); + // append disk info + { + const static String disk_type_str[] = {"UNKNOWN", "HDD", "SSD"}; + for (const auto & disk_info : disk_infos) + { + fmt_buf.fmtAppend("Disk: \n" + " name: {}\n" + " type: {}\n" + " total space: {}\n" + " free space: {}\n" + " mount point: {}\n" + " fstype: {}\n", + disk_info.name, + disk_type_str[static_cast(disk_info.disk_type)], + disk_info.total_space, + disk_info.free_space, + disk_info.mount_point, + disk_info.fs_type); + } + } + // append memory info + fmt_buf.fmtAppend("Memory: \n" + " capacity: {}\n", + memory_info.capacity); + + return fmt_buf.toString(); +} + +} // namespace DB \ No newline at end of file diff --git a/dbms/src/Server/ServerInfo.h b/dbms/src/Server/ServerInfo.h new file mode 100644 index 00000000000..9663bd37568 --- /dev/null +++ b/dbms/src/Server/ServerInfo.h @@ -0,0 +1,99 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once +#include +#include +#include + +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#ifdef __clang__ +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif +#include +#pragma GCC diagnostic pop + +namespace DB +{ +class ServerInfo +{ +public: + struct CPUInfo + { + /// number of logical CPU cores + UInt16 logical_cores = std::thread::hardware_concurrency(); + /// number of physical CPU cores + UInt16 physical_cores = getNumberOfPhysicalCPUCores(); + /// number of L1 cache size + /// units: Byte + UInt32 l1_cache_size = 16384; // 16KB (typical value) + /// number of L2 cache size + /// units: Byte + UInt32 l2_cache_size = 65536; // 64KB (typical value) + /// number of L3 cache size + /// units: Byte + UInt32 l3_cache_size = 2097152; // 2MB (typical value) + /// number of L1 cache line size + UInt8 l1_cache_line_size = 64; // 64B (typical value) + /// number of L2 cache line size + UInt8 l2_cache_line_size = 64; // 64B (typical value) + /// number of L3 cache line size + UInt8 l3_cache_line_size = 64; // 64B (typical value) + /// CPU architecture + String arch; + /// CPU frequency + String frequency; + }; + + struct Disk + { + String name; + enum DiskType + { + UNKNOWN = 0, + HDD = 1, + SSD = 2 + }; + DiskType disk_type; + UInt64 total_space = 0; + UInt64 free_space = 0; + String mount_point; + String fs_type; + }; + using DiskInfo = std::vector; + + struct MemoryInfo + { + /// total memory size + /// units: Byte + UInt64 capacity = getMemoryAmount(); + }; + + ServerInfo() = default; + ~ServerInfo() = default; + void parseCPUInfo(const diagnosticspb::ServerInfoItem & cpu_info_item); + void parseDiskInfo(const diagnosticspb::ServerInfoItem & disk_info_item); + void parseMemoryInfo(const diagnosticspb::ServerInfoItem & memory_info_item); + void parseSysInfo(const diagnosticspb::ServerInfoResponse & sys_info_response); + String debugString() const; + + CPUInfo cpu_info; + DiskInfo disk_infos; + MemoryInfo memory_info; +}; +} // namespace DB From 8916afe6eb0fccea897df172473b6bb0a98acae0 Mon Sep 17 00:00:00 2001 From: xufei Date: Fri, 10 Jun 2022 17:28:31 +0800 Subject: [PATCH 096/127] move `tunnel_map` to MPPTunnelSet (#5123) ref pingcap/tiflash#5095 --- dbms/src/Flash/Mpp/MPPTask.cpp | 46 +++++++------------- dbms/src/Flash/Mpp/MPPTask.h | 3 -- dbms/src/Flash/Mpp/MPPTunnelSet.cpp | 65 +++++++++++++++++++++++++++++ dbms/src/Flash/Mpp/MPPTunnelSet.h | 21 ++++++---- 4 files changed, 92 insertions(+), 43 deletions(-) diff --git a/dbms/src/Flash/Mpp/MPPTask.cpp b/dbms/src/Flash/Mpp/MPPTask.cpp index 0f18ad582b4..8f9ca8e55e5 100644 --- a/dbms/src/Flash/Mpp/MPPTask.cpp +++ b/dbms/src/Flash/Mpp/MPPTask.cpp @@ -56,6 +56,7 @@ MPPTask::MPPTask(const mpp::TaskMeta & meta_, const ContextPtr & context_) , id(meta.start_ts(), meta.task_id()) , log(Logger::get("MPPTask", id.toString())) , mpp_task_statistics(id, meta.address()) + , needed_threads(0) , schedule_state(ScheduleState::WAITING) {} @@ -78,18 +79,14 @@ MPPTask::~MPPTask() void MPPTask::closeAllTunnels(const String & reason) { - for (auto & it : tunnel_map) - { - it.second->close(reason); - } + if (likely(tunnel_set)) + tunnel_set->close(reason); } void MPPTask::finishWrite() { - for (const auto & it : tunnel_map) - { - it.second->writeDone(); - } + RUNTIME_ASSERT(tunnel_set != nullptr, log, "mpp task without tunnel set"); + tunnel_set->finishWrite(); } void MPPTask::run() @@ -97,15 +94,13 @@ void MPPTask::run() newThreadManager()->scheduleThenDetach(true, "MPPTask", [self = shared_from_this()] { self->runImpl(); }); } -void MPPTask::registerTunnel(const MPPTaskId & id, MPPTunnelPtr tunnel) +void MPPTask::registerTunnel(const MPPTaskId & task_id, MPPTunnelPtr tunnel) { if (status == CANCELLED) throw Exception("the tunnel " + tunnel->id() + " can not been registered, because the task is cancelled"); - if (tunnel_map.find(id) != tunnel_map.end()) - throw Exception("the tunnel " + tunnel->id() + " has been registered"); - - tunnel_map[id] = tunnel; + RUNTIME_ASSERT(tunnel_set != nullptr, log, "mpp task without tunnel set"); + tunnel_set->registerTunnel(task_id, tunnel); } std::pair MPPTask::getTunnel(const ::mpp::EstablishMPPConnectionRequest * request) @@ -120,8 +115,9 @@ std::pair MPPTask::getTunnel(const ::mpp::EstablishMPPConn } MPPTaskId receiver_id{request->receiver_meta().start_ts(), request->receiver_meta().task_id()}; - auto it = tunnel_map.find(receiver_id); - if (it == tunnel_map.end()) + RUNTIME_ASSERT(tunnel_set != nullptr, log, "mpp task without tunnel set"); + auto tunnel_ptr = tunnel_set->getTunnelById(receiver_id); + if (tunnel_ptr == nullptr) { auto err_msg = fmt::format( "can't find tunnel ({} + {})", @@ -129,7 +125,7 @@ std::pair MPPTask::getTunnel(const ::mpp::EstablishMPPConn request->receiver_meta().task_id()); return {nullptr, err_msg}; } - return {it->second, ""}; + return {tunnel_ptr, ""}; } void MPPTask::unregisterTask() @@ -211,7 +207,7 @@ void MPPTask::prepare(const mpp::DispatchTaskRequest & task_request) } // register tunnels - tunnel_set = std::make_shared(); + tunnel_set = std::make_shared(log->identifier()); std::chrono::seconds timeout(task_request.timeout()); for (int i = 0; i < exchange_sender.encoded_task_meta_size(); i++) @@ -225,7 +221,6 @@ void MPPTask::prepare(const mpp::DispatchTaskRequest & task_request) MPPTunnelPtr tunnel = std::make_shared(task_meta, task_request.meta(), timeout, context->getSettingsRef().max_threads, is_local, is_async, log->identifier()); LOG_FMT_DEBUG(log, "begin to register the tunnel {}", tunnel->id()); registerTunnel(MPPTaskId{task_meta.start_ts(), task_meta.task_id()}, tunnel); - tunnel_set->addTunnel(tunnel); if (!dag_context->isRootMPPTask()) { FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::exception_during_mpp_register_tunnel_for_non_root_mpp_task); @@ -369,19 +364,8 @@ void MPPTask::runImpl() void MPPTask::writeErrToAllTunnels(const String & e) { - for (auto & it : tunnel_map) - { - try - { - FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::exception_during_mpp_write_err_to_tunnel); - it.second->write(getPacketWithError(e), true); - } - catch (...) - { - it.second->close("Failed to write error msg to tunnel"); - tryLogCurrentException(log, "Failed to write error " + e + " to tunnel: " + it.second->id()); - } - } + RUNTIME_ASSERT(tunnel_set != nullptr, log, "mpp task without tunnel set"); + tunnel_set->writeError(e); } void MPPTask::cancel(const String & reason) diff --git a/dbms/src/Flash/Mpp/MPPTask.h b/dbms/src/Flash/Mpp/MPPTask.h index c34cae49699..ee434a2f2ff 100644 --- a/dbms/src/Flash/Mpp/MPPTask.h +++ b/dbms/src/Flash/Mpp/MPPTask.h @@ -123,9 +123,6 @@ class MPPTask : public std::enable_shared_from_this MPPTunnelSetPtr tunnel_set; - // which targeted task we should send data by which tunnel. - std::unordered_map tunnel_map; - MPPTaskManager * manager = nullptr; const LoggerPtr log; diff --git a/dbms/src/Flash/Mpp/MPPTunnelSet.cpp b/dbms/src/Flash/Mpp/MPPTunnelSet.cpp index 12de07d4a18..500e9501b08 100644 --- a/dbms/src/Flash/Mpp/MPPTunnelSet.cpp +++ b/dbms/src/Flash/Mpp/MPPTunnelSet.cpp @@ -13,11 +13,17 @@ // limitations under the License. #include +#include #include +#include #include namespace DB { +namespace FailPoints +{ +extern const char exception_during_mpp_write_err_to_tunnel[]; +} // namespace FailPoints namespace { inline mpp::MPPDataPacket serializeToPacket(const tipb::SelectResponse & response) @@ -108,6 +114,65 @@ void MPPTunnelSetBase::write(mpp::MPPDataPacket & packet, int16_t partit tunnels[partition_id]->write(packet); } +template +void MPPTunnelSetBase::writeError(const String & msg) +{ + for (auto & tunnel : tunnels) + { + try + { + FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::exception_during_mpp_write_err_to_tunnel); + tunnel->write(getPacketWithError(msg), true); + } + catch (...) + { + tunnel->close("Failed to write error msg to tunnel"); + tryLogCurrentException(log, "Failed to write error " + msg + " to tunnel: " + tunnel->id()); + } + } +} + +template +void MPPTunnelSetBase::registerTunnel(const MPPTaskId & id, const TunnelPtr & tunnel) +{ + if (id_to_index_map.find(id) != id_to_index_map.end()) + throw Exception("the tunnel " + tunnel->id() + " has been registered"); + + id_to_index_map[id] = tunnels.size(); + tunnels.push_back(tunnel); + if (!tunnel->isLocal()) + { + remote_tunnel_cnt++; + } +} + +template +void MPPTunnelSetBase::close(const String & reason) +{ + for (auto & tunnel : tunnels) + tunnel->close(reason); +} + +template +void MPPTunnelSetBase::finishWrite() +{ + for (auto & tunnel : tunnels) + { + tunnel->writeDone(); + } +} + +template +typename MPPTunnelSetBase::TunnelPtr MPPTunnelSetBase::getTunnelById(const MPPTaskId & id) +{ + auto it = id_to_index_map.find(id); + if (it == id_to_index_map.end()) + { + return nullptr; + } + return tunnels[it->second]; +} + /// Explicit template instantiations - to avoid code bloat in headers. template class MPPTunnelSetBase; diff --git a/dbms/src/Flash/Mpp/MPPTunnelSet.h b/dbms/src/Flash/Mpp/MPPTunnelSet.h index f2279b945cb..021c609f516 100644 --- a/dbms/src/Flash/Mpp/MPPTunnelSet.h +++ b/dbms/src/Flash/Mpp/MPPTunnelSet.h @@ -14,6 +14,7 @@ #pragma once +#include #include #ifdef __clang__ #pragma clang diagnostic push @@ -32,6 +33,9 @@ class MPPTunnelSetBase : private boost::noncopyable { public: using TunnelPtr = std::shared_ptr; + explicit MPPTunnelSetBase(const String & req_id) + : log(Logger::get("MPPTunnelSet", req_id)) + {} void clearExecutionSummaries(tipb::SelectResponse & response); @@ -50,17 +54,14 @@ class MPPTunnelSetBase : private boost::noncopyable // this is a partition writing. void write(tipb::SelectResponse & response, int16_t partition_id); void write(mpp::MPPDataPacket & packet, int16_t partition_id); + void writeError(const String & msg); + void close(const String & reason); + void finishWrite(); + void registerTunnel(const MPPTaskId & id, const TunnelPtr & tunnel); - uint16_t getPartitionNum() const { return tunnels.size(); } + TunnelPtr getTunnelById(const MPPTaskId & id); - void addTunnel(const TunnelPtr & tunnel) - { - tunnels.push_back(tunnel); - if (!tunnel->isLocal()) - { - remote_tunnel_cnt++; - } - } + uint16_t getPartitionNum() const { return tunnels.size(); } int getRemoteTunnelCnt() { @@ -71,6 +72,8 @@ class MPPTunnelSetBase : private boost::noncopyable private: std::vector tunnels; + std::unordered_map id_to_index_map; + const LoggerPtr log; int remote_tunnel_cnt = 0; }; From 8a81342c104a78c9d84b2877c111a4cb87e3cdff Mon Sep 17 00:00:00 2001 From: Lloyd-Pottiger <60744015+Lloyd-Pottiger@users.noreply.github.com> Date: Fri, 10 Jun 2022 21:42:30 +0800 Subject: [PATCH 097/127] add some notes about `getMemoryAmount()` and `getNumberOfPhysicalCPUCores()` (#5126) close pingcap/tiflash#5125 --- dbms/src/Common/getNumberOfPhysicalCPUCores.h | 1 + dbms/src/Storages/DeltaMerge/workload/Handle.h | 6 ++++-- libs/libcommon/include/common/getMemoryAmount.h | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dbms/src/Common/getNumberOfPhysicalCPUCores.h b/dbms/src/Common/getNumberOfPhysicalCPUCores.h index 6f7eaef4bb4..b3ab65a66e5 100644 --- a/dbms/src/Common/getNumberOfPhysicalCPUCores.h +++ b/dbms/src/Common/getNumberOfPhysicalCPUCores.h @@ -15,4 +15,5 @@ #pragma once /// Get number of CPU cores without hyper-threading. +/// Note: do not support environment under resource isolation mechanism like Docker, CGroup. unsigned getNumberOfPhysicalCPUCores(); diff --git a/dbms/src/Storages/DeltaMerge/workload/Handle.h b/dbms/src/Storages/DeltaMerge/workload/Handle.h index c4949c15a1f..2bbd1bd409d 100644 --- a/dbms/src/Storages/DeltaMerge/workload/Handle.h +++ b/dbms/src/Storages/DeltaMerge/workload/Handle.h @@ -51,14 +51,16 @@ class HandleLock std::vector> getLocks(const std::vector & handles) { - std::vector indexes(handles.size()); + std::vector indexes; + indexes.reserve(handles.size()); for (const auto & h : handles) { indexes.push_back(index(h)); } // Sort mutex indexes to avoid dead lock. sort(indexes.begin(), indexes.end()); - std::vector> locks(indexes.size()); + std::vector> locks; + locks.reserve(indexes.size()); for (auto i : indexes) { locks.push_back(getLockByIndex(i)); diff --git a/libs/libcommon/include/common/getMemoryAmount.h b/libs/libcommon/include/common/getMemoryAmount.h index 98aa87661c3..0807c6f8e12 100644 --- a/libs/libcommon/include/common/getMemoryAmount.h +++ b/libs/libcommon/include/common/getMemoryAmount.h @@ -19,5 +19,6 @@ /** * Returns the size of physical memory (RAM) in bytes. * Returns 0 on unsupported platform +* Note: do not support environment under resource isolation mechanism like Docker, CGroup. */ uint64_t getMemoryAmount(); From 10bcb06bcd379601014c70b6dd9173bb960b3c32 Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Mon, 13 Jun 2022 12:52:32 +0800 Subject: [PATCH 098/127] *: remove TableFunctionFile (#5098) ref pingcap/tiflash#2019 --- dbms/src/Storages/StorageFile.cpp | 348 ------------------ dbms/src/Storages/StorageFile.h | 100 ----- dbms/src/Storages/registerStorages.cpp | 2 - dbms/src/TableFunctions/TableFunctionFile.cpp | 98 ----- dbms/src/TableFunctions/TableFunctionFile.h | 39 -- .../TableFunctions/registerTableFunctions.cpp | 2 - 6 files changed, 589 deletions(-) delete mode 100644 dbms/src/Storages/StorageFile.cpp delete mode 100644 dbms/src/Storages/StorageFile.h delete mode 100644 dbms/src/TableFunctions/TableFunctionFile.cpp delete mode 100644 dbms/src/TableFunctions/TableFunctionFile.h diff --git a/dbms/src/Storages/StorageFile.cpp b/dbms/src/Storages/StorageFile.cpp deleted file mode 100644 index 4dec4fd5ea0..00000000000 --- a/dbms/src/Storages/StorageFile.cpp +++ /dev/null @@ -1,348 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include - -#include -#include - -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include - -#include -#include - -namespace DB -{ - -namespace ErrorCodes -{ - extern const int CANNOT_WRITE_TO_FILE_DESCRIPTOR; - extern const int CANNOT_SEEK_THROUGH_FILE; - extern const int DATABASE_ACCESS_DENIED; - extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; - extern const int UNKNOWN_IDENTIFIER; - extern const int INCORRECT_FILE_NAME; - extern const int FILE_DOESNT_EXIST; - extern const int EMPTY_LIST_OF_COLUMNS_PASSED; -}; - - -static std::string getTablePath(const std::string & db_dir_path, const std::string & table_name, const std::string & format_name) -{ - return db_dir_path + escapeForFileName(table_name) + "/data." + escapeForFileName(format_name); -} - -/// Both db_dir_path and table_path must be converted to absolute paths (in particular, path cannot contain '..'). -static void checkCreationIsAllowed(Context & context_global, const std::string & db_dir_path, const std::string & table_path, int table_fd) -{ - if (context_global.getApplicationType() != Context::ApplicationType::SERVER) - return; - - if (table_fd >= 0) - throw Exception("Using file descriptor as source of storage isn't allowed for server daemons", ErrorCodes::DATABASE_ACCESS_DENIED); - else if (!startsWith(table_path, db_dir_path)) - throw Exception("Part path " + table_path + " is not inside " + db_dir_path, ErrorCodes::DATABASE_ACCESS_DENIED); - - Poco::File table_path_poco_file = Poco::File(table_path); - if (!table_path_poco_file.exists()) - throw Exception("File " + table_path + " is not exist", ErrorCodes::FILE_DOESNT_EXIST); - else if (table_path_poco_file.isDirectory()) - throw Exception("File " + table_path + " must not be a directory", ErrorCodes::INCORRECT_FILE_NAME); -} - - -StorageFile::StorageFile( - const std::string & table_path_, - int table_fd_, - const std::string & db_dir_path, - const std::string & table_name_, - const std::string & format_name_, - const ColumnsDescription & columns_, - Context & context_) - : IStorage(columns_), - table_name(table_name_), format_name(format_name_), context_global(context_), table_fd(table_fd_) -{ - if (table_fd < 0) /// Will use file - { - use_table_fd = false; - - if (!table_path_.empty()) /// Is user's file - { - Poco::Path poco_path = Poco::Path(table_path_); - if (poco_path.isRelative()) - poco_path = Poco::Path(db_dir_path, poco_path); - - path = poco_path.absolute().toString(); - checkCreationIsAllowed(context_global, db_dir_path, path, table_fd); - is_db_table = false; - } - else /// Is DB's file - { - if (db_dir_path.empty()) - throw Exception("Storage " + getName() + " requires data path", ErrorCodes::INCORRECT_FILE_NAME); - - path = getTablePath(db_dir_path, table_name, format_name); - is_db_table = true; - Poco::File(Poco::Path(path).parent()).createDirectories(); - } - } - else /// Will use FD - { - checkCreationIsAllowed(context_global, db_dir_path, path, table_fd); - - is_db_table = false; - use_table_fd = true; - - /// Save initial offset, it will be used for repeating SELECTs - /// If FD isn't seekable (lseek returns -1), then the second and subsequent SELECTs will fail. - table_fd_init_offset = lseek(table_fd, 0, SEEK_CUR); - } -} - - -class StorageFileBlockInputStream : public IProfilingBlockInputStream -{ -public: - StorageFileBlockInputStream(StorageFile & storage_, const Context & context, size_t max_block_size) - : storage(storage_) - { - if (storage.use_table_fd) - { - storage.rwlock.lock(); - - /// We could use common ReadBuffer and WriteBuffer in storage to leverage cache - /// and add ability to seek unseekable files, but cache sync isn't supported. - - if (storage.table_fd_was_used) /// We need seek to initial position - { - if (storage.table_fd_init_offset < 0) - throw Exception("File descriptor isn't seekable, inside " + storage.getName(), ErrorCodes::CANNOT_SEEK_THROUGH_FILE); - - /// ReadBuffer's seek() doesn't make sence, since cache is empty - if (lseek(storage.table_fd, storage.table_fd_init_offset, SEEK_SET) < 0) - throwFromErrno("Cannot seek file descriptor, inside " + storage.getName(), ErrorCodes::CANNOT_SEEK_THROUGH_FILE); - } - - storage.table_fd_was_used = true; - read_buf = std::make_unique(storage.table_fd); - } - else - { - storage.rwlock.lock_shared(); - - read_buf = std::make_unique(storage.path); - } - - reader = FormatFactory().getInput(storage.format_name, *read_buf, storage.getSampleBlock(), context, max_block_size); - } - - ~StorageFileBlockInputStream() override - { - if (storage.use_table_fd) - storage.rwlock.unlock(); - else - storage.rwlock.unlock_shared(); - } - - String getName() const override - { - return storage.getName(); - } - - Block readImpl() override - { - return reader->read(); - } - - Block getHeader() const override { return reader->getHeader(); }; - - void readPrefixImpl() override - { - reader->readPrefix(); - } - - void readSuffixImpl() override - { - reader->readSuffix(); - } - -private: - StorageFile & storage; - Block sample_block; - std::unique_ptr read_buf; - BlockInputStreamPtr reader; -}; - - -BlockInputStreams StorageFile::read( - const Names & /*column_names*/, - const SelectQueryInfo & /*query_info*/, - const Context & context, - QueryProcessingStage::Enum & /*processed_stage*/, - size_t max_block_size, - unsigned /*num_streams*/) -{ - return BlockInputStreams(1, std::make_shared(*this, context, max_block_size)); -} - - -class StorageFileBlockOutputStream : public IBlockOutputStream -{ -public: - explicit StorageFileBlockOutputStream(StorageFile & storage_) - : storage(storage_), lock(storage.rwlock) - { - if (storage.use_table_fd) - { - /** NOTE: Using real file binded to FD may be misleading: - * SELECT *; INSERT insert_data; SELECT *; last SELECT returns initil_fd_data + insert_data - * INSERT data; SELECT *; last SELECT returns only insert_data - */ - storage.table_fd_was_used = true; - write_buf = std::make_unique(storage.table_fd); - } - else - { - write_buf = std::make_unique(storage.path, DBMS_DEFAULT_BUFFER_SIZE, O_WRONLY | O_APPEND | O_CREAT); - } - - writer = FormatFactory().getOutput(storage.format_name, *write_buf, storage.getSampleBlock(), storage.context_global); - } - - Block getHeader() const override { return storage.getSampleBlock(); } - - void write(const Block & block) override - { - writer->write(block); - } - - void writePrefix() override - { - writer->writePrefix(); - } - - void writeSuffix() override - { - writer->writeSuffix(); - } - - void flush() override - { - writer->flush(); - } - -private: - StorageFile & storage; - std::unique_lock lock; - std::unique_ptr write_buf; - BlockOutputStreamPtr writer; -}; - -BlockOutputStreamPtr StorageFile::write( - const ASTPtr & /*query*/, - const Settings & /*settings*/) -{ - return std::make_shared(*this); -} - - -void StorageFile::drop() -{ - /// Extra actions are not required. -} - - -void StorageFile::rename(const String & new_path_to_db, const String & /*new_database_name*/, const String & new_table_name) -{ - if (!is_db_table) - throw Exception("Can't rename table '" + table_name + "' binded to user-defined file (or FD)", ErrorCodes::DATABASE_ACCESS_DENIED); - - std::unique_lock lock(rwlock); - - std::string path_new = getTablePath(new_path_to_db, new_table_name, format_name); - Poco::File(Poco::Path(path_new).parent()).createDirectories(); - Poco::File(path).renameTo(path_new); - - path = std::move(path_new); -} - - -void registerStorageFile(StorageFactory & factory) -{ - factory.registerStorage("File", [](const StorageFactory::Arguments & args) - { - ASTs & engine_args = args.engine_args; - - if (!(engine_args.size() == 1 || engine_args.size() == 2)) - throw Exception( - "Storage File requires 1 or 2 arguments: name of used format and source.", - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - - engine_args[0] = evaluateConstantExpressionOrIdentifierAsLiteral(engine_args[0], args.local_context); - String format_name = static_cast(*engine_args[0]).value.safeGet(); - - int source_fd = -1; - String source_path; - if (engine_args.size() >= 2) - { - /// Will use FD if engine_args[1] is int literal or identifier with std* name - - if (const ASTIdentifier * identifier = typeid_cast(engine_args[1].get())) - { - if (identifier->name == "stdin") - source_fd = STDIN_FILENO; - else if (identifier->name == "stdout") - source_fd = STDOUT_FILENO; - else if (identifier->name == "stderr") - source_fd = STDERR_FILENO; - else - throw Exception("Unknown identifier '" + identifier->name + "' in second arg of File storage constructor", - ErrorCodes::UNKNOWN_IDENTIFIER); - } - else if (const ASTLiteral * literal = typeid_cast(engine_args[1].get())) - { - auto type = literal->value.getType(); - if (type == Field::Types::Int64) - source_fd = static_cast(literal->value.get()); - else if (type == Field::Types::UInt64) - source_fd = static_cast(literal->value.get()); - else if (type == Field::Types::String) - source_path = literal->value.get(); - } - } - - return StorageFile::create( - source_path, source_fd, - args.data_path, - args.table_name, format_name, args.columns, - args.context); - }); -} - -} diff --git a/dbms/src/Storages/StorageFile.h b/dbms/src/Storages/StorageFile.h deleted file mode 100644 index ca46f7f366e..00000000000 --- a/dbms/src/Storages/StorageFile.h +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include -#include -#include -#include - -#include -#include -#include - - -namespace DB -{ -class StorageFileBlockInputStream; -class StorageFileBlockOutputStream; - -class StorageFile : public ext::SharedPtrHelper - , public IStorage -{ -public: - std::string getName() const override - { - return "File"; - } - - std::string getTableName() const override - { - return table_name; - } - - BlockInputStreams read( - const Names & column_names, - const SelectQueryInfo & query_info, - const Context & context, - QueryProcessingStage::Enum & processed_stage, - size_t max_block_size, - unsigned num_streams) override; - - BlockOutputStreamPtr write( - const ASTPtr & query, - const Settings & settings) override; - - void drop() override; - - void rename(const String & new_path_to_db, const String & new_database_name, const String & new_table_name) override; - - String getDataPath() const override { return path; } - -protected: - friend class StorageFileBlockInputStream; - friend class StorageFileBlockOutputStream; - - /** there are three options (ordered by priority): - - use specified file descriptor if (fd >= 0) - - use specified table_path if it isn't empty - - create own table inside data/db/table/ - */ - StorageFile( - const std::string & table_path_, - int table_fd_, - const std::string & db_dir_path, - const std::string & table_name_, - const std::string & format_name_, - const ColumnsDescription & columns_, - Context & context_); - -private: - std::string table_name; - std::string format_name; - Context & context_global; - - std::string path; - int table_fd = -1; - - bool is_db_table = true; /// Table is stored in real database, not user's file - bool use_table_fd = false; /// Use table_fd insted of path - std::atomic table_fd_was_used{false}; /// To detect repeating reads from stdin - off_t table_fd_init_offset = -1; /// Initial position of fd, used for repeating reads - - mutable std::shared_mutex rwlock; - - Poco::Logger * log = &Poco::Logger::get("StorageFile"); -}; - -} // namespace DB diff --git a/dbms/src/Storages/registerStorages.cpp b/dbms/src/Storages/registerStorages.cpp index a709be0b017..ddf815316ab 100644 --- a/dbms/src/Storages/registerStorages.cpp +++ b/dbms/src/Storages/registerStorages.cpp @@ -27,7 +27,6 @@ void registerStorageNull(StorageFactory & factory); void registerStorageMerge(StorageFactory & factory); void registerStorageBuffer(StorageFactory & factory); void registerStorageMemory(StorageFactory & factory); -void registerStorageFile(StorageFactory & factory); void registerStorageDictionary(StorageFactory & factory); void registerStorageSet(StorageFactory & factory); void registerStorageJoin(StorageFactory & factory); @@ -47,7 +46,6 @@ void registerStorages() registerStorageMerge(factory); registerStorageBuffer(factory); registerStorageMemory(factory); - registerStorageFile(factory); registerStorageDictionary(factory); registerStorageSet(factory); registerStorageJoin(factory); diff --git a/dbms/src/TableFunctions/TableFunctionFile.cpp b/dbms/src/TableFunctions/TableFunctionFile.cpp deleted file mode 100644 index 0ff1a5b443f..00000000000 --- a/dbms/src/TableFunctions/TableFunctionFile.cpp +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace DB -{ -namespace ErrorCodes -{ -extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; -extern const int DATABASE_ACCESS_DENIED; -} // namespace ErrorCodes - -StoragePtr TableFunctionFile::executeImpl(const ASTPtr & ast_function, const Context & context) const -{ - // Parse args - ASTs & args_func = typeid_cast(*ast_function).children; - - if (args_func.size() != 1) - throw Exception("Table function '" + getName() + "' must have arguments.", ErrorCodes::LOGICAL_ERROR); - - ASTs & args = typeid_cast(*args_func.at(0)).children; - - if (args.size() != 3) - throw Exception("Table function '" + getName() + "' requires exactly 3 arguments: path, format and structure.", - ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - - for (size_t i = 0; i < 3; ++i) - args[i] = evaluateConstantExpressionOrIdentifierAsLiteral(args[i], context); - - std::string path = static_cast(*args[0]).value.safeGet(); - std::string format = static_cast(*args[1]).value.safeGet(); - std::string structure = static_cast(*args[2]).value.safeGet(); - - // Create sample block - std::vector structure_vals; - boost::split(structure_vals, structure, boost::algorithm::is_any_of(" ,"), boost::algorithm::token_compress_on); - - if (structure_vals.size() % 2 != 0) - throw Exception("Odd number of elements in section structure: must be a list of name type pairs", ErrorCodes::LOGICAL_ERROR); - - Block sample_block; - const DataTypeFactory & data_type_factory = DataTypeFactory::instance(); - - for (size_t i = 0, size = structure_vals.size(); i < size; i += 2) - { - ColumnWithTypeAndName column; - column.name = structure_vals[i]; - column.type = data_type_factory.get(structure_vals[i + 1]); - column.column = column.type->createColumn(); - sample_block.insert(std::move(column)); - } - - // Create table - StoragePtr storage = StorageFile::create( - path, - -1, - context.getUserFilesPath(), - getName(), - format, - ColumnsDescription{sample_block.getNamesAndTypesList()}, - const_cast(context)); - - storage->startup(); - - return storage; -} - - -void registerTableFunctionFile(TableFunctionFactory & factory) -{ - factory.registerFunction(); -} - -} // namespace DB diff --git a/dbms/src/TableFunctions/TableFunctionFile.h b/dbms/src/TableFunctions/TableFunctionFile.h deleted file mode 100644 index dda367c2679..00000000000 --- a/dbms/src/TableFunctions/TableFunctionFile.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - - -namespace DB -{ -/* file(path, format, structure) - creates a temporary storage from file - * - * - * The file must be in the clickhouse data directory. - * The relative path begins with the clickhouse data directory. - */ -class TableFunctionFile : public ITableFunction -{ -public: - static constexpr auto name = "file"; - std::string getName() const override { return name; } - -private: - StoragePtr executeImpl(const ASTPtr & ast_function, const Context & context) const override; -}; - - -} // namespace DB diff --git a/dbms/src/TableFunctions/registerTableFunctions.cpp b/dbms/src/TableFunctions/registerTableFunctions.cpp index 2eac0ce0548..bfe219eec62 100644 --- a/dbms/src/TableFunctions/registerTableFunctions.cpp +++ b/dbms/src/TableFunctions/registerTableFunctions.cpp @@ -22,7 +22,6 @@ namespace DB void registerTableFunctionMerge(TableFunctionFactory & factory); void registerTableFunctionNumbers(TableFunctionFactory & factory); void registerTableFunctionCatBoostPool(TableFunctionFactory & factory); -void registerTableFunctionFile(TableFunctionFactory & factory); void registerTableFunctions() { auto & factory = TableFunctionFactory::instance(); @@ -30,7 +29,6 @@ void registerTableFunctions() registerTableFunctionMerge(factory); registerTableFunctionNumbers(factory); registerTableFunctionCatBoostPool(factory); - registerTableFunctionFile(factory); } } // namespace DB From d54fcc7723606ea92ffa787b0d06ae408f303fb5 Mon Sep 17 00:00:00 2001 From: Grace Cai Date: Mon, 13 Jun 2022 14:44:33 +0800 Subject: [PATCH 099/127] README.md: update URL of TiDB Cloud free trial (#5133) close pingcap/tiflash#4936, ref pingcap/tidb#35316 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a2217b9a42..02af727105b 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ TiFlash repository is based on [ClickHouse](https://github.com/ClickHouse/ClickH ### Start with TiDB Cloud -Quickly explore TiFlash with [a free trial of TiDB Cloud](https://tidbcloud.com/signup). +Quickly explore TiFlash with [a free trial of TiDB Cloud](https://tidbcloud.com/free-trial). See [TiDB Cloud Quick Start Guide](https://docs.pingcap.com/tidbcloud/tidb-cloud-quickstart). From 9c8a58884b3f61afd17a4c1a132dbd78495f3037 Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Mon, 13 Jun 2022 15:46:32 +0800 Subject: [PATCH 100/127] *: remove CatBoost (#5131) ref pingcap/tiflash#2019 --- dbms/src/Storages/StorageCatBoostPool.cpp | 287 ------------------ dbms/src/Storages/StorageCatBoostPool.h | 100 ------ .../TableFunctionCatBoostPool.cpp | 68 ----- .../TableFunctionCatBoostPool.h | 35 --- .../TableFunctions/registerTableFunctions.cpp | 3 +- 5 files changed, 1 insertion(+), 492 deletions(-) delete mode 100644 dbms/src/Storages/StorageCatBoostPool.cpp delete mode 100644 dbms/src/Storages/StorageCatBoostPool.h delete mode 100644 dbms/src/TableFunctions/TableFunctionCatBoostPool.cpp delete mode 100644 dbms/src/TableFunctions/TableFunctionCatBoostPool.h diff --git a/dbms/src/Storages/StorageCatBoostPool.cpp b/dbms/src/Storages/StorageCatBoostPool.cpp deleted file mode 100644 index 317cac21d52..00000000000 --- a/dbms/src/Storages/StorageCatBoostPool.cpp +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace DB -{ - -namespace ErrorCodes -{ - extern const int CANNOT_OPEN_FILE; - extern const int CANNOT_PARSE_TEXT; - extern const int DATABASE_ACCESS_DENIED; -} - -namespace -{ -class CatBoostDatasetBlockInputStream : public IProfilingBlockInputStream -{ -public: - - CatBoostDatasetBlockInputStream(const std::string & file_name, const std::string & format_name, - const Block & sample_block, const Context & context, size_t max_block_size) - : file_name(file_name), format_name(format_name) - { - read_buf = std::make_unique(file_name); - reader = FormatFactory().getInput(format_name, *read_buf, sample_block, context, max_block_size); - } - - String getName() const override - { - return "CatBoostDataset"; - } - - Block readImpl() override - { - return reader->read(); - } - - void readPrefixImpl() override - { - reader->readPrefix(); - } - - void readSuffixImpl() override - { - reader->readSuffix(); - } - - Block getHeader() const override { return sample_block; }; - -private: - Block sample_block; - std::unique_ptr read_buf; - BlockInputStreamPtr reader; - std::string file_name; - std::string format_name; -}; - -} - -static boost::filesystem::path canonicalPath(std::string && path) -{ - return boost::filesystem::canonical(boost::filesystem::path(path)); -} - -static std::string resolvePath(const boost::filesystem::path & base_path, std::string && path) -{ - boost::filesystem::path resolved_path(path); - if (!resolved_path.is_absolute()) - return (base_path / resolved_path).string(); - return resolved_path.string(); -} - -static void checkCreationIsAllowed(const String & base_path, const String & path) -{ - if (base_path != path.substr(0, base_path.size())) - throw Exception( - "Using file descriptor or user specified path as source of storage isn't allowed for server daemons", - ErrorCodes::DATABASE_ACCESS_DENIED); -} - - -StorageCatBoostPool::StorageCatBoostPool(const Context & context, - String column_description_file_name_, - String data_description_file_name_) - : column_description_file_name(std::move(column_description_file_name_)), - data_description_file_name(std::move(data_description_file_name_)) -{ - auto base_path = canonicalPath(context.getPath()); - column_description_file_name = resolvePath(base_path, std::move(column_description_file_name)); - data_description_file_name = resolvePath(base_path, std::move(data_description_file_name)); - if (context.getApplicationType() == Context::ApplicationType::SERVER) - { - const auto & base_path_str = base_path.string(); - checkCreationIsAllowed(base_path_str, column_description_file_name); - checkCreationIsAllowed(base_path_str, data_description_file_name); - } - - parseColumnDescription(); - createSampleBlockAndColumns(); -} - -std::string StorageCatBoostPool::getColumnTypesString(const ColumnTypesMap & columnTypesMap) -{ - std::string types_string; - bool first = true; - for (const auto & value : columnTypesMap) - { - if (!first) - types_string.append(", "); - - first = false; - types_string += value.first; - } - - return types_string; -} - -void StorageCatBoostPool::checkDatasetDescription() -{ - std::ifstream in(data_description_file_name); - if (!in.good()) - throw Exception("Cannot open file: " + data_description_file_name, ErrorCodes::CANNOT_OPEN_FILE); - - std::string line; - if (!std::getline(in, line)) - throw Exception("File is empty: " + data_description_file_name, ErrorCodes::CANNOT_PARSE_TEXT); - - size_t columns_count = 1; - for (char sym : line) - if (sym == '\t') - ++columns_count; - - columns_description.resize(columns_count); -} - -void StorageCatBoostPool::parseColumnDescription() -{ - /// NOTE: simple parsing - /// TODO: use ReadBufferFromFile - - checkDatasetDescription(); - - std::ifstream in(column_description_file_name); - if (!in.good()) - throw Exception("Cannot open file: " + column_description_file_name, ErrorCodes::CANNOT_OPEN_FILE); - - std::string line; - size_t line_num = 0; - auto column_types_map = getColumnTypesMap(); - auto column_types_string = getColumnTypesString(column_types_map); - - /// Enumerate default names for columns as Auxiliary, Auxiliary1, Auxiliary2, ... - std::map columns_per_type_count; - - while (std::getline(in, line)) - { - ++line_num; - std::string str_line_num = std::to_string(line_num); - - if (line.empty()) - continue; - - std::istringstream iss(line); - std::vector tokens; - std::string token; - while (std::getline(iss, token, '\t')) - tokens.push_back(token); - - if (tokens.size() != 2 && tokens.size() != 3) - throw Exception("Cannot parse column description at line " + str_line_num + " '" + line + "' " - + ": expected 2 or 3 columns, got " + std::to_string(tokens.size()), - ErrorCodes::CANNOT_PARSE_TEXT); - - std::string str_id = tokens[0]; - std::string col_type = tokens[1]; - std::string col_alias = tokens.size() > 2 ? tokens[2] : ""; - - size_t num_id; - try - { - num_id = std::stoull(str_id); - } - catch (std::exception & e) - { - throw Exception("Cannot parse column index at row " + str_line_num + ": " + e.what(), - ErrorCodes::CANNOT_PARSE_TEXT); - } - - if (num_id >= columns_description.size()) - throw Exception("Invalid index at row " + str_line_num + ": " + str_id - + ", expected in range [0, " + std::to_string(columns_description.size()) + ")", - ErrorCodes::CANNOT_PARSE_TEXT); - - if (column_types_map.count(col_type) == 0) - throw Exception("Invalid column type: " + col_type + ", expected: " + column_types_string, - ErrorCodes::CANNOT_PARSE_TEXT); - - auto type = column_types_map[col_type]; - - std::string col_name; - - bool is_feature_column = type == DatasetColumnType::Num || type == DatasetColumnType::Categ; - auto & col_number = columns_per_type_count[type]; - /// If column is not feature skip '0' after the name (to use 'Target' instead of 'Target0'). - col_name = col_type + (is_feature_column || col_number ? std::to_string(col_number) : ""); - ++col_number; - - columns_description[num_id] = ColumnDescription(col_name, col_alias, type); - } -} - -void StorageCatBoostPool::createSampleBlockAndColumns() -{ - ColumnsDescription columns; - NamesAndTypesList cat_columns; - NamesAndTypesList num_columns; - sample_block.clear(); - for (auto & desc : columns_description) - { - DataTypePtr type; - if (desc.column_type == DatasetColumnType::Categ - || desc.column_type == DatasetColumnType::Auxiliary - || desc.column_type == DatasetColumnType::DocId) - type = std::make_shared(); - else - type = std::make_shared(); - - if (desc.column_type == DatasetColumnType::Categ) - cat_columns.emplace_back(desc.column_name, type); - else if (desc.column_type == DatasetColumnType::Num) - num_columns.emplace_back(desc.column_name, type); - else - columns.materialized.emplace_back(desc.column_name, type); - - if (!desc.alias.empty()) - { - auto alias = std::make_shared(desc.column_name); - columns.defaults[desc.alias] = {ColumnDefaultKind::Alias, alias}; - columns.aliases.emplace_back(desc.alias, type); - } - - sample_block.insert(ColumnWithTypeAndName(type, desc.column_name)); - } - columns.ordinary.insert(columns.ordinary.end(), num_columns.begin(), num_columns.end()); - columns.ordinary.insert(columns.ordinary.end(), cat_columns.begin(), cat_columns.end()); - - setColumns(columns); -} - -BlockInputStreams StorageCatBoostPool::read(const Names & column_names, - const SelectQueryInfo & /*query_info*/, - const Context & context, - QueryProcessingStage::Enum & /*processed_stage*/, - size_t max_block_size, - unsigned /*threads*/) -{ - auto stream = std::make_shared( - data_description_file_name, "TSV", sample_block, context, max_block_size); - - auto filter_stream = std::make_shared(stream, column_names, false); - return { filter_stream }; -} - -} diff --git a/dbms/src/Storages/StorageCatBoostPool.h b/dbms/src/Storages/StorageCatBoostPool.h deleted file mode 100644 index 0f4f7c2cede..00000000000 --- a/dbms/src/Storages/StorageCatBoostPool.h +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include -#include - -#include - -namespace DB -{ -class StorageCatBoostPool : public ext::SharedPtrHelper - , public IStorage -{ -public: - std::string getName() const override { return "CatBoostPool"; } - - std::string getTableName() const override { return table_name; } - - BlockInputStreams read(const Names & column_names, - const SelectQueryInfo & query_info, - const Context & context, - QueryProcessingStage::Enum & processed_stage, - size_t max_block_size, - unsigned threads) override; - -private: - String table_name; - - String column_description_file_name; - String data_description_file_name; - Block sample_block; - - enum class DatasetColumnType - { - Target, - Num, - Categ, - Auxiliary, - DocId, - Weight, - Baseline - }; - - using ColumnTypesMap = std::map; - - ColumnTypesMap getColumnTypesMap() const - { - return { - {"Target", DatasetColumnType::Target}, - {"Num", DatasetColumnType::Num}, - {"Categ", DatasetColumnType::Categ}, - {"Auxiliary", DatasetColumnType::Auxiliary}, - {"DocId", DatasetColumnType::DocId}, - {"Weight", DatasetColumnType::Weight}, - {"Baseline", DatasetColumnType::Baseline}, - }; - }; - - std::string getColumnTypesString(const ColumnTypesMap & columnTypesMap); - - struct ColumnDescription - { - std::string column_name; - std::string alias; - DatasetColumnType column_type; - - ColumnDescription() - : column_type(DatasetColumnType::Num) - {} - ColumnDescription(std::string column_name, std::string alias, DatasetColumnType column_type) - : column_name(std::move(column_name)) - , alias(std::move(alias)) - , column_type(column_type) - {} - }; - - std::vector columns_description; - - void checkDatasetDescription(); - void parseColumnDescription(); - void createSampleBlockAndColumns(); - -protected: - StorageCatBoostPool(const Context & context, String column_description_file_name, String data_description_file_name); -}; - -} // namespace DB diff --git a/dbms/src/TableFunctions/TableFunctionCatBoostPool.cpp b/dbms/src/TableFunctions/TableFunctionCatBoostPool.cpp deleted file mode 100644 index ab5cd7e5849..00000000000 --- a/dbms/src/TableFunctions/TableFunctionCatBoostPool.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include -#include - - -namespace DB -{ -namespace ErrorCodes -{ -extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; -extern const int BAD_ARGUMENTS; -} // namespace ErrorCodes - - -StoragePtr TableFunctionCatBoostPool::executeImpl(const ASTPtr & ast_function, const Context & context) const -{ - ASTs & args_func = typeid_cast(*ast_function).children; - - std::string err = "Table function '" + getName() + "' requires 2 parameters: " - + "column descriptions file, dataset description file"; - - if (args_func.size() != 1) - throw Exception(err, ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - - ASTs & args = typeid_cast(*args_func.at(0)).children; - - if (args.size() != 2) - throw Exception(err, ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - - auto getStringLiteral = [](const IAST & node, const char * description) { - auto lit = typeid_cast(&node); - if (!lit) - throw Exception(description + String(" must be string literal (in single quotes)."), ErrorCodes::BAD_ARGUMENTS); - - if (lit->value.getType() != Field::Types::String) - throw Exception(description + String(" must be string literal (in single quotes)."), ErrorCodes::BAD_ARGUMENTS); - - return safeGet(lit->value); - }; - String column_descriptions_file = getStringLiteral(*args[0], "Column descriptions file"); - String dataset_description_file = getStringLiteral(*args[1], "Dataset description file"); - - return StorageCatBoostPool::create(context, column_descriptions_file, dataset_description_file); -} - -void registerTableFunctionCatBoostPool(TableFunctionFactory & factory) -{ - factory.registerFunction(); -} - -} // namespace DB diff --git a/dbms/src/TableFunctions/TableFunctionCatBoostPool.h b/dbms/src/TableFunctions/TableFunctionCatBoostPool.h deleted file mode 100644 index 0b5b32dfffe..00000000000 --- a/dbms/src/TableFunctions/TableFunctionCatBoostPool.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#pragma once - -#include - - -namespace DB -{ -/* catboostPool('column_descriptions_file', 'dataset_description_file') - * Create storage from CatBoost dataset. - */ -class TableFunctionCatBoostPool : public ITableFunction -{ -public: - static constexpr auto name = "catBoostPool"; - std::string getName() const override { return name; } - -private: - StoragePtr executeImpl(const ASTPtr & ast_function, const Context & context) const override; -}; - -} // namespace DB diff --git a/dbms/src/TableFunctions/registerTableFunctions.cpp b/dbms/src/TableFunctions/registerTableFunctions.cpp index bfe219eec62..8449077cc96 100644 --- a/dbms/src/TableFunctions/registerTableFunctions.cpp +++ b/dbms/src/TableFunctions/registerTableFunctions.cpp @@ -21,14 +21,13 @@ namespace DB { void registerTableFunctionMerge(TableFunctionFactory & factory); void registerTableFunctionNumbers(TableFunctionFactory & factory); -void registerTableFunctionCatBoostPool(TableFunctionFactory & factory); + void registerTableFunctions() { auto & factory = TableFunctionFactory::instance(); registerTableFunctionMerge(factory); registerTableFunctionNumbers(factory); - registerTableFunctionCatBoostPool(factory); } } // namespace DB From f4c2e012fc3067193412aca91c6b26e30173c722 Mon Sep 17 00:00:00 2001 From: yanweiqi <592838129@qq.com> Date: Mon, 13 Jun 2022 16:16:35 +0800 Subject: [PATCH 101/127] Test: Mock window function, refactor window function tests (#5021) ref pingcap/tiflash#4609, close pingcap/tiflash#5081 --- .../DataStreams/WindowBlockInputStream.cpp | 18 + dbms/src/DataStreams/WindowBlockInputStream.h | 2 + dbms/src/Debug/astToExecutor.cpp | 199 +++++++- dbms/src/Debug/astToExecutor.h | 56 ++- .../Flash/Coprocessor/DAGExpressionAnalyzer.h | 4 +- .../Coprocessor/DAGQueryBlockInterpreter.h | 2 - dbms/src/Flash/Coprocessor/DAGUtils.cpp | 5 +- .../Coprocessor/collectOutputFieldTypes.cpp | 26 +- dbms/src/Flash/tests/gtest_interpreter.cpp | 80 ++++ dbms/src/Interpreters/WindowDescription.cpp | 36 +- dbms/src/Interpreters/WindowDescription.h | 4 + dbms/src/TestUtils/ExecutorTestUtils.cpp | 34 +- dbms/src/TestUtils/ExecutorTestUtils.h | 57 +-- dbms/src/TestUtils/FunctionTestUtils.cpp | 32 ++ dbms/src/TestUtils/FunctionTestUtils.h | 27 ++ dbms/src/TestUtils/executorSerializer.cpp | 66 ++- dbms/src/TestUtils/mockExecutor.cpp | 56 ++- dbms/src/TestUtils/mockExecutor.h | 19 +- .../TestUtils/tests/gtest_mock_executors.cpp | 12 + .../tests/gtest_window_functions.cpp | 436 ++++++------------ 20 files changed, 823 insertions(+), 348 deletions(-) diff --git a/dbms/src/DataStreams/WindowBlockInputStream.cpp b/dbms/src/DataStreams/WindowBlockInputStream.cpp index bc63db52873..2cc61df8104 100644 --- a/dbms/src/DataStreams/WindowBlockInputStream.cpp +++ b/dbms/src/DataStreams/WindowBlockInputStream.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include namespace DB @@ -574,4 +575,21 @@ void WindowBlockInputStream::tryCalculate() peer_group_number = 1; } } + +void WindowBlockInputStream::appendInfo(FmtBuffer & buffer) const +{ + buffer.append(", function: {"); + buffer.joinStr( + window_description.window_functions_descriptions.begin(), + window_description.window_functions_descriptions.end(), + [&](const auto & func, FmtBuffer & b) { + b.append(func.window_function->getName()); + }, + ", "); + buffer.fmtAppend( + "}}, frame: {{type: {}, boundary_begin: {}, boundary_end: {}}}", + frameTypeToString(window_description.frame.type), + boundaryTypeToString(window_description.frame.begin_type), + boundaryTypeToString(window_description.frame.end_type)); +} } // namespace DB diff --git a/dbms/src/DataStreams/WindowBlockInputStream.h b/dbms/src/DataStreams/WindowBlockInputStream.h index 46b18dec1ee..0ef23aa9f6f 100644 --- a/dbms/src/DataStreams/WindowBlockInputStream.h +++ b/dbms/src/DataStreams/WindowBlockInputStream.h @@ -14,6 +14,7 @@ #pragma once +#include #include #include #include @@ -169,6 +170,7 @@ class WindowBlockInputStream : public IProfilingBlockInputStream protected: Block readImpl() override; + void appendInfo(FmtBuffer & buffer) const override; LoggerPtr log; diff --git a/dbms/src/Debug/astToExecutor.cpp b/dbms/src/Debug/astToExecutor.cpp index 82f894905e6..fec76d7a085 100644 --- a/dbms/src/Debug/astToExecutor.cpp +++ b/dbms/src/Debug/astToExecutor.cpp @@ -24,13 +24,13 @@ #include #include #include -#include #include #include #include namespace DB { +using ASTPartitionByElement = ASTOrderByElement; void literalFieldToTiPBExpr(const ColumnInfo & ci, const Field & val_field, tipb::Expr * expr, Int32 collator_id) { *(expr->mutable_field_type()) = columnInfoToFieldType(ci); @@ -190,6 +190,12 @@ std::unordered_map agg_func_name_to_sig({ {"group_concat", tipb::ExprType::GroupConcat}, }); +std::unordered_map window_func_name_to_sig({ + {"RowNumber", tipb::ExprType::RowNumber}, + {"Rank", tipb::ExprType::Rank}, + {"DenseRank", tipb::ExprType::DenseRank}, +}); + DAGColumnInfo toNullableDAGColumnInfo(const DAGColumnInfo & input) { DAGColumnInfo output = input; @@ -1343,6 +1349,105 @@ void Join::toMPPSubPlan(size_t & executor_index, const DAGProperties & propertie exchange_map[left_exchange_receiver->name] = std::make_pair(left_exchange_receiver, left_exchange_sender); exchange_map[right_exchange_receiver->name] = std::make_pair(right_exchange_receiver, right_exchange_sender); } + +bool Window::toTiPBExecutor(tipb::Executor * tipb_executor, uint32_t collator_id, const MPPInfo & mpp_info, const Context & context) +{ + tipb_executor->set_tp(tipb::ExecType::TypeWindow); + tipb_executor->set_executor_id(name); + tipb::Window * window = tipb_executor->mutable_window(); + auto & input_schema = children[0]->output_schema; + for (const auto & expr : func_descs) + { + tipb::Expr * window_expr = window->add_func_desc(); + const auto * window_func = typeid_cast(expr.get()); + for (const auto & arg : window_func->arguments->children) + { + tipb::Expr * func = window_expr->add_children(); + astToPB(input_schema, arg, func, collator_id, context); + } + auto window_sig_it = window_func_name_to_sig.find(window_func->name); + if (window_sig_it == window_func_name_to_sig.end()) + throw Exception(fmt::format("Unsupported window function {}", window_func->name), ErrorCodes::LOGICAL_ERROR); + auto window_sig = window_sig_it->second; + window_expr->set_tp(window_sig); + auto * ft = window_expr->mutable_field_type(); + // TODO: Maybe more window functions with different field type. + ft->set_tp(TiDB::TypeLongLong); + ft->set_flag(TiDB::ColumnFlagBinary); + ft->set_collate(collator_id); + ft->set_flen(21); + ft->set_decimal(-1); + } + + for (const auto & child : order_by_exprs) + { + auto * elem = typeid_cast(child.get()); + if (!elem) + throw Exception("Invalid order by element", ErrorCodes::LOGICAL_ERROR); + tipb::ByItem * by = window->add_order_by(); + by->set_desc(elem->direction < 0); + tipb::Expr * expr = by->mutable_expr(); + astToPB(children[0]->output_schema, elem->children[0], expr, collator_id, context); + } + + for (const auto & child : partition_by_exprs) + { + auto * elem = typeid_cast(child.get()); + if (!elem) + throw Exception("Invalid partition by element", ErrorCodes::LOGICAL_ERROR); + tipb::ByItem * by = window->add_partition_by(); + by->set_desc(elem->direction < 0); + tipb::Expr * expr = by->mutable_expr(); + astToPB(children[0]->output_schema, elem->children[0], expr, collator_id, context); + } + + if (frame.type.has_value()) + { + tipb::WindowFrame * mut_frame = window->mutable_frame(); + mut_frame->set_type(frame.type.value()); + if (frame.start.has_value()) + { + auto * start = mut_frame->mutable_start(); + start->set_offset(std::get<2>(frame.start.value())); + start->set_unbounded(std::get<1>(frame.start.value())); + start->set_type(std::get<0>(frame.start.value())); + } + + if (frame.end.has_value()) + { + auto * end = mut_frame->mutable_end(); + end->set_offset(std::get<2>(frame.end.value())); + end->set_unbounded(std::get<1>(frame.end.value())); + end->set_type(std::get<0>(frame.end.value())); + } + } + + auto * children_executor = window->mutable_child(); + return children[0]->toTiPBExecutor(children_executor, collator_id, mpp_info, context); +} + +bool Sort::toTiPBExecutor(tipb::Executor * tipb_executor, uint32_t collator_id, const MPPInfo & mpp_info, const Context & context) +{ + tipb_executor->set_tp(tipb::ExecType::TypeSort); + tipb_executor->set_executor_id(name); + tipb::Sort * sort = tipb_executor->mutable_sort(); + sort->set_ispartialsort(is_partial_sort); + + for (const auto & child : by_exprs) + { + auto * elem = typeid_cast(child.get()); + if (!elem) + throw Exception("Invalid order by element", ErrorCodes::LOGICAL_ERROR); + tipb::ByItem * by = sort->add_byitems(); + by->set_desc(elem->direction < 0); + tipb::Expr * expr = by->mutable_expr(); + astToPB(children[0]->output_schema, elem->children[0], expr, collator_id, context); + } + + auto * children_executor = sort->mutable_child(); + return children[0]->toTiPBExecutor(children_executor, collator_id, mpp_info, context); +} + } // namespace mock ExecutorPtr compileTableScan(size_t & executor_index, TableInfo & table_info, String & table_alias, bool append_pk_column) @@ -1561,11 +1666,101 @@ ExecutorPtr compileExchangeSender(ExecutorPtr input, size_t & executor_index, ti return exchange_sender; } - ExecutorPtr compileExchangeReceiver(size_t & executor_index, DAGSchema schema) { ExecutorPtr exchange_receiver = std::make_shared(executor_index, schema); return exchange_receiver; } +ExecutorPtr compileWindow(ExecutorPtr input, size_t & executor_index, ASTPtr func_desc_list, ASTPtr partition_by_expr_list, ASTPtr order_by_expr_list, mock::MockWindowFrame frame) +{ + std::vector partition_columns; + if (partition_by_expr_list != nullptr) + { + for (const auto & child : partition_by_expr_list->children) + { + auto * elem = typeid_cast(child.get()); + if (!elem) + throw Exception("Invalid partition by element", ErrorCodes::LOGICAL_ERROR); + partition_columns.push_back(child); + compileExpr(input->output_schema, elem->children[0]); + } + } + + std::vector order_columns; + if (order_by_expr_list != nullptr) + { + for (const auto & child : order_by_expr_list->children) + { + auto * elem = typeid_cast(child.get()); + if (!elem) + throw Exception("Invalid order by element", ErrorCodes::LOGICAL_ERROR); + order_columns.push_back(child); + compileExpr(input->output_schema, elem->children[0]); + } + } + + DAGSchema output_schema; + output_schema.insert(output_schema.end(), input->output_schema.begin(), input->output_schema.end()); + + std::vector window_exprs; + if (func_desc_list != nullptr) + { + for (const auto & expr : func_desc_list->children) + { + const auto * func = typeid_cast(expr.get()); + window_exprs.push_back(expr); + std::vector children_ci; + for (const auto & arg : func->arguments->children) + { + children_ci.push_back(compileExpr(input->output_schema, arg)); + } + // TODO: add more window functions + TiDB::ColumnInfo ci; + switch (window_func_name_to_sig[func->name]) + { + case tipb::ExprType::RowNumber: + case tipb::ExprType::Rank: + case tipb::ExprType::DenseRank: + { + ci.tp = TiDB::TypeLongLong; + ci.flag = TiDB::ColumnFlagBinary; + break; + } + default: + throw Exception(fmt::format("Unsupported window function {}", func->name), ErrorCodes::LOGICAL_ERROR); + } + output_schema.emplace_back(std::make_pair(func->getColumnName(), ci)); + } + } + + ExecutorPtr window = std::make_shared( + executor_index, + output_schema, + window_exprs, + std::move(partition_columns), + std::move(order_columns), + frame); + window->children.push_back(input); + return window; +} + +ExecutorPtr compileSort(ExecutorPtr input, size_t & executor_index, ASTPtr order_by_expr_list, bool is_partial_sort) +{ + std::vector order_columns; + if (order_by_expr_list != nullptr) + { + for (const auto & child : order_by_expr_list->children) + { + auto * elem = typeid_cast(child.get()); + if (!elem) + throw Exception("Invalid order by element", ErrorCodes::LOGICAL_ERROR); + order_columns.push_back(child); + compileExpr(input->output_schema, elem->children[0]); + } + } + ExecutorPtr sort = std::make_shared(executor_index, input->output_schema, std::move(order_columns), is_partial_sort); + sort->children.push_back(input); + return sort; +} } // namespace DB \ No newline at end of file diff --git a/dbms/src/Debug/astToExecutor.h b/dbms/src/Debug/astToExecutor.h index 37d3f22b6e1..cbd2e5ade3a 100644 --- a/dbms/src/Debug/astToExecutor.h +++ b/dbms/src/Debug/astToExecutor.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,8 @@ #include #include +#include + namespace DB { namespace ErrorCodes @@ -272,6 +275,54 @@ struct Join : Executor void toMPPSubPlan(size_t & executor_index, const DAGProperties & properties, std::unordered_map, std::shared_ptr>> & exchange_map) override; }; + +using MockWindowFrameBound = std::tuple; + +struct MockWindowFrame +{ + std::optional type; + std::optional start; + std::optional end; + // TODO: support calcFuncs +}; + +struct Window : Executor +{ + std::vector func_descs; + std::vector partition_by_exprs; + std::vector order_by_exprs; + MockWindowFrame frame; + + Window(size_t & index_, const DAGSchema & output_schema_, std::vector func_descs_, std::vector partition_by_exprs_, std::vector order_by_exprs_, MockWindowFrame frame_) + : Executor(index_, "window_" + std::to_string(index_), output_schema_) + , func_descs(std::move(func_descs_)) + , partition_by_exprs(std::move(partition_by_exprs_)) + , order_by_exprs(order_by_exprs_) + , frame(frame_) + { + } + // Currently only use Window Executor in Unit Test which don't call columnPrume. + // TODO: call columnPrune in unit test and further benchmark test to eliminate compute process. + void columnPrune(std::unordered_set &) override { throw Exception("Should not reach here"); } + bool toTiPBExecutor(tipb::Executor * tipb_executor, uint32_t collator_id, const MPPInfo & mpp_info, const Context & context) override; +}; + +struct Sort : Executor +{ + std::vector by_exprs; + bool is_partial_sort; + + Sort(size_t & index_, const DAGSchema & output_schema_, std::vector by_exprs_, bool is_partial_sort_) + : Executor(index_, "sort_" + std::to_string(index_), output_schema_) + , by_exprs(by_exprs_) + , is_partial_sort(is_partial_sort_) + { + } + // Currently only use Sort Executor in Unit Test which don't call columnPrume. + // TODO: call columnPrune in unit test and further benchmark test to eliminate compute process. + void columnPrune(std::unordered_set &) override { throw Exception("Should not reach here"); } + bool toTiPBExecutor(tipb::Executor * tipb_executor, uint32_t collator_id, const MPPInfo & mpp_info, const Context & context) override; +}; } // namespace mock using ExecutorPtr = std::shared_ptr; @@ -294,8 +345,9 @@ ExecutorPtr compileExchangeSender(ExecutorPtr input, size_t & executor_index, ti ExecutorPtr compileExchangeReceiver(size_t & executor_index, DAGSchema schema); -void literalFieldToTiPBExpr(const ColumnInfo & ci, const Field & field, tipb::Expr * expr, Int32 collator_id); +ExecutorPtr compileWindow(ExecutorPtr input, size_t & executor_index, ASTPtr func_desc_list, ASTPtr partition_by_expr_list, ASTPtr order_by_expr_list, mock::MockWindowFrame frame); -//TODO: add compileWindow +ExecutorPtr compileSort(ExecutorPtr input, size_t & executor_index, ASTPtr order_by_expr_list, bool is_partial_sort); +void literalFieldToTiPBExpr(const ColumnInfo & ci, const Field & field, tipb::Expr * expr, Int32 collator_id); } // namespace DB \ No newline at end of file diff --git a/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h b/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h index 3b7112af02d..9f201006a88 100644 --- a/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h +++ b/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h @@ -151,11 +151,9 @@ class DAGExpressionAnalyzer : private boost::noncopyable void appendCastAfterWindow( const ExpressionActionsPtr & actions, const tipb::Window & window, - const size_t window_columns_start_index); + size_t window_columns_start_index); -#ifndef DBMS_PUBLIC_GTEST private: -#endif NamesAndTypes buildOrderColumns( const ExpressionActionsPtr & actions, const ::google::protobuf::RepeatedPtrField & order_by); diff --git a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h index e68c4f91cee..0b3b2db9623 100644 --- a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h +++ b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h @@ -54,9 +54,7 @@ class DAGQueryBlockInterpreter BlockInputStreams execute(); -#ifndef DBMS_PUBLIC_GTEST private: -#endif void executeImpl(DAGPipeline & pipeline); void handleMockTableScan(const TiDBTableScan & table_scan, DAGPipeline & pipeline); void handleTableScan(const TiDBTableScan & table_scan, DAGPipeline & pipeline); diff --git a/dbms/src/Flash/Coprocessor/DAGUtils.cpp b/dbms/src/Flash/Coprocessor/DAGUtils.cpp index 87f58131c8c..bea26fe9f99 100644 --- a/dbms/src/Flash/Coprocessor/DAGUtils.cpp +++ b/dbms/src/Flash/Coprocessor/DAGUtils.cpp @@ -29,7 +29,6 @@ #include namespace DB { - const Int8 VAR_SIZE = 0; extern const String uniq_raw_res_name; @@ -770,6 +769,10 @@ const String & getFunctionName(const tipb::Expr & expr) { return getAggFunctionName(expr); } + else if (isWindowFunctionExpr(expr)) + { + return getWindowFunctionName(expr); + } else { auto it = scalar_func_map.find(expr.sig()); diff --git a/dbms/src/Flash/Coprocessor/collectOutputFieldTypes.cpp b/dbms/src/Flash/Coprocessor/collectOutputFieldTypes.cpp index b68279faa13..87744c553e0 100644 --- a/dbms/src/Flash/Coprocessor/collectOutputFieldTypes.cpp +++ b/dbms/src/Flash/Coprocessor/collectOutputFieldTypes.cpp @@ -45,19 +45,36 @@ bool collectForAgg(std::vector & output_field_types, const tipb { for (const auto & expr : agg.agg_func()) { - if (!exprHasValidFieldType(expr)) + if (unlikely(!exprHasValidFieldType(expr))) throw TiFlashException("Agg expression without valid field type", Errors::Coprocessor::BadRequest); output_field_types.push_back(expr.field_type()); } for (const auto & expr : agg.group_by()) { - if (!exprHasValidFieldType(expr)) + if (unlikely(!exprHasValidFieldType(expr))) throw TiFlashException("Group by expression without valid field type", Errors::Coprocessor::BadRequest); output_field_types.push_back(expr.field_type()); } return false; } +bool collectForExecutor(std::vector & output_field_types, const tipb::Executor & executor); +bool collectForWindow(std::vector & output_field_types, const tipb::Executor & executor) +{ + // collect output_field_types of child + getChildren(executor).forEach([&output_field_types](const tipb::Executor & child) { + traverseExecutorTree(child, [&output_field_types](const tipb::Executor & e) { return collectForExecutor(output_field_types, e); }); + }); + + for (const auto & expr : executor.window().func_desc()) + { + if (unlikely(!exprHasValidFieldType(expr))) + throw TiFlashException("Window expression without valid field type", Errors::Coprocessor::BadRequest); + output_field_types.push_back(expr.field_type()); + } + return false; +} + bool collectForReceiver(std::vector & output_field_types, const tipb::ExchangeReceiver & receiver) { for (const auto & field_type : receiver.field_types()) @@ -82,7 +99,6 @@ bool collectForTableScan(std::vector & output_field_types, cons return false; } -bool collectForExecutor(std::vector & output_field_types, const tipb::Executor & executor); bool collectForJoin(std::vector & output_field_types, const tipb::Executor & executor) { // collect output_field_types of children @@ -147,8 +163,8 @@ bool collectForExecutor(std::vector & output_field_types, const case tipb::ExecType::TypeWindow: // Window will only be pushed down in mpp mode. // In mpp mode, ExchangeSender or Sender will return output_field_types directly. - // If not in mpp mode, window executor type is invalid. - throw TiFlashException("Window executor type is invalid in non-mpp mode, should not reach here.", Errors::Coprocessor::Internal); + // If not in mpp mode or debug mode, window executor type is invalid. + return collectForWindow(output_field_types, executor); case tipb::ExecType::TypeExchangeReceiver: return collectForReceiver(output_field_types, executor.exchange_receiver()); case tipb::ExecType::TypeTableScan: diff --git a/dbms/src/Flash/tests/gtest_interpreter.cpp b/dbms/src/Flash/tests/gtest_interpreter.cpp index a6bb8ff1702..ba7d8fd15ee 100644 --- a/dbms/src/Flash/tests/gtest_interpreter.cpp +++ b/dbms/src/Flash/tests/gtest_interpreter.cpp @@ -385,5 +385,85 @@ CreatingSets } CATCH +TEST_F(InterpreterExecuteTest, Window) +try +{ + auto request = context + .scan("test_db", "test_table") + .sort({{"s1", true}, {"s2", false}}, true) + .window(RowNumber(), {"s1", true}, {"s2", false}, buildDefaultRowsFrame()) + .build(context); + { + String expected = R"( +Union: + Expression x 10: + SharedQuery: + Expression: + Window, function: {row_number}, frame: {type: Rows, boundary_begin: Current, boundary_end: Current} + Expression: + MergeSorting, limit = 0 + Union: + PartialSorting x 10: limit = 0 + Expression: + MockTableScan)"; + ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); + } + + request = context.scan("test_db", "test_table") + .sort({{"s1", true}, {"s2", false}}, true) + .window(RowNumber(), {"s1", true}, {"s2", false}, buildDefaultRowsFrame()) + .project({"s1", "s2", "RowNumber()"}) + .build(context); + { + String expected = R"( +Union: + Expression x 10: + Expression: + Expression: + Expression: + SharedQuery: + Expression: + Window, function: {row_number}, frame: {type: Rows, boundary_begin: Current, boundary_end: Current} + Expression: + MergeSorting, limit = 0 + Union: + PartialSorting x 10: limit = 0 + Expression: + MockTableScan)"; + ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); + } + + request = context.scan("test_db", "test_table_1") + .sort({{"s1", true}, {"s2", false}}, true) + .project({"s1", "s2", "s3"}) + .window(RowNumber(), {"s1", true}, {"s1", false}, buildDefaultRowsFrame()) + .project({"s1", "s2", "s3", "RowNumber()"}) + .build(context); + { + String expected = R"( +Union: + Expression x 10: + Expression: + Expression: + Expression: + SharedQuery: + Expression: + Window, function: {row_number}, frame: {type: Rows, boundary_begin: Current, boundary_end: Current} + Union: + Expression x 10: + Expression: + Expression: + SharedQuery: + Expression: + MergeSorting, limit = 0 + Union: + PartialSorting x 10: limit = 0 + Expression: + MockTableScan)"; + ASSERT_BLOCKINPUTSTREAM_EQAUL(expected, request, 10); + } +} +CATCH + } // namespace tests } // namespace DB \ No newline at end of file diff --git a/dbms/src/Interpreters/WindowDescription.cpp b/dbms/src/Interpreters/WindowDescription.cpp index 2ab407bb18e..09d96411673 100644 --- a/dbms/src/Interpreters/WindowDescription.cpp +++ b/dbms/src/Interpreters/WindowDescription.cpp @@ -44,7 +44,7 @@ WindowFrame::FrameType getFrameTypeFromTipb(const tipb::WindowFrameType & type) return WindowFrame::FrameType::Groups; default: throw Exception(ErrorCodes::BAD_ARGUMENTS, - "Unknowed frame type {}", + "Unknown frame type {}", type); } } @@ -60,4 +60,38 @@ void WindowDescription::setWindowFrame(const tipb::WindowFrame & frame_) frame.end_preceding = (frame_.end().type() == tipb::WindowBoundType::Preceding); frame.is_default = false; } + +String frameTypeToString(const WindowFrame::FrameType & type) +{ + switch (type) + { + case WindowFrame::FrameType::Rows: + return "Rows"; + case WindowFrame::FrameType::Groups: + return "Groups"; + case WindowFrame::FrameType::Ranges: + return "Ranges"; + default: + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "Unknown frame type {}", + type); + } +} + +String boundaryTypeToString(const WindowFrame::BoundaryType & type) +{ + switch (type) + { + case WindowFrame::BoundaryType::Unbounded: + return "Unbounded"; + case WindowFrame::BoundaryType::Current: + return "Current"; + case WindowFrame::BoundaryType::Offset: + return "Offset"; + default: + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "Unknown boundary type {}", + type); + } +} } // namespace DB diff --git a/dbms/src/Interpreters/WindowDescription.h b/dbms/src/Interpreters/WindowDescription.h index cdcade1b750..a3c2bac5747 100644 --- a/dbms/src/Interpreters/WindowDescription.h +++ b/dbms/src/Interpreters/WindowDescription.h @@ -87,6 +87,10 @@ struct WindowFrame && other.end_preceding == end_preceding; } }; + +String frameTypeToString(const WindowFrame::FrameType & type); +String boundaryTypeToString(const WindowFrame::BoundaryType & type); + class ExpressionActions; using ExpressionActionsPtr = std::shared_ptr; struct WindowDescription diff --git a/dbms/src/TestUtils/ExecutorTestUtils.cpp b/dbms/src/TestUtils/ExecutorTestUtils.cpp index 67a21d12286..881ebaf88db 100644 --- a/dbms/src/TestUtils/ExecutorTestUtils.cpp +++ b/dbms/src/TestUtils/ExecutorTestUtils.cpp @@ -12,11 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include #include #include #include + +#include + namespace DB::tests { DAGContext & ExecutorTest::getDAGContext() @@ -34,15 +38,20 @@ void ExecutorTest::initializeContext() void ExecutorTest::SetUpTestCase() { - try - { - DB::registerFunctions(); - DB::registerAggregateFunctions(); - } - catch (DB::Exception &) - { - // Maybe another test has already registered, ignore exception here. - } + auto register_func = [](std::function func) { + try + { + func(); + } + catch (DB::Exception &) + { + // Maybe another test has already registered, ignore exception here. + } + }; + + register_func(DB::registerFunctions); + register_func(DB::registerAggregateFunctions); + register_func(DB::registerWindowFunctions); } void ExecutorTest::initializeClientInfo() @@ -125,6 +134,13 @@ void ExecutorTest::executeStreams(const std::shared_ptr & requ executeStreams(request, context.executorIdColumnsMap(), expect_columns, concurrency); } +void ExecutorTest::executeStreamsWithSingleSource(const std::shared_ptr & request, const ColumnsWithTypeAndName & source_columns, const ColumnsWithTypeAndName & expect_columns, SourceType type, size_t concurrency) +{ + std::unordered_map source_columns_map; + source_columns_map[getSourceName(type)] = source_columns; + executeStreams(request, source_columns_map, expect_columns, concurrency); +} + void ExecutorTest::dagRequestEqual(const String & expected_string, const std::shared_ptr & actual) { ASSERT_EQ(Poco::trim(expected_string), Poco::trim(ExecutorSerializer().serialize(actual.get()))); diff --git a/dbms/src/TestUtils/ExecutorTestUtils.h b/dbms/src/TestUtils/ExecutorTestUtils.h index 977b46abbd2..87bb7115bed 100644 --- a/dbms/src/TestUtils/ExecutorTestUtils.h +++ b/dbms/src/TestUtils/ExecutorTestUtils.h @@ -15,15 +15,13 @@ #pragma once #include -#include -#include #include #include #include -#include -#include #include #include +#include + namespace DB::tests { void executeInterpreter(const std::shared_ptr & request, Context & context); @@ -52,6 +50,28 @@ class ExecutorTest : public ::testing::Test void executeInterpreter(const String & expected_string, const std::shared_ptr & request, size_t concurrency); + enum SourceType + { + TableScan, + ExchangeReceiver + }; + + // for single source query, the source executor name is ${type}_0 + static String getSourceName(SourceType type) + { + switch (type) + { + case TableScan: + return "table_scan_0"; + case ExchangeReceiver: + return "exchange_receiver_0"; + default: + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "Unknown Executor Source type {}", + type); + } + } + void executeStreams( const std::shared_ptr & request, std::unordered_map & source_columns_map, @@ -62,29 +82,12 @@ class ExecutorTest : public ::testing::Test const ColumnsWithTypeAndName & expect_columns, size_t concurrency = 1); - template - ColumnWithTypeAndName toNullableVec(const std::vector::FieldType>> & v) - { - return createColumn>(v); - } - - template - ColumnWithTypeAndName toVec(const std::vector::FieldType> & v) - { - return createColumn(v); - } - - template - ColumnWithTypeAndName toNullableVec(String name, const std::vector::FieldType>> & v) - { - return createColumn>(v, name); - } - - template - ColumnWithTypeAndName toVec(String name, const std::vector::FieldType> & v) - { - return createColumn(v, name); - } + void executeStreamsWithSingleSource( + const std::shared_ptr & request, + const ColumnsWithTypeAndName & source_columns, + const ColumnsWithTypeAndName & expect_columns, + SourceType type = TableScan, + size_t concurrency = 1); protected: MockDAGRequestContext context; diff --git a/dbms/src/TestUtils/FunctionTestUtils.cpp b/dbms/src/TestUtils/FunctionTestUtils.cpp index dae07f7123b..637fbf51c00 100644 --- a/dbms/src/TestUtils/FunctionTestUtils.cpp +++ b/dbms/src/TestUtils/FunctionTestUtils.cpp @@ -242,5 +242,37 @@ ColumnWithTypeAndName createOnlyNullColumn(size_t size, const String & name) return {std::move(col), data_type, name}; } +ColumnWithTypeAndName toDatetimeVec(String name, const std::vector & v, int fsp) +{ + std::vector::FieldType> vec; + vec.reserve(v.size()); + for (const auto & value_str : v) + { + Field value = parseMyDateTime(value_str, fsp); + vec.push_back(value.template safeGet()); + } + DataTypePtr data_type = std::make_shared(fsp); + return {makeColumn(data_type, vec), data_type, name, 0}; +} + +ColumnWithTypeAndName toNullableDatetimeVec(String name, const std::vector & v, int fsp) +{ + std::vector::FieldType>> vec; + vec.reserve(v.size()); + for (const auto & value_str : v) + { + if (!value_str.empty()) + { + Field value = parseMyDateTime(value_str, fsp); + vec.push_back(value.template safeGet()); + } + else + { + vec.push_back({}); + } + } + DataTypePtr data_type = makeNullable(std::make_shared(fsp)); + return {makeColumn>(data_type, vec), data_type, name, 0}; +} } // namespace tests } // namespace DB diff --git a/dbms/src/TestUtils/FunctionTestUtils.h b/dbms/src/TestUtils/FunctionTestUtils.h index 7704c69a89f..d6b7351df05 100644 --- a/dbms/src/TestUtils/FunctionTestUtils.h +++ b/dbms/src/TestUtils/FunctionTestUtils.h @@ -654,6 +654,33 @@ ColumnWithTypeAndName createNullableColumn( return createNullableColumn(data_type_args, vec, null_map, name, 0); } +template +ColumnWithTypeAndName toNullableVec(const std::vector::FieldType>> & v) +{ + return createColumn>(v); +} + +template +ColumnWithTypeAndName toVec(const std::vector::FieldType> & v) +{ + return createColumn(v); +} + +template +ColumnWithTypeAndName toNullableVec(String name, const std::vector::FieldType>> & v) +{ + return createColumn>(v, name); +} + +template +ColumnWithTypeAndName toVec(String name, const std::vector::FieldType> & v) +{ + return createColumn(v, name); +} + +ColumnWithTypeAndName toDatetimeVec(String name, const std::vector & v, int fsp); + +ColumnWithTypeAndName toNullableDatetimeVec(String name, const std::vector & v, int fsp); class FunctionTest : public ::testing::Test { protected: diff --git a/dbms/src/TestUtils/executorSerializer.cpp b/dbms/src/TestUtils/executorSerializer.cpp index b8d2b039bd2..a0ae4b11270 100644 --- a/dbms/src/TestUtils/executorSerializer.cpp +++ b/dbms/src/TestUtils/executorSerializer.cpp @@ -204,6 +204,66 @@ void serializeExchangeReceiver(const String & executor_id, const tipb::ExchangeR buf.append("}\n"); } +void serializeWindow(const String & executor_id, const tipb::Window & window [[maybe_unused]], FmtBuffer & buf) +{ + buf.fmtAppend("{} | partition_by: {{", executor_id); + buf.joinStr( + window.partition_by().begin(), + window.partition_by().end(), + [&](const auto & partition_by, FmtBuffer & fb) { + fb.append("("); + serializeExpression(partition_by.expr(), buf); + fb.fmtAppend(", desc: {})", partition_by.desc()); + }, + ", "); + buf.append("}}, order_by: {"); + buf.joinStr( + window.order_by().begin(), + window.order_by().end(), + [&](const auto & order_by, FmtBuffer & fb) { + fb.append("("); + serializeExpression(order_by.expr(), buf); + fb.fmtAppend(", desc: {})", order_by.desc()); + }, + ", "); + buf.append("}, func_desc: {"); + buf.joinStr( + window.func_desc().begin(), + window.func_desc().end(), + [&](const auto & func, FmtBuffer &) { + serializeExpression(func, buf); + }, + ", "); + if (window.has_frame()) + { + buf.append("}, frame: {"); + if (window.frame().has_start()) + { + buf.fmtAppend("start<{}, {}, {}>", window.frame().start().type(), window.frame().start().unbounded(), window.frame().start().offset()); + } + if (window.frame().has_end()) + { + buf.fmtAppend(", end<{}, {}, {}>", window.frame().end().type(), window.frame().end().unbounded(), window.frame().end().offset()); + } + } + buf.append("}\n"); +} + +void serializeSort(const String & executor_id, const tipb::Sort & sort [[maybe_unused]], FmtBuffer & buf) +{ + buf.fmtAppend("{} | isPartialSort: {}, partition_by: {{", executor_id, sort.ispartialsort()); + buf.joinStr( + sort.byitems().begin(), + sort.byitems().end(), + [&](const auto & by, FmtBuffer & fb) { + fb.append("("); + serializeExpression(by.expr(), buf); + fb.fmtAppend(", desc: {})", by.desc()); + }, + ", "); + buf.append("}\n"); +} + void ExecutorSerializer::serialize(const tipb::Executor & root_executor, size_t level) { auto append_str = [&level, this](const tipb::Executor & executor) { @@ -248,9 +308,11 @@ void ExecutorSerializer::serialize(const tipb::Executor & root_executor, size_t serializeExchangeSender(executor.executor_id(), executor.exchange_sender(), buf); break; case tipb::ExecType::TypeSort: - throw TiFlashException("Sort executor is not supported", Errors::Coprocessor::Unimplemented); // todo support sort executor. + serializeSort(executor.executor_id(), executor.sort(), buf); + break; case tipb::ExecType::TypeWindow: - throw TiFlashException("Window executor is not supported", Errors::Coprocessor::Unimplemented); // todo support window executor. + serializeWindow(executor.executor_id(), executor.window(), buf); + break; default: throw TiFlashException("Should not reach here", Errors::Coprocessor::Internal); } diff --git a/dbms/src/TestUtils/mockExecutor.cpp b/dbms/src/TestUtils/mockExecutor.cpp index af939002cff..e1ccbdbb010 100644 --- a/dbms/src/TestUtils/mockExecutor.cpp +++ b/dbms/src/TestUtils/mockExecutor.cpp @@ -23,8 +23,6 @@ #include #include -#include - namespace DB::tests { ASTPtr buildColumn(const String & column_name) @@ -54,6 +52,15 @@ ASTPtr buildOrderByItemList(MockOrderByItems order_by_items) return exp_list; } +MockWindowFrame buildDefaultRowsFrame() +{ + MockWindowFrame frame; + frame.type = tipb::WindowFrameType::Rows; + frame.end = {tipb::WindowBoundType::CurrentRow, false, 0}; + frame.start = {tipb::WindowBoundType::CurrentRow, false, 0}; + return frame; +} + // a mock DAGRequest should prepare its time_zone, flags, encode_type and output_schema. void DAGRequestBuilder::initDAGRequest(tipb::DAGRequest & dag_request) { @@ -96,6 +103,9 @@ DAGRequestBuilder & DAGRequestBuilder::mockTable(const String & db, const String TiDB::ColumnInfo ret; ret.tp = column.second; ret.name = column.first; + // TODO: find a way to assign decimal field's flen. + if (ret.tp == TiDB::TP::TypeNewDecimal) + ret.flen = 65; ret.id = i++; table_info.columns.push_back(std::move(ret)); } @@ -276,6 +286,48 @@ DAGRequestBuilder & DAGRequestBuilder::buildAggregation(ASTPtr agg_funcs, ASTPtr return *this; } +DAGRequestBuilder & DAGRequestBuilder::window(ASTPtr window_func, MockOrderByItem order_by, MockPartitionByItem partition_by, MockWindowFrame frame) +{ + assert(root); + auto window_func_list = std::make_shared(); + window_func_list->children.push_back(window_func); + root = compileWindow(root, getExecutorIndex(), window_func_list, buildOrderByItemList({partition_by}), buildOrderByItemList({order_by}), frame); + return *this; +} + +DAGRequestBuilder & DAGRequestBuilder::window(ASTPtr window_func, MockOrderByItems order_by_list, MockPartitionByItems partition_by_list, MockWindowFrame frame) +{ + assert(root); + auto window_func_list = std::make_shared(); + window_func_list->children.push_back(window_func); + root = compileWindow(root, getExecutorIndex(), window_func_list, buildOrderByItemList(partition_by_list), buildOrderByItemList(order_by_list), frame); + return *this; +} + +DAGRequestBuilder & DAGRequestBuilder::window(MockAsts window_funcs, MockOrderByItems order_by_list, MockPartitionByItems partition_by_list, MockWindowFrame frame) +{ + assert(root); + auto window_func_list = std::make_shared(); + for (const auto & func : window_funcs) + window_func_list->children.push_back(func); + root = compileWindow(root, getExecutorIndex(), window_func_list, buildOrderByItemList(partition_by_list), buildOrderByItemList(order_by_list), frame); + return *this; +} + +DAGRequestBuilder & DAGRequestBuilder::sort(MockOrderByItem order_by, bool is_partial_sort) +{ + assert(root); + root = compileSort(root, getExecutorIndex(), buildOrderByItemList({order_by}), is_partial_sort); + return *this; +} + +DAGRequestBuilder & DAGRequestBuilder::sort(MockOrderByItems order_by_list, bool is_partial_sort) +{ + assert(root); + root = compileSort(root, getExecutorIndex(), buildOrderByItemList(order_by_list), is_partial_sort); + return *this; +} + void MockDAGRequestContext::addMockTable(const MockTableName & name, const MockColumnInfoList & columnInfos) { std::vector v_column_info(columnInfos.size()); diff --git a/dbms/src/TestUtils/mockExecutor.h b/dbms/src/TestUtils/mockExecutor.h index 88d98158b74..d52b5ec674a 100644 --- a/dbms/src/TestUtils/mockExecutor.h +++ b/dbms/src/TestUtils/mockExecutor.h @@ -20,9 +20,6 @@ #include #include -#include -#include - namespace DB::tests { using MockColumnInfo = std::pair; @@ -31,8 +28,11 @@ using MockColumnInfoList = std::initializer_list; using MockTableName = std::pair; using MockOrderByItem = std::pair; using MockOrderByItems = std::initializer_list; +using MockPartitionByItem = std::pair; +using MockPartitionByItems = std::initializer_list; using MockColumnNames = std::initializer_list; using MockAsts = std::initializer_list; +using MockWindowFrame = mock::MockWindowFrame; class MockDAGRequestContext; @@ -96,6 +96,13 @@ class DAGRequestBuilder DAGRequestBuilder & aggregation(ASTPtr agg_func, ASTPtr group_by_expr); DAGRequestBuilder & aggregation(MockAsts agg_funcs, MockAsts group_by_exprs); + // window + DAGRequestBuilder & window(ASTPtr window_func, MockOrderByItem order_by, MockPartitionByItem partition_by, MockWindowFrame frame); + DAGRequestBuilder & window(MockAsts window_funcs, MockOrderByItems order_by_list, MockPartitionByItems partition_by_list, MockWindowFrame frame); + DAGRequestBuilder & window(ASTPtr window_func, MockOrderByItems order_by_list, MockPartitionByItems partition_by_list, MockWindowFrame frame); + DAGRequestBuilder & sort(MockOrderByItem order_by, bool is_partial_sort); + DAGRequestBuilder & sort(MockOrderByItems order_by_list, bool is_partial_sort); + private: void initDAGRequest(tipb::DAGRequest & dag_request); DAGRequestBuilder & buildAggregation(ASTPtr agg_funcs, ASTPtr group_by_exprs); @@ -164,6 +171,8 @@ ASTPtr buildLiteral(const Field & field); ASTPtr buildFunction(MockAsts exprs, const String & name); ASTPtr buildOrderByItemList(MockOrderByItems order_by_items); +MockWindowFrame buildDefaultRowsFrame(); + #define col(name) buildColumn((name)) #define lit(field) buildLiteral((field)) #define eq(expr1, expr2) makeASTFunction("equals", (expr1), (expr2)) @@ -174,5 +183,9 @@ ASTPtr buildOrderByItemList(MockOrderByItems order_by_items); #define Or(expr1, expr2) makeASTFunction("or", (expr1), (expr2)) #define NOT(expr) makeASTFunction("not", (expr1), (expr2)) #define Max(expr) makeASTFunction("max", expr) +/// Window functions +#define RowNumber() makeASTFunction("RowNumber") +#define Rank() makeASTFunction("Rank") +#define DenseRank() makeASTFunction("DenseRank") } // namespace DB::tests \ No newline at end of file diff --git a/dbms/src/TestUtils/tests/gtest_mock_executors.cpp b/dbms/src/TestUtils/tests/gtest_mock_executors.cpp index 214148fe47f..8bed0f2fc6c 100644 --- a/dbms/src/TestUtils/tests/gtest_mock_executors.cpp +++ b/dbms/src/TestUtils/tests/gtest_mock_executors.cpp @@ -252,5 +252,17 @@ try } CATCH +TEST_F(MockDAGRequestTest, MockWindow) +try +{ + auto request = context.scan("test_db", "test_table").sort({"s1", false}, true).window(RowNumber(), {"s1", true}, {"s2", false}, buildDefaultRowsFrame()).build(context); + { + String expected = "window_2 | partition_by: {(<1, String>, desc: false)}}, order_by: {(<0, String>, desc: true)}, func_desc: {row_number()}, frame: {start<2, false, 0>, end<2, false, 0>}\n" + " sort_1 | isPartialSort: true, partition_by: {(<0, String>, desc: false)}\n" + " table_scan_0 | {<0, String>, <1, String>}\n"; + ASSERT_DAGREQUEST_EQAUL(expected, request); + } +} +CATCH } // namespace tests } // namespace DB \ No newline at end of file diff --git a/dbms/src/WindowFunctions/tests/gtest_window_functions.cpp b/dbms/src/WindowFunctions/tests/gtest_window_functions.cpp index e4205f6f938..3addf73a642 100644 --- a/dbms/src/WindowFunctions/tests/gtest_window_functions.cpp +++ b/dbms/src/WindowFunctions/tests/gtest_window_functions.cpp @@ -12,334 +12,192 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include -#include -#include -#include -#include -#include -#include +#include namespace DB::tests { -class WindowFunction : public DB::tests::FunctionTest +class WindowExecutorTestRunner : public DB::tests::ExecutorTest { -protected: - std::shared_ptr mock_interpreter; - - void SetUp() override - { - DB::tests::FunctionTest::SetUp(); - DB::registerWindowFunctions(); - } - - template - ColumnWithTypeAndName toNullableVec(String name, const std::vector::FieldType>> & v) - { - return createColumn>(v, name); - } - - template - ColumnWithTypeAndName toVec(String name, const std::vector::FieldType> & v) - { - return createColumn(v, name); - } - - template - static ColumnWithTypeAndName toConst(const T s) - { - return createConstColumn(1, s); - } - - static ColumnWithTypeAndName toDatetimeVec(String name, const std::vector & v, int fsp) - { - std::vector::FieldType> vec; - for (const auto & value_str : v) - { - Field value = parseMyDateTime(value_str, fsp); - vec.push_back(value.template safeGet()); - } - DataTypePtr data_type = std::make_shared(fsp); - return {makeColumn(data_type, vec), data_type, name, 0}; - } - - static ColumnWithTypeAndName toNullableDatetimeVec(String name, const std::vector & v, int fsp) - { - std::vector::FieldType>> vec; - for (const auto & value_str : v) - { - if (!value_str.empty()) - { - Field value = parseMyDateTime(value_str, fsp); - vec.push_back(value.template safeGet()); - } - else - { - vec.push_back({}); - } - } - DataTypePtr data_type = makeNullable(std::make_shared(fsp)); - return {makeColumn>(data_type, vec), data_type, name, 0}; - } - - void setMaxBlockSize(int size) - { - context.getSettingsRef().max_block_size.set(size); - } - - void mockInterpreter(std::vector source_columns, Context context) - { - std::vector mock_input_streams_vec = {}; - DAGQueryBlock mock_query_block(0, static_cast>(nullptr)); - std::vector mock_subqueries_for_sets = {}; - mock_interpreter = std::make_shared(context, - mock_input_streams_vec, - mock_query_block, - 1); - - mock_interpreter->analyzer = std::make_unique(std::move(source_columns), context); - } - - void mockExecuteTableScan(DAGPipeline & pipeline, ColumnsWithTypeAndName columns) - { - pipeline.streams.push_back(std::make_shared(columns, context.getSettingsRef().max_block_size)); - mock_interpreter->input_streams_vec.push_back(pipeline.streams); - } - - void mockExecuteWindowOrder(DAGPipeline & pipeline, std::string sort_json_str) +public: + void initializeContext() override { - tipb::Sort sort; - google::protobuf::util::JsonStringToMessage(sort_json_str, &sort); - mock_interpreter->handleWindowOrder(pipeline, sort); - mock_interpreter->input_streams_vec[0] = pipeline.streams; - NamesWithAliases final_project; - for (const auto & column : (*mock_interpreter->analyzer).source_columns) - { - final_project.push_back({column.name, ""}); - } - mockExecuteProject(pipeline, final_project); - } - - void mockExecuteWindow(DAGPipeline & pipeline, std::string window_json_str) - { - tipb::Window window; - google::protobuf::util::JsonStringToMessage(window_json_str, &window); - mock_interpreter->handleWindow(pipeline, window); - mock_interpreter->input_streams_vec[0] = pipeline.streams; - NamesWithAliases final_project; - for (const auto & column : (*mock_interpreter->analyzer).source_columns) - { - final_project.push_back({column.name, ""}); - } - mockExecuteProject(pipeline, final_project); - } - - void mockExecuteProject(DAGPipeline & pipeline, NamesWithAliases & final_project) - { - mock_interpreter->executeProject(pipeline, final_project); - } - - static Block mergeBlocks(Blocks blocks) - { - if (blocks.empty()) - { - return {}; - } - Block sample_block; - std::vector actual_cols; - - for (const auto & block : blocks) - { - if (!sample_block) - { - sample_block = block; - for (const auto & column : block.getColumnsWithTypeAndName()) - { - actual_cols.push_back(column.type->createColumn()); - } - } - - for (size_t i = 0; i < block.columns(); ++i) - { - for (size_t j = 0; j < block.rows(); ++j) - { - actual_cols[i]->insert((*(block.getColumnsWithTypeAndName())[i].column)[j]); - } - } - } - - ColumnsWithTypeAndName actual_columns; - - for (size_t i = 0; i < actual_cols.size(); ++i) - { - actual_columns.push_back({std::move(actual_cols[i]), sample_block.getColumnsWithTypeAndName()[i].type, sample_block.getColumnsWithTypeAndName()[i].name, sample_block.getColumnsWithTypeAndName()[i].column_id}); - } - return Block(actual_columns); - } - - void testOneWindowFunction(const std::vector & source_column_types, const ColumnsWithTypeAndName & source_columns, const ColumnsWithTypeAndName & expect_columns, const std::string window_json_str, const std::string sort_json_str) - { - mockInterpreter(source_column_types, context); - DAGPipeline pipeline; - ExpressionActionsChain chain; - Block except_block(expect_columns); - - mockExecuteTableScan(pipeline, source_columns); - - mockExecuteWindowOrder(pipeline, sort_json_str); - - mockExecuteWindow(pipeline, window_json_str); - - auto stream = pipeline.firstStream(); - - Blocks actual_blocks; - while (Block block = stream->read()) - { - actual_blocks.push_back(block); - } - - Block actual_block = mergeBlocks(actual_blocks); - - if (actual_block) - { - // Check that input columns is properly split to many blocks - ASSERT_EQ(actual_blocks.size(), (actual_block.rows() - 1) / context.getSettingsRef().max_block_size + 1); - } - ASSERT_BLOCK_EQ(except_block, actual_block); + ExecutorTest::initializeContext(); + context.addMockTable( + {"test_db", "test_table"}, + {{"partition", TiDB::TP::TypeLongLong}, {"order", TiDB::TP::TypeLongLong}}, + {toVec("partition", {1, 1, 1, 1, 2, 2, 2, 2}), + toVec("order", {1, 1, 2, 2, 1, 1, 2, 2})}); + context.addMockTable( + {"test_db", "test_table_string"}, + {{"partition", TiDB::TP::TypeString}, {"order", TiDB::TP::TypeString}}, + {toVec("partition", {"banana", "banana", "banana", "banana", "apple", "apple", "apple", "apple"}), + toVec("order", {"apple", "apple", "banana", "banana", "apple", "apple", "banana", "banana"})}); + + context.addMockTable( + {"test_db", "test_table_more_cols"}, + {{"partition1", TiDB::TP::TypeLongLong}, {"partition2", TiDB::TP::TypeLongLong}, {"order1", TiDB::TP::TypeLongLong}, {"order2", TiDB::TP::TypeLongLong}}, + {toVec("partition1", {1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2}), + toVec("partition2", {1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2}), + toVec("order1", {2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1}), + toVec("order2", {2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 1})}); + + context.addMockTable( + {"test_db", "test_table_float64"}, + {{"partition", TiDB::TP::TypeDouble}, {"order", TiDB::TP::TypeDouble}}, + {toVec("partition", {1.00, 1.00, 1.00, 1.00, 2.00, 2.00, 2.00, 2.00}), + toVec("order", {1.00, 1.00, 2.00, 2.00, 1.00, 1.00, 2.00, 2.00})}); + + context.addMockTable( + {"test_db", "test_table_datetime"}, + {{"partition", TiDB::TP::TypeDatetime}, {"order", TiDB::TP::TypeDatetime}}); + + context.addMockTable( + {"test_db", "test_table_for_rank"}, + {{"partition", TiDB::TP::TypeLongLong}, {"order", TiDB::TP::TypeLongLong}}, + {toVec("partition", {1, 1, 1, 1, 2, 2, 2, 2}), + toVec("order", {1, 1, 2, 2, 1, 1, 2, 2})}); } }; -TEST_F(WindowFunction, testWindowFunctionByPartitionAndOrder) +TEST_F(WindowExecutorTestRunner, testWindowFunctionByPartitionAndOrder) try { - setMaxBlockSize(3); - - std::string window_json; - std::string sort_json; - /***** row_number with different types of input *****/ // int - sql : select *, row_number() over w1 from test1 window w1 as (partition by partition_int order by order_int) - window_json = R"({"funcDesc":[{"tp":"RowNumber","sig":"Unspecified","fieldType":{"tp":8,"flag":128,"flen":21,"decimal":-1,"collate":63,"charset":"binary"},"hasDistinct":false}],"partitionBy":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"orderBy":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"frame":{"type":"Rows","start":{"type":"CurrentRow","unbounded":false,"offset":"0"},"end":{"type":"CurrentRow","unbounded":false,"offset":"0"}},"child":{"tp":"TypeSort","executorId":"Sort_12","sort":{"byItems":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"isPartialSort":true,"child":{"tp":"TypeExchangeReceiver","exchangeReceiver":{"encodedTaskMeta":["CIGAkMCV6NP+BRABIg4xMjcuMC4wLjE6MzkzMA=="],"fieldTypes":[{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"}]},"executorId":"ExchangeReceiver_11"}}}})"; - sort_json = R"({"byItems":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"isPartialSort":true,"child":{"tp":"TypeExchangeReceiver","exchangeReceiver":{"encodedTaskMeta":["CIGAkMCV6NP+BRABIg4xMjcuMC4wLjE6MzkzMA=="],"fieldTypes":[{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"}]},"executorId":"ExchangeReceiver_11"}})"; - testOneWindowFunction( - {NameAndTypePair("partition", std::make_shared()), NameAndTypePair("order", std::make_shared())}, - {toVec("partition", {1, 1, 1, 1, 2, 2, 2, 2}), toVec("order", {1, 1, 2, 2, 1, 1, 2, 2})}, - {toVec("partition", {1, 1, 1, 1, 2, 2, 2, 2}), toVec("order", {1, 1, 2, 2, 1, 1, 2, 2}), toNullableVec("row_number", {1, 2, 3, 4, 1, 2, 3, 4})}, - window_json, - sort_json); + auto request = context + .scan("test_db", "test_table") + .sort({{"partition", false}, {"order", false}, {"partition", false}, {"order", false}}, true) + .window(RowNumber(), {"order", false}, {"partition", false}, buildDefaultRowsFrame()) + .build(context); + executeStreams( + request, + {toNullableVec("partition", {1, 1, 1, 1, 2, 2, 2, 2}), + toNullableVec("order", {1, 1, 2, 2, 1, 1, 2, 2}), + toNullableVec("row_number", {1, 2, 3, 4, 1, 2, 3, 4})}); // null input - testOneWindowFunction( - {NameAndTypePair("partition", makeNullable(std::make_shared())), NameAndTypePair("order", makeNullable(std::make_shared()))}, + executeStreamsWithSingleSource( + request, {toNullableVec("partition", {}), toNullableVec("order", {})}, - {}, - window_json, - sort_json); + {}); // nullable - testOneWindowFunction( - {NameAndTypePair("partition", makeNullable(std::make_shared())), NameAndTypePair("order", makeNullable(std::make_shared()))}, - {toNullableVec("partition", {{}, 1, 1, 1, 1, 2, 2, 2, 2}), toNullableVec("order", {{}, 1, 1, 2, 2, 1, 1, 2, 2})}, - {toNullableVec("partition", {{}, 1, 1, 1, 1, 2, 2, 2, 2}), toNullableVec("order", {{}, 1, 1, 2, 2, 1, 1, 2, 2}), toNullableVec("row_number", {1, 1, 2, 3, 4, 1, 2, 3, 4})}, - window_json, - sort_json); + executeStreamsWithSingleSource( + request, + {toNullableVec("partition", {{}, 1, 1, 1, 1, 2, 2, 2, 2}), {toNullableVec("order", {{}, 1, 1, 2, 2, 1, 1, 2, 2})}}, + {toNullableVec("partition", {{}, 1, 1, 1, 1, 2, 2, 2, 2}), toNullableVec("order", {{}, 1, 1, 2, 2, 1, 1, 2, 2}), toNullableVec("row_number", {1, 1, 2, 3, 4, 1, 2, 3, 4})}); // string - sql : select *, row_number() over w1 from test2 window w1 as (partition by partition_string order by order_string) - window_json = R"({"funcDesc":[{"tp":"RowNumber","sig":"Unspecified","fieldType":{"tp":8,"flag":128,"flen":21,"decimal":-1,"collate":63,"charset":"binary"},"hasDistinct":false}],"partitionBy":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":254,"flag":0,"flen":32,"decimal":0,"collate":46,"charset":"utf8mb4"},"hasDistinct":false},"desc":false}],"orderBy":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":254,"flag":0,"flen":32,"decimal":0,"collate":46,"charset":"utf8mb4"},"hasDistinct":false},"desc":false}],"frame":{"type":"Rows","start":{"type":"CurrentRow","unbounded":false,"offset":"0"},"end":{"type":"CurrentRow","unbounded":false,"offset":"0"}},"child":{"tp":"TypeSort","executorId":"Sort_12","sort":{"byItems":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":254,"flag":0,"flen":32,"decimal":0,"collate":46,"charset":"utf8mb4"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":254,"flag":0,"flen":32,"decimal":0,"collate":46,"charset":"utf8mb4"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":254,"flag":0,"flen":32,"decimal":0,"collate":46,"charset":"utf8mb4"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":254,"flag":0,"flen":32,"decimal":0,"collate":46,"charset":"utf8mb4"},"hasDistinct":false},"desc":false}],"isPartialSort":true,"child":{"tp":"TypeExchangeReceiver","exchangeReceiver":{"encodedTaskMeta":["CIGA8Nz57tP+BRABIg4xMjcuMC4wLjE6MzkzMA=="],"fieldTypes":[{"tp":254,"flag":0,"flen":32,"decimal":0,"collate":46,"charset":"utf8mb4"},{"tp":254,"flag":0,"flen":32,"decimal":0,"collate":46,"charset":"utf8mb4"}]},"executorId":"ExchangeReceiver_11"}}}})"; - sort_json = R"({"byItems":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":254,"flag":0,"flen":32,"decimal":0,"collate":46,"charset":"utf8mb4"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":254,"flag":0,"flen":32,"decimal":0,"collate":46,"charset":"utf8mb4"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":254,"flag":0,"flen":32,"decimal":0,"collate":46,"charset":"utf8mb4"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":254,"flag":0,"flen":32,"decimal":0,"collate":46,"charset":"utf8mb4"},"hasDistinct":false},"desc":false}],"isPartialSort":true,"child":{"tp":"TypeExchangeReceiver","exchangeReceiver":{"encodedTaskMeta":["CIGA8Nz57tP+BRABIg4xMjcuMC4wLjE6MzkzMA=="],"fieldTypes":[{"tp":254,"flag":0,"flen":32,"decimal":0,"collate":46,"charset":"utf8mb4"},{"tp":254,"flag":0,"flen":32,"decimal":0,"collate":46,"charset":"utf8mb4"}]},"executorId":"ExchangeReceiver_11"}})"; - testOneWindowFunction( - {NameAndTypePair("partition", std::make_shared()), NameAndTypePair("order", std::make_shared())}, - {toVec("partition", {"banana", "banana", "banana", "banana", "apple", "apple", "apple", "apple"}), toVec("order", {"apple", "apple", "banana", "banana", "apple", "apple", "banana", "banana"})}, - {toVec("partition", {"apple", "apple", "apple", "apple", "banana", "banana", "banana", "banana"}), toVec("order", {"apple", "apple", "banana", "banana", "apple", "apple", "banana", "banana"}), toNullableVec("row_number", {1, 2, 3, 4, 1, 2, 3, 4})}, - window_json, - sort_json); + request = context + .scan("test_db", "test_table_string") + .sort({{"partition", false}, {"order", false}, {"partition", false}, {"order", false}}, true) + .window(RowNumber(), {"order", false}, {"partition", false}, buildDefaultRowsFrame()) + .build(context); + + executeStreams( + request, + {toNullableVec("partition", {"apple", "apple", "apple", "apple", "banana", "banana", "banana", "banana"}), + toNullableVec("order", {"apple", "apple", "banana", "banana", "apple", "apple", "banana", "banana"}), + toNullableVec("row_number", {1, 2, 3, 4, 1, 2, 3, 4})}); // nullable - testOneWindowFunction( - {NameAndTypePair("partition", makeNullable(std::make_shared())), NameAndTypePair("order", makeNullable(std::make_shared()))}, - {toNullableVec("partition", {"banana", "banana", "banana", "banana", {}, "apple", "apple", "apple", "apple"}), toNullableVec("order", {"apple", "apple", "banana", "banana", {}, "apple", "apple", "banana", "banana"})}, - {toNullableVec("partition", {{}, "apple", "apple", "apple", "apple", "banana", "banana", "banana", "banana"}), toNullableVec("order", {{}, "apple", "apple", "banana", "banana", "apple", "apple", "banana", "banana"}), toNullableVec("row_number", {1, 1, 2, 3, 4, 1, 2, 3, 4})}, - window_json, - sort_json); - - // decimal - sql : select *, row_number() over w1 from test3 window w1 as (partition by partition_float order by order_decimal) - window_json = R"({"funcDesc":[{"tp":"RowNumber","sig":"Unspecified","fieldType":{"tp":8,"flag":128,"flen":21,"decimal":-1,"collate":63,"charset":"binary"},"hasDistinct":false}],"partitionBy":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":246,"flag":0,"flen":6,"decimal":2,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"orderBy":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":246,"flag":0,"flen":6,"decimal":2,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"frame":{"type":"Rows","start":{"type":"CurrentRow","unbounded":false,"offset":"0"},"end":{"type":"CurrentRow","unbounded":false,"offset":"0"}},"child":{"tp":"TypeSort","executorId":"Sort_12","sort":{"byItems":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":246,"flag":0,"flen":6,"decimal":2,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":246,"flag":0,"flen":6,"decimal":2,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":246,"flag":0,"flen":6,"decimal":2,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":246,"flag":0,"flen":6,"decimal":2,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"isPartialSort":true,"child":{"tp":"TypeExchangeReceiver","exchangeReceiver":{"encodedTaskMeta":["CIGAoN3M99P+BRABIg4xMjcuMC4wLjE6MzkzMA=="],"fieldTypes":[{"tp":246,"flag":0,"flen":6,"decimal":2,"collate":63,"charset":"binary"},{"tp":246,"flag":0,"flen":6,"decimal":2,"collate":63,"charset":"binary"}]},"executorId":"ExchangeReceiver_11"}}}})"; - sort_json = R"({"byItems":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":246,"flag":0,"flen":6,"decimal":2,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":246,"flag":0,"flen":6,"decimal":2,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":246,"flag":0,"flen":6,"decimal":2,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":246,"flag":0,"flen":6,"decimal":2,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"isPartialSort":true,"child":{"tp":"TypeExchangeReceiver","exchangeReceiver":{"encodedTaskMeta":["CIGAoN3M99P+BRABIg4xMjcuMC4wLjE6MzkzMA=="],"fieldTypes":[{"tp":246,"flag":0,"flen":6,"decimal":2,"collate":63,"charset":"binary"},{"tp":246,"flag":0,"flen":6,"decimal":2,"collate":63,"charset":"binary"}]},"executorId":"ExchangeReceiver_11"}})"; - testOneWindowFunction( - {NameAndTypePair("partition", std::make_shared()), NameAndTypePair("order", std::make_shared())}, - {toVec("partition", {1.00, 1.00, 1.00, 1.00, 2.00, 2.00, 2.00, 2.00}), toVec("order", {1.00, 1.00, 2.00, 2.00, 1.00, 1.00, 2.00, 2.00})}, - {toVec("partition", {1.00, 1.00, 1.00, 1.00, 2.00, 2.00, 2.00, 2.00}), toVec("order", {1.00, 1.00, 2.00, 2.00, 1.00, 1.00, 2.00, 2.00}), toNullableVec("row_number", {1, 2, 3, 4, 1, 2, 3, 4})}, - window_json, - sort_json); + executeStreamsWithSingleSource( + request, + {toNullableVec("partition", {"banana", "banana", "banana", "banana", {}, "apple", "apple", "apple", "apple"}), + toNullableVec("order", {"apple", "apple", "banana", "banana", {}, "apple", "apple", "banana", "banana"})}, + {toNullableVec("partition", {{}, "apple", "apple", "apple", "apple", "banana", "banana", "banana", "banana"}), + toNullableVec("order", {{}, "apple", "apple", "banana", "banana", "apple", "apple", "banana", "banana"}), + toNullableVec("row_number", {1, 1, 2, 3, 4, 1, 2, 3, 4})}); + + // float64 - sql : select *, row_number() over w1 from test3 window w1 as (partition by partition_float order by order_float64) + request = context + .scan("test_db", "test_table_float64") + .sort({{"partition", false}, {"order", false}, {"partition", false}, {"order", false}}, true) + .window(RowNumber(), {"order", false}, {"partition", false}, buildDefaultRowsFrame()) + .build(context); + + executeStreams( + request, + {toNullableVec("partition", {1.00, 1.00, 1.00, 1.00, 2.00, 2.00, 2.00, 2.00}), + toNullableVec("order", {1.00, 1.00, 2.00, 2.00, 1.00, 1.00, 2.00, 2.00}), + toNullableVec("row_number", {1, 2, 3, 4, 1, 2, 3, 4})}); // nullable - testOneWindowFunction( - {NameAndTypePair("partition", makeNullable(std::make_shared())), NameAndTypePair("order", makeNullable(std::make_shared()))}, - {toNullableVec("partition", {{}, 1.00, 1.00, 1.00, 1.00, 2.00, 2.00, 2.00, 2.00}), toNullableVec("order", {{}, 1.00, 1.00, 2.00, 2.00, 1.00, 1.00, 2.00, 2.00})}, - {toNullableVec("partition", {{}, 1.00, 1.00, 1.00, 1.00, 2.00, 2.00, 2.00, 2.00}), toNullableVec("order", {{}, 1.00, 1.00, 2.00, 2.00, 1.00, 1.00, 2.00, 2.00}), toNullableVec("row_number", {1, 1, 2, 3, 4, 1, 2, 3, 4})}, - window_json, - sort_json); + executeStreamsWithSingleSource( + request, + {toNullableVec("partition", {{}, 1.00, 1.00, 1.00, 1.00, 2.00, 2.00, 2.00, 2.00}), + toNullableVec("order", {{}, 1.00, 1.00, 2.00, 2.00, 1.00, 1.00, 2.00, 2.00})}, + {toNullableVec("partition", {{}, 1.00, 1.00, 1.00, 1.00, 2.00, 2.00, 2.00, 2.00}), + toNullableVec("order", {{}, 1.00, 1.00, 2.00, 2.00, 1.00, 1.00, 2.00, 2.00}), + toNullableVec("row_number", {1, 1, 2, 3, 4, 1, 2, 3, 4})}); // datetime - select *, row_number() over w1 from test4 window w1 as (partition by partition_datetime order by order_datetime); - window_json = R"({"funcDesc":[{"tp":"RowNumber","sig":"Unspecified","fieldType":{"tp":8,"flag":128,"flen":21,"decimal":-1,"collate":63,"charset":"binary"},"hasDistinct":false}],"partitionBy":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":12,"flag":128,"flen":26,"decimal":6,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"orderBy":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":12,"flag":128,"flen":26,"decimal":6,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"frame":{"type":"Rows","start":{"type":"CurrentRow","unbounded":false,"offset":"0"},"end":{"type":"CurrentRow","unbounded":false,"offset":"0"}},"child":{"tp":"TypeSort","executorId":"Sort_12","sort":{"byItems":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":12,"flag":128,"flen":26,"decimal":6,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":12,"flag":128,"flen":26,"decimal":6,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":12,"flag":128,"flen":26,"decimal":6,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":12,"flag":128,"flen":26,"decimal":6,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"isPartialSort":true,"child":{"tp":"TypeExchangeReceiver","exchangeReceiver":{"encodedTaskMeta":["CIGAsNmBhdT+BRABIg4xMjcuMC4wLjE6MzkzMA=="],"fieldTypes":[{"tp":12,"flag":128,"flen":26,"decimal":6,"collate":63,"charset":"binary"},{"tp":12,"flag":128,"flen":26,"decimal":6,"collate":63,"charset":"binary"}]},"executorId":"ExchangeReceiver_11"}}}})"; - sort_json = R"({"byItems":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":12,"flag":128,"flen":26,"decimal":6,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":12,"flag":128,"flen":26,"decimal":6,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":12,"flag":128,"flen":26,"decimal":6,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":12,"flag":128,"flen":26,"decimal":6,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"isPartialSort":true,"child":{"tp":"TypeExchangeReceiver","exchangeReceiver":{"encodedTaskMeta":["CIGAsNmBhdT+BRABIg4xMjcuMC4wLjE6MzkzMA=="],"fieldTypes":[{"tp":12,"flag":128,"flen":26,"decimal":6,"collate":63,"charset":"binary"},{"tp":12,"flag":128,"flen":26,"decimal":6,"collate":63,"charset":"binary"}]},"executorId":"ExchangeReceiver_11"}})"; - testOneWindowFunction( - {NameAndTypePair("partition", std::make_shared()), NameAndTypePair("order", std::make_shared())}, - {toDatetimeVec("partition", {"20220101010102", "20220101010102", "20220101010102", "20220101010102", "20220101010101", "20220101010101", "20220101010101", "20220101010101"}, 0), + request = context + .scan("test_db", "test_table_datetime") + .sort({{"partition", false}, {"order", false}, {"partition", false}, {"order", false}}, true) + .window(RowNumber(), {"order", false}, {"partition", false}, buildDefaultRowsFrame()) + .build(context); + executeStreamsWithSingleSource( + request, + {toNullableDatetimeVec("partition", {"20220101010102", "20220101010102", "20220101010102", "20220101010102", "20220101010101", "20220101010101", "20220101010101", "20220101010101"}, 0), toDatetimeVec("order", {"20220101010101", "20220101010101", "20220101010102", "20220101010102", "20220101010101", "20220101010101", "20220101010102", "20220101010102"}, 0)}, - {toDatetimeVec("partition", {"20220101010101", "20220101010101", "20220101010101", "20220101010101", "20220101010102", "20220101010102", "20220101010102", "20220101010102"}, 0), - toDatetimeVec("order", {"20220101010101", "20220101010101", "20220101010102", "20220101010102", "20220101010101", "20220101010101", "20220101010102", "20220101010102"}, 0), - toNullableVec("row_number", {1, 2, 3, 4, 1, 2, 3, 4})}, - window_json, - sort_json); + {toNullableDatetimeVec("partition", {"20220101010101", "20220101010101", "20220101010101", "20220101010101", "20220101010102", "20220101010102", "20220101010102", "20220101010102"}, 0), + toNullableDatetimeVec("order", {"20220101010101", "20220101010101", "20220101010102", "20220101010102", "20220101010101", "20220101010101", "20220101010102", "20220101010102"}, 0), + toNullableVec("row_number", {1, 2, 3, 4, 1, 2, 3, 4})}); // nullable - testOneWindowFunction( - {NameAndTypePair("partition", makeNullable(std::make_shared())), NameAndTypePair("order", makeNullable(std::make_shared()))}, + executeStreamsWithSingleSource( + request, {toNullableDatetimeVec("partition", {"20220101010102", {}, "20220101010102", "20220101010102", "20220101010102", "20220101010101", "20220101010101", "20220101010101", "20220101010101"}, 0), toNullableDatetimeVec("order", {"20220101010101", {}, "20220101010101", "20220101010102", "20220101010102", "20220101010101", "20220101010101", "20220101010102", "20220101010102"}, 0)}, {toNullableDatetimeVec("partition", {{}, "20220101010101", "20220101010101", "20220101010101", "20220101010101", "20220101010102", "20220101010102", "20220101010102", "20220101010102"}, 0), toNullableDatetimeVec("order", {{}, "20220101010101", "20220101010101", "20220101010102", "20220101010102", "20220101010101", "20220101010101", "20220101010102", "20220101010102"}, 0), - toNullableVec("row_number", {1, 1, 2, 3, 4, 1, 2, 3, 4})}, - window_json, - sort_json); + toNullableVec("row_number", {1, 1, 2, 3, 4, 1, 2, 3, 4})}); // 2 partiton key and 2 order key // sql : select *, row_number() over w1 from test6 window w1 as (partition by partition_int1, partition_int2 order by order_int1,order_int2) - window_json = R"({"funcDesc":[{"tp":"RowNumber","sig":"Unspecified","fieldType":{"tp":8,"flag":128,"flen":21,"decimal":-1,"collate":63,"charset":"binary"},"hasDistinct":false}],"partitionBy":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"orderBy":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAI=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAM=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"frame":{"type":"Rows","start":{"type":"CurrentRow","unbounded":false,"offset":"0"},"end":{"type":"CurrentRow","unbounded":false,"offset":"0"}},"child":{"tp":"TypeSort","executorId":"Sort_12","sort":{"byItems":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAI=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAM=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAI=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAM=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"isPartialSort":true,"child":{"tp":"TypeExchangeReceiver","exchangeReceiver":{"encodedTaskMeta":["CIKA0Img1If/BRABIg4xMjcuMC4wLjE6MzkzMA=="],"fieldTypes":[{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"}]},"executorId":"ExchangeReceiver_11"}}}})"; - sort_json = R"({"byItems":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAI=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAM=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAI=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAM=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"isPartialSort":true,"child":{"tp":"TypeExchangeReceiver","exchangeReceiver":{"encodedTaskMeta":["CIKA0Img1If/BRABIg4xMjcuMC4wLjE6MzkzMA=="],"fieldTypes":[{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"}]},"executorId":"ExchangeReceiver_11"}})"; - testOneWindowFunction( - {NameAndTypePair("partition1", std::make_shared()), NameAndTypePair("partition2", std::make_shared()), NameAndTypePair("order1", std::make_shared()), NameAndTypePair("order2", std::make_shared())}, - {toVec("partition1", {1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2}), toVec("partition2", {1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2}), toVec("order1", {2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1}), toVec("order2", {2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 1})}, - {toVec("partition1", {1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2}), toVec("partition2", {1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2}), toVec("order1", {1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2}), toVec("order2", {1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 2}), toNullableVec("row_number", {1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3})}, - window_json, - sort_json); + request = context + .scan("test_db", "test_table_more_cols") + .sort({{"partition1", false}, {"partition2", false}, {"order1", false}, {"order2", false}}, true) + .window(RowNumber(), {{"order1", false}, {"order2", false}}, {{"partition1", false}, {"partition2", false}}, buildDefaultRowsFrame()) + .build(context); + + executeStreams( + request, + {toNullableVec("partition1", {1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2}), + toNullableVec("partition2", {1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2}), + toNullableVec("order1", {1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2}), + toNullableVec("order2", {1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 2}), + toNullableVec("row_number", {1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3})}); /***** rank, dense_rank *****/ - window_json = R"({"funcDesc":[{"tp":"Rank","sig":"Unspecified","fieldType":{"tp":8,"flag":128,"flen":21,"decimal":-1,"collate":63,"charset":"binary"},"hasDistinct":false},{"tp":"DenseRank","sig":"Unspecified","fieldType":{"tp":8,"flag":128,"flen":21,"decimal":-1,"collate":63,"charset":"binary"},"hasDistinct":false}],"partitionBy":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"orderBy":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"child":{"tp":"TypeSort","executorId":"Sort_12","sort":{"byItems":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"isPartialSort":true,"child":{"tp":"TypeExchangeReceiver","exchangeReceiver":{"encodedTaskMeta":["CIGAsOnl3NP+BRABIg4xMjcuMC4wLjE6MzkzMA=="],"fieldTypes":[{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"}]},"executorId":"ExchangeReceiver_11"}}}})"; - sort_json = R"({"byItems":[{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAA=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false},{"expr":{"tp":"ColumnRef","val":"gAAAAAAAAAE=","sig":"Unspecified","fieldType":{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},"hasDistinct":false},"desc":false}],"isPartialSort":true,"child":{"tp":"TypeExchangeReceiver","exchangeReceiver":{"encodedTaskMeta":["CIGAsOnl3NP+BRABIg4xMjcuMC4wLjE6MzkzMA=="],"fieldTypes":[{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"},{"tp":3,"flag":0,"flen":11,"decimal":0,"collate":63,"charset":"binary"}]},"executorId":"ExchangeReceiver_11"}})"; - testOneWindowFunction( - {NameAndTypePair("partition", std::make_shared()), NameAndTypePair("order", std::make_shared())}, - {toVec("partition", {1, 1, 1, 1, 2, 2, 2, 2}), toVec("order", {1, 1, 2, 2, 1, 1, 2, 2})}, - {toVec("partition", {1, 1, 1, 1, 2, 2, 2, 2}), toVec("order", {1, 1, 2, 2, 1, 1, 2, 2}), toNullableVec("rank", {1, 1, 3, 3, 1, 1, 3, 3}), toNullableVec("dense_rank", {1, 1, 2, 2, 1, 1, 2, 2})}, - window_json, - sort_json); + request = context.scan("test_db", "test_table_for_rank").sort({{"partition", false}, {"order", false}}, true).window({Rank(), DenseRank()}, {{"order", false}}, {{"partition", false}}, MockWindowFrame{}).build(context); + executeStreams( + request, + {toNullableVec("partition", {1, 1, 1, 1, 2, 2, 2, 2}), + toNullableVec("order", {1, 1, 2, 2, 1, 1, 2, 2}), + toNullableVec("rank", {1, 1, 3, 3, 1, 1, 3, 3}), + toNullableVec("dense_rank", {1, 1, 2, 2, 1, 1, 2, 2})}); // nullable - testOneWindowFunction( - {NameAndTypePair("partition", makeNullable(std::make_shared())), NameAndTypePair("order", makeNullable(std::make_shared()))}, - {toNullableVec("partition", {{}, 1, 1, 1, 1, 2, 2, 2, 2}), toNullableVec("order", {{}, 1, 1, 2, 2, 1, 1, 2, 2})}, - {toNullableVec("partition", {{}, 1, 1, 1, 1, 2, 2, 2, 2}), toNullableVec("order", {{}, 1, 1, 2, 2, 1, 1, 2, 2}), toNullableVec("rank", {1, 1, 1, 3, 3, 1, 1, 3, 3}), toNullableVec("dense_rank", {1, 1, 1, 2, 2, 1, 1, 2, 2})}, - window_json, - sort_json); - - testOneWindowFunction( - {NameAndTypePair("partition", makeNullable(std::make_shared())), NameAndTypePair("order", makeNullable(std::make_shared()))}, - {toNullableVec("partition", {{}, {}, 1, 1, 1, 1, 2, 2, 2, 2}), toNullableVec("order", {{}, 1, 1, 1, 2, 2, 1, 1, 2, 2})}, - {toNullableVec("partition", {{}, {}, 1, 1, 1, 1, 2, 2, 2, 2}), toNullableVec("order", {{}, 1, 1, 1, 2, 2, 1, 1, 2, 2}), toNullableVec("rank", {1, 2, 1, 1, 3, 3, 1, 1, 3, 3}), toNullableVec("dense_rank", {1, 2, 1, 1, 2, 2, 1, 1, 2, 2})}, - window_json, - sort_json); + executeStreamsWithSingleSource( + request, + {toNullableVec("partition", {{}, 1, 1, 1, 1, 2, 2, 2, 2}), + toNullableVec("order", {{}, 1, 1, 2, 2, 1, 1, 2, 2})}, + {toNullableVec("partition", {{}, 1, 1, 1, 1, 2, 2, 2, 2}), + toNullableVec("order", {{}, 1, 1, 2, 2, 1, 1, 2, 2}), + toNullableVec("rank", {1, 1, 1, 3, 3, 1, 1, 3, 3}), + toNullableVec("dense_rank", {1, 1, 1, 2, 2, 1, 1, 2, 2})}); + + executeStreamsWithSingleSource( + request, + {toNullableVec("partition", {{}, {}, 1, 1, 1, 1, 2, 2, 2, 2}), + toNullableVec("order", {{}, 1, 1, 1, 2, 2, 1, 1, 2, 2})}, + {toNullableVec("partition", {{}, {}, 1, 1, 1, 1, 2, 2, 2, 2}), + toNullableVec("order", {{}, 1, 1, 1, 2, 2, 1, 1, 2, 2}), + toNullableVec("rank", {1, 2, 1, 1, 3, 3, 1, 1, 3, 3}), + toNullableVec("dense_rank", {1, 2, 1, 1, 2, 2, 1, 1, 2, 2})}); } CATCH + } // namespace DB::tests From 123440cf8b9244f42c721ed67ebc95b474551470 Mon Sep 17 00:00:00 2001 From: hongyunyan <649330952@qq.com> Date: Tue, 14 Jun 2022 12:06:33 +0800 Subject: [PATCH 102/127] Remove the log with high frequency and not useful enough (#5141) ref pingcap/tiflash#5140 --- dbms/src/Storages/tests/gtest_filter_parser.cpp | 2 +- dbms/src/TiDB/Schema/SchemaBuilder-internal.h | 2 +- dbms/src/TiDB/Schema/SchemaBuilder.cpp | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/dbms/src/Storages/tests/gtest_filter_parser.cpp b/dbms/src/Storages/tests/gtest_filter_parser.cpp index 8820c05d2da..3a554fcf4b6 100644 --- a/dbms/src/Storages/tests/gtest_filter_parser.cpp +++ b/dbms/src/Storages/tests/gtest_filter_parser.cpp @@ -98,7 +98,7 @@ DM::RSOperatorPtr FilterParserTest::generateRsOperator(const String table_info_j DM::ColumnDefines columns_to_read; { NamesAndTypes source_columns; - std::tie(source_columns, std::ignore) = parseColumnsFromTableInfo(table_info, log->getLog()); + std::tie(source_columns, std::ignore) = parseColumnsFromTableInfo(table_info); dag_query = std::make_unique( conditions, DAGPreparedSets(), diff --git a/dbms/src/TiDB/Schema/SchemaBuilder-internal.h b/dbms/src/TiDB/Schema/SchemaBuilder-internal.h index a331205ce8c..94edcbea204 100644 --- a/dbms/src/TiDB/Schema/SchemaBuilder-internal.h +++ b/dbms/src/TiDB/Schema/SchemaBuilder-internal.h @@ -35,7 +35,7 @@ struct TableInfo; } namespace DB { -std::tuple parseColumnsFromTableInfo(const TiDB::TableInfo & table_info, Poco::Logger * log); +std::tuple parseColumnsFromTableInfo(const TiDB::TableInfo & table_info); constexpr char tmpNamePrefix[] = "_tiflash_tmp_"; diff --git a/dbms/src/TiDB/Schema/SchemaBuilder.cpp b/dbms/src/TiDB/Schema/SchemaBuilder.cpp index 99e540e6c95..f532ac231e2 100644 --- a/dbms/src/TiDB/Schema/SchemaBuilder.cpp +++ b/dbms/src/TiDB/Schema/SchemaBuilder.cpp @@ -963,13 +963,12 @@ void SchemaBuilder::applyDropSchema(const String & db_name) } std::tuple -parseColumnsFromTableInfo(const TiDB::TableInfo & table_info, Poco::Logger * log) +parseColumnsFromTableInfo(const TiDB::TableInfo & table_info) { NamesAndTypes columns; std::vector primary_keys; for (const auto & column : table_info.columns) { - LOG_FMT_DEBUG(log, "Analyzing column: {}, type: {}", column.name, static_cast(column.tp)); DataTypePtr type = getDataTypeByColumnInfo(column); columns.emplace_back(column.name, type); if (column.hasPriKeyFlag()) @@ -999,7 +998,7 @@ String createTableStmt( Poco::Logger * log) { LOG_FMT_DEBUG(log, "Analyzing table info : {}", table_info.serialize()); - auto [columns, pks] = parseColumnsFromTableInfo(table_info, log); + auto [columns, pks] = parseColumnsFromTableInfo(table_info); String stmt; WriteBufferFromString stmt_buf(stmt); From 4b3a2ce5a691ea4a3aa400d9ef157484b638982c Mon Sep 17 00:00:00 2001 From: shuke <37901441+shuke987@users.noreply.github.com> Date: Tue, 14 Jun 2022 12:48:33 +0800 Subject: [PATCH 103/127] modify cached_gc_safe_point to atomic to prevent more request to PD (#5079) ref pingcap/tiflash#4928 --- dbms/src/Storages/Transaction/PDTiKVClient.cpp | 4 ++-- dbms/src/Storages/Transaction/PDTiKVClient.h | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/dbms/src/Storages/Transaction/PDTiKVClient.cpp b/dbms/src/Storages/Transaction/PDTiKVClient.cpp index 5a4b751fd9c..a06f1a3ae64 100644 --- a/dbms/src/Storages/Transaction/PDTiKVClient.cpp +++ b/dbms/src/Storages/Transaction/PDTiKVClient.cpp @@ -22,7 +22,7 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } -Timestamp PDClientHelper::cached_gc_safe_point = 0; -std::chrono::time_point PDClientHelper::safe_point_last_update_time; +std::atomic PDClientHelper::cached_gc_safe_point = 0; +std::atomic> PDClientHelper::safe_point_last_update_time; } // namespace DB diff --git a/dbms/src/Storages/Transaction/PDTiKVClient.h b/dbms/src/Storages/Transaction/PDTiKVClient.h index 4986c28f4ac..e5801cc7fae 100644 --- a/dbms/src/Storages/Transaction/PDTiKVClient.h +++ b/dbms/src/Storages/Transaction/PDTiKVClient.h @@ -29,6 +29,8 @@ #include #include +#include + // We define a shared ptr here, because TMTContext / SchemaSyncer / IndexReader all need to // `share` the resource of cluster. using KVClusterPtr = std::shared_ptr; @@ -49,7 +51,7 @@ struct PDClientHelper { // In case we cost too much to update safe point from PD. std::chrono::time_point now = std::chrono::system_clock::now(); - const auto duration = std::chrono::duration_cast(now - safe_point_last_update_time); + const auto duration = std::chrono::duration_cast(now - safe_point_last_update_time.load()); const auto min_interval = std::max(Int64(1), safe_point_update_interval_seconds); // at least one second if (duration.count() < min_interval) return cached_gc_safe_point; @@ -73,8 +75,8 @@ struct PDClientHelper } private: - static Timestamp cached_gc_safe_point; - static std::chrono::time_point safe_point_last_update_time; + static std::atomic cached_gc_safe_point; + static std::atomic> safe_point_last_update_time; }; From ad6b8310fc733045ae7f188c22726620706a513f Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Tue, 14 Jun 2022 14:36:33 +0800 Subject: [PATCH 104/127] Removed a unused proxy status api named test-show (#5136) close pingcap/tiflash#5135 --- .../Transaction/ProxyFFIStatusService.cpp | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/dbms/src/Storages/Transaction/ProxyFFIStatusService.cpp b/dbms/src/Storages/Transaction/ProxyFFIStatusService.cpp index dafacd8947d..792f149f588 100644 --- a/dbms/src/Storages/Transaction/ProxyFFIStatusService.cpp +++ b/dbms/src/Storages/Transaction/ProxyFFIStatusService.cpp @@ -22,26 +22,6 @@ namespace DB { -HttpRequestRes HandleHttpRequestTestShow( - EngineStoreServerWrap *, - std::string_view path, - const std::string & api_name, - std::string_view query, - std::string_view body) -{ - auto * res = RawCppString::New(fmt::format( - "api_name: {}\npath: {}\nquery: {}\nbody: {}", - api_name, - path, - query, - body)); - return HttpRequestRes{ - .status = HttpRequestStatus::Ok, - .res = CppStrWithView{ - .inner = GenRawCppPtr(res, RawCppPtrTypeImpl::String), - .view = BaseBuffView{res->data(), res->size()}}}; -} - HttpRequestRes HandleHttpRequestSyncStatus( EngineStoreServerWrap * server, std::string_view path, @@ -112,8 +92,7 @@ using HANDLE_HTTP_URI_METHOD = HttpRequestRes (*)(EngineStoreServerWrap *, std:: static const std::map AVAILABLE_HTTP_URI = { {"/tiflash/sync-status/", HandleHttpRequestSyncStatus}, - {"/tiflash/store-status", HandleHttpRequestStoreStatus}, - {"/tiflash/test-show", HandleHttpRequestTestShow}}; + {"/tiflash/store-status", HandleHttpRequestStoreStatus}}; uint8_t CheckHttpUriAvailable(BaseBuffView path_) { From 94aa0291482ab44ef36309e984f87a68d5e75122 Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Tue, 14 Jun 2022 15:06:33 +0800 Subject: [PATCH 105/127] Fix blobstore truncate size may not right (#5127) close pingcap/tiflash#5076, close pingcap/tiflash#5134 --- dbms/src/Storages/Page/V3/BlobStore.cpp | 55 ++++++++++++++----- dbms/src/Storages/Page/V3/PageDirectory.cpp | 2 +- dbms/src/Storages/Page/V3/spacemap/SpaceMap.h | 6 +- .../Storages/Page/V3/spacemap/SpaceMapBig.h | 2 +- .../Page/V3/spacemap/SpaceMapRBTree.cpp | 28 +++++++--- .../Page/V3/spacemap/SpaceMapRBTree.h | 2 +- .../Page/V3/spacemap/SpaceMapSTDMap.h | 22 +++++++- .../Storages/Page/V3/tests/gtest_free_map.cpp | 37 +++++++++++++ 8 files changed, 125 insertions(+), 29 deletions(-) diff --git a/dbms/src/Storages/Page/V3/BlobStore.cpp b/dbms/src/Storages/Page/V3/BlobStore.cpp index 37a4fd429f4..3bd0bd9c4fa 100644 --- a/dbms/src/Storages/Page/V3/BlobStore.cpp +++ b/dbms/src/Storages/Page/V3/BlobStore.cpp @@ -851,8 +851,8 @@ struct BlobStoreGCInfo toTypeString("Read-Only Blob", 0), toTypeString("No GC Blob", 1), toTypeString("Full GC Blob", 2), - toTypeString("Truncated Blob", 3), - toTypeString("Big Blob", 4)); + toTypeString("Big Blob", 3), + toTypeTruncateString("Truncated Blob")); } void appendToReadOnlyBlob(const BlobFileId blob_id, double valid_rate) @@ -870,23 +870,24 @@ struct BlobStoreGCInfo blob_gc_info[2].emplace_back(std::make_pair(blob_id, valid_rate)); } - void appendToTruncatedBlob(const BlobFileId blob_id, double valid_rate) + void appendToBigBlob(const BlobFileId blob_id, double valid_rate) { blob_gc_info[3].emplace_back(std::make_pair(blob_id, valid_rate)); } - void appendToBigBlob(const BlobFileId blob_id, double valid_rate) + void appendToTruncatedBlob(const BlobFileId blob_id, UInt64 origin_size, UInt64 truncated_size, double valid_rate) { - blob_gc_info[4].emplace_back(std::make_pair(blob_id, valid_rate)); + blob_gc_truncate_info.emplace_back(std::make_tuple(blob_id, origin_size, truncated_size, valid_rate)); } private: // 1. read only blob // 2. no need gc blob // 3. full gc blob - // 4. need truncate blob - // 5. big blob - std::vector> blob_gc_info[5]; + // 4. big blob + std::vector> blob_gc_info[4]; + + std::vector> blob_gc_truncate_info; String toTypeString(const std::string_view prefix, const size_t index) const { @@ -911,6 +912,32 @@ struct BlobStoreGCInfo return fmt_buf.toString(); } + + String toTypeTruncateString(const std::string_view prefix) const + { + FmtBuffer fmt_buf; + if (blob_gc_truncate_info.empty()) + { + fmt_buf.fmtAppend("{}: [null]", prefix); + } + else + { + fmt_buf.fmtAppend("{}: [", prefix); + fmt_buf.joinStr( + blob_gc_truncate_info.begin(), + blob_gc_truncate_info.end(), + [](const auto arg, FmtBuffer & fb) { + fb.fmtAppend("{} origin: {} truncate: {} rate: {:.2f}", // + std::get<0>(arg), // blob id + std::get<1>(arg), // origin size + std::get<2>(arg), // truncated size + std::get<3>(arg)); // valid rate + }, + ", "); + fmt_buf.append("]"); + } + return fmt_buf.toString(); + } }; std::vector BlobStore::getGCStats() @@ -953,7 +980,7 @@ std::vector BlobStore::getGCStats() } auto lock = stat->lock(); - auto right_margin = stat->smap->getRightMargin(); + auto right_margin = stat->smap->getUsedBoundary(); // Avoid divide by zero if (right_margin == 0) @@ -966,14 +993,13 @@ std::vector BlobStore::getGCStats() stat->sm_valid_rate)); } - LOG_FMT_TRACE(log, "Current blob is empty [blob_id={}, total size(all invalid)={}] [valid_rate={}].", stat->id, stat->sm_total_size, stat->sm_valid_rate); - // If current blob empty, the size of in disk blob may not empty // So we need truncate current blob, and let it be reused. auto blobfile = getBlobFile(stat->id); - LOG_FMT_TRACE(log, "Truncate empty blob file [blob_id={}] to 0.", stat->id); + LOG_FMT_INFO(log, "Current blob file is empty, truncated to zero [blob_id={}] [total_size={}] [valid_rate={}]", stat->id, stat->sm_total_size, stat->sm_valid_rate); blobfile->truncate(right_margin); - blobstore_gc_info.appendToTruncatedBlob(stat->id, stat->sm_valid_rate); + blobstore_gc_info.appendToTruncatedBlob(stat->id, stat->sm_total_size, right_margin, stat->sm_valid_rate); + stat->sm_total_size = right_margin; continue; } @@ -1014,9 +1040,10 @@ std::vector BlobStore::getGCStats() auto blobfile = getBlobFile(stat->id); LOG_FMT_TRACE(log, "Truncate blob file [blob_id={}] [origin size={}] [truncated size={}]", stat->id, stat->sm_total_size, right_margin); blobfile->truncate(right_margin); + blobstore_gc_info.appendToTruncatedBlob(stat->id, stat->sm_total_size, right_margin, stat->sm_valid_rate); + stat->sm_total_size = right_margin; stat->sm_valid_rate = stat->sm_valid_size * 1.0 / stat->sm_total_size; - blobstore_gc_info.appendToTruncatedBlob(stat->id, stat->sm_valid_rate); } } } diff --git a/dbms/src/Storages/Page/V3/PageDirectory.cpp b/dbms/src/Storages/Page/V3/PageDirectory.cpp index e9b754854b8..5eb275f5af5 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectory.cpp @@ -1223,7 +1223,7 @@ bool PageDirectory::tryDumpSnapshot(const ReadLimiterPtr & read_limiter, const W // `being_ref_count` by the function `createSnapshot()`. assert(!files_snap.persisted_log_files.empty()); // should not be empty when `needSave` return true auto log_num = files_snap.persisted_log_files.rbegin()->log_num; - auto identifier = fmt::format("{}_dump_{}", wal->name(), log_num); + auto identifier = fmt::format("{}.dump_{}", wal->name(), log_num); auto snapshot_reader = wal->createReaderForFiles(identifier, files_snap.persisted_log_files, read_limiter); PageDirectoryFactory factory; // we just use the `collapsed_dir` to dump edit of the snapshot, should never call functions like `apply` that diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h b/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h index ae44b608de0..d230b2f3e35 100644 --- a/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMap.h @@ -95,9 +95,11 @@ class SpaceMap virtual std::tuple searchInsertOffset(size_t size) = 0; /** - * Get the offset of the last free block. `[margin_offset, +∞)` is not used at all. + * Get the used boundary of this SpaceMap. + * The return value (`used_boundary`) means that `[used_bounary + 1, +∞)` is safe to be truncated. + * If the `used_boundary` is equal to the `end` of this SpaceMap, it means that there is no space to be truncated. */ - virtual UInt64 getRightMargin() = 0; + virtual UInt64 getUsedBoundary() = 0; /** * Get the accurate max capacity of the space map. diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMapBig.h b/dbms/src/Storages/Page/V3/spacemap/SpaceMapBig.h index 22128a09f30..81c2a5cb786 100644 --- a/dbms/src/Storages/Page/V3/spacemap/SpaceMapBig.h +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMapBig.h @@ -74,7 +74,7 @@ class BigSpaceMap return std::make_pair(size_in_used, size_in_used); } - UInt64 getRightMargin() override + UInt64 getUsedBoundary() override { return end; } diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.cpp b/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.cpp index 54275574060..4bd53b93e07 100644 --- a/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.cpp +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.cpp @@ -84,7 +84,7 @@ static void rb_get_new_entry(struct SmapRbEntry ** entry, UInt64 start, UInt64 c { struct SmapRbEntry * new_entry; - new_entry = static_cast(calloc(1, sizeof(struct SmapRbEntry))); + new_entry = static_cast(calloc(1, sizeof(struct SmapRbEntry))); // NOLINT if (new_entry == nullptr) { return; @@ -115,7 +115,7 @@ inline static void rb_free_entry(struct RbPrivate * private_data, struct SmapRbE private_data->read_index_next = nullptr; } - free(entry); + free(entry); // NOLINT } @@ -419,7 +419,7 @@ std::shared_ptr RBTreeSpaceMap::create(UInt64 start, UInt64 end) { auto ptr = std::shared_ptr(new RBTreeSpaceMap(start, end)); - ptr->rb_tree = static_cast(calloc(1, sizeof(struct RbPrivate))); + ptr->rb_tree = static_cast(calloc(1, sizeof(struct RbPrivate))); // NOLINT if (ptr->rb_tree == nullptr) { return nullptr; @@ -435,7 +435,7 @@ std::shared_ptr RBTreeSpaceMap::create(UInt64 start, UInt64 end) if (!rb_insert_entry(start, end, ptr->rb_tree, ptr->log)) { LOG_FMT_ERROR(ptr->log, "Erorr happend, when mark all space free. [start={}] , [end={}]", start, end); - free(ptr->rb_tree); + free(ptr->rb_tree); // NOLINT return nullptr; } return ptr; @@ -451,7 +451,7 @@ static void rb_free_tree(struct rb_root * root) next = rb_tree_next(node); entry = node_to_entry(node); rb_node_remove(node, root); - free(entry); + free(entry); // NOLINT } } @@ -460,7 +460,7 @@ void RBTreeSpaceMap::freeSmap() if (rb_tree) { rb_free_tree(&rb_tree->root); - free(rb_tree); + free(rb_tree); // NOLINT } } @@ -734,7 +734,7 @@ std::pair RBTreeSpaceMap::getSizes() const } } -UInt64 RBTreeSpaceMap::getRightMargin() +UInt64 RBTreeSpaceMap::getUsedBoundary() { struct rb_node * node = rb_tree_last(&rb_tree->root); if (node == nullptr) @@ -743,6 +743,20 @@ UInt64 RBTreeSpaceMap::getRightMargin() } auto * entry = node_to_entry(node); + + // If the `offset+size` of the last free node is not equal to `end`, it means the range `[last_node.offset, end)` is marked as used, + // then we should return `end` as the used boundary. + // + // eg. + // 1. The spacemap manage a space of `[0, 100]` + // 2. A span {offset=90, size=10} is marked as used, then the free range in SpaceMap is `[0, 90)` + // 3. The return value should be 100 + if (entry->start + entry->count != end) + { + return end; + } + + // Else we should return the offset of last free node return entry->start; } diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.h b/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.h index 0393fda081b..04691007a47 100644 --- a/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.h +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMapRBTree.h @@ -46,7 +46,7 @@ class RBTreeSpaceMap std::pair getSizes() const override; - UInt64 getRightMargin() override; + UInt64 getUsedBoundary() override; protected: RBTreeSpaceMap(UInt64 start, UInt64 end) diff --git a/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h b/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h index b6ff8797f0f..41ddd77d03a 100644 --- a/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h +++ b/dbms/src/Storages/Page/V3/spacemap/SpaceMapSTDMap.h @@ -111,13 +111,29 @@ class STDMapSpaceMap } } - UInt64 getRightMargin() override + UInt64 getUsedBoundary() override { if (free_map.empty()) { - return end - start; + return end; } - return free_map.rbegin()->first; + + const auto & last_node_it = free_map.rbegin(); + + // If the `offset+size` of the last free node is not equal to `end`, it means the range `[last_node.offset, end)` is marked as used, + // then we should return `end` as the used boundary. + // + // eg. + // 1. The spacemap manage a space of `[0, 100]` + // 2. A span {offset=90, size=10} is marked as used, then the free range in SpaceMap is `[0, 90)` + // 3. The return value should be 100 + if (last_node_it->first + last_node_it->second != end) + { + return end; + } + + // Else we should return the offset of last free node + return last_node_it->first; } bool isMarkUnused(UInt64 offset, size_t length) override diff --git a/dbms/src/Storages/Page/V3/tests/gtest_free_map.cpp b/dbms/src/Storages/Page/V3/tests/gtest_free_map.cpp index f7120f000b2..faec139920b 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_free_map.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_free_map.cpp @@ -427,6 +427,43 @@ TEST_P(SpaceMapTest, TestGetMaxCap) } } + +TEST_P(SpaceMapTest, TestGetUsedBoundary) +{ + { + auto smap = SpaceMap::createSpaceMap(test_type, 0, 100); + ASSERT_TRUE(smap->markUsed(50, 10)); + ASSERT_EQ(smap->getUsedBoundary(), 60); + ASSERT_TRUE(smap->markUsed(80, 10)); + ASSERT_EQ(smap->getUsedBoundary(), 90); + + ASSERT_TRUE(smap->markUsed(90, 10)); + ASSERT_EQ(smap->getUsedBoundary(), 100); + } + + { + auto smap = SpaceMap::createSpaceMap(test_type, 0, 100); + ASSERT_TRUE(smap->markUsed(90, 10)); + ASSERT_EQ(smap->getUsedBoundary(), 100); + + ASSERT_TRUE(smap->markUsed(20, 10)); + ASSERT_EQ(smap->getUsedBoundary(), 100); + + ASSERT_TRUE(smap->markFree(90, 10)); + ASSERT_EQ(smap->getUsedBoundary(), 30); + + ASSERT_TRUE(smap->markUsed(90, 10)); + ASSERT_EQ(smap->getUsedBoundary(), 100); + } + + { + auto smap = SpaceMap::createSpaceMap(test_type, 0, 100); + ASSERT_EQ(smap->getUsedBoundary(), 0); + ASSERT_TRUE(smap->markUsed(0, 100)); + ASSERT_EQ(smap->getUsedBoundary(), 100); + } +} + INSTANTIATE_TEST_CASE_P( Type, SpaceMapTest, From bcb837bc25215891a5c2b9a95d2cec085df66053 Mon Sep 17 00:00:00 2001 From: hongyunyan <649330952@qq.com> Date: Tue, 14 Jun 2022 15:58:33 +0800 Subject: [PATCH 106/127] enhencement: supplement the comment for SchemaActionType (#5139) ref pingcap/tiflash#4862 --- dbms/src/TiDB/Schema/SchemaBuilder.cpp | 10 +++++++++- dbms/src/TiDB/Schema/SchemaGetter.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/dbms/src/TiDB/Schema/SchemaBuilder.cpp b/dbms/src/TiDB/Schema/SchemaBuilder.cpp index f532ac231e2..ae78923fc61 100644 --- a/dbms/src/TiDB/Schema/SchemaBuilder.cpp +++ b/dbms/src/TiDB/Schema/SchemaBuilder.cpp @@ -370,7 +370,15 @@ void SchemaBuilder::applyAlterPhysicalTable(DBInfoPtr db_inf const auto & schema_change = schema_changes[i]; /// Update column infos by applying schema change in this step. schema_change.second(orig_table_info); - /// Update schema version aggressively for the sake of correctness. + /// Update schema version aggressively for the sake of correctness(for read part). + /// In read action, we will use table_info.schema_version(storage_version) and TiDBSchemaSyncer.cur_version(global_version) to compare with query_version, to decide whether we can read under this query_version, or we need to make the schema newer. + /// In our comparison logic, we only serve the query when the query schema version meet the criterion: storage_version <= query_version <= global_version(The more detail info you can refer the comments in DAGStorageInterpreter::getAndLockStorages.) + /// And when apply multi diffs here, we only update global_version when all diffs have been applied. + /// So the global_version may be less than the actual "global_version" of the local schema in the process of applying schema changes. + /// And if we don't update the storage_version ahead of time, we may meet the following case when apply multiple diffs: storage_version <= global_version < actual "global_version". + /// If we receive a query with the same version as global_version, we can have the following scenario: storage_version <= global_version == query_version < actual "global_version". + /// And because storage_version <= global_version == query_version meet the criterion of serving the query, the query will be served. But query_version < actual "global_version" indicates that we use a newer schema to server an older query which may cause some inconsistency issue. + /// So we update storage_version aggressively to prevent the above scenario happens. orig_table_info.schema_version = target_version; auto alter_lock = storage->lockForAlter(getThreadName()); storage->alterFromTiDB( diff --git a/dbms/src/TiDB/Schema/SchemaGetter.h b/dbms/src/TiDB/Schema/SchemaGetter.h index cfa5e1c6335..02d2f7a7c88 100644 --- a/dbms/src/TiDB/Schema/SchemaGetter.h +++ b/dbms/src/TiDB/Schema/SchemaGetter.h @@ -28,6 +28,7 @@ namespace DB { +// The enum results are completely the same as the DDL Action listed in the "parser/model/ddl.go" of TiDB codebase, which must be keeping in sync. enum class SchemaActionType : Int8 { None = 0, From 864cfe9933efdd3caca1d94d1d49e270b5c6285a Mon Sep 17 00:00:00 2001 From: xufei Date: Tue, 14 Jun 2022 17:22:33 +0800 Subject: [PATCH 107/127] Some refinements of `mpp_exchange_receiver_map` and `MPPTunnelSet` (#5132) ref pingcap/tiflash#5095 --- dbms/src/Flash/Coprocessor/DAGContext.cpp | 51 +-------- dbms/src/Flash/Coprocessor/DAGContext.h | 14 +-- dbms/src/Flash/Coprocessor/DAGUtils.cpp | 1 + dbms/src/Flash/Coprocessor/InterpreterDAG.cpp | 17 +-- dbms/src/Flash/Mpp/MPPTask.cpp | 100 +++++++++++++----- dbms/src/Flash/Mpp/MPPTask.h | 12 ++- dbms/src/Flash/Mpp/MPPTunnelSet.cpp | 14 +-- dbms/src/Flash/Mpp/MPPTunnelSet.h | 4 +- dbms/src/Interpreters/Context.cpp | 25 +++++ dbms/src/Interpreters/Context.h | 2 + 10 files changed, 130 insertions(+), 110 deletions(-) diff --git a/dbms/src/Flash/Coprocessor/DAGContext.cpp b/dbms/src/Flash/Coprocessor/DAGContext.cpp index 1736e0b6cec..1ef7338a589 100644 --- a/dbms/src/Flash/Coprocessor/DAGContext.cpp +++ b/dbms/src/Flash/Coprocessor/DAGContext.cpp @@ -205,60 +205,13 @@ void DAGContext::attachBlockIO(const BlockIO & io_) { io = io_; } -void DAGContext::initExchangeReceiverIfMPP(Context & context, size_t max_streams) -{ - if (isMPPTask()) - { - if (mpp_exchange_receiver_map_inited) - throw TiFlashException("Repeatedly initialize mpp_exchange_receiver_map", Errors::Coprocessor::Internal); - traverseExecutors(dag_request, [&](const tipb::Executor & executor) { - if (executor.tp() == tipb::ExecType::TypeExchangeReceiver) - { - assert(executor.has_executor_id()); - const auto & executor_id = executor.executor_id(); - // In order to distinguish different exchange receivers. - auto exchange_receiver = std::make_shared( - std::make_shared( - executor.exchange_receiver(), - getMPPTaskMeta(), - context.getTMTContext().getKVCluster(), - context.getTMTContext().getMPPTaskManager(), - context.getSettingsRef().enable_local_tunnel, - context.getSettingsRef().enable_async_grpc_client), - executor.exchange_receiver().encoded_task_meta_size(), - max_streams, - log->identifier(), - executor_id); - mpp_exchange_receiver_map[executor_id] = exchange_receiver; - new_thread_count_of_exchange_receiver += exchange_receiver->computeNewThreadCount(); - } - return true; - }); - mpp_exchange_receiver_map_inited = true; - } -} - const std::unordered_map> & DAGContext::getMPPExchangeReceiverMap() const { if (!isMPPTask()) throw TiFlashException("mpp_exchange_receiver_map is used in mpp only", Errors::Coprocessor::Internal); - if (!mpp_exchange_receiver_map_inited) - throw TiFlashException("mpp_exchange_receiver_map has not been initialized", Errors::Coprocessor::Internal); - return mpp_exchange_receiver_map; -} - -void DAGContext::cancelAllExchangeReceiver() -{ - for (auto & it : mpp_exchange_receiver_map) - { - it.second->cancel(); - } -} - -int DAGContext::getNewThreadCountOfExchangeReceiver() const -{ - return new_thread_count_of_exchange_receiver; + RUNTIME_ASSERT(mpp_exchange_receiver_map != nullptr, log, "MPPTask without exchange receiver map"); + return *mpp_exchange_receiver_map; } bool DAGContext::containsRegionsInfoForTable(Int64 table_id) const diff --git a/dbms/src/Flash/Coprocessor/DAGContext.h b/dbms/src/Flash/Coprocessor/DAGContext.h index c20eb3a367e..07b65b2d8fe 100644 --- a/dbms/src/Flash/Coprocessor/DAGContext.h +++ b/dbms/src/Flash/Coprocessor/DAGContext.h @@ -37,6 +37,8 @@ namespace DB class Context; class MPPTunnelSet; class ExchangeReceiver; +using ExchangeReceiverMap = std::unordered_map>; +using ExchangeReceiverMapPtr = std::shared_ptr>>; class Join; using JoinPtr = std::shared_ptr; @@ -254,7 +256,6 @@ class DAGContext return io; } - int getNewThreadCountOfExchangeReceiver() const; UInt64 getFlags() const { return flags; @@ -303,10 +304,11 @@ class DAGContext bool columnsForTestEmpty() { return columns_for_test_map.empty(); } - void cancelAllExchangeReceiver(); - - void initExchangeReceiverIfMPP(Context & context, size_t max_streams); const std::unordered_map> & getMPPExchangeReceiverMap() const; + void setMPPExchangeReceiverMap(ExchangeReceiverMapPtr & exchange_receiver_map) + { + mpp_exchange_receiver_map = exchange_receiver_map; + } void addSubquery(const String & subquery_id, SubqueryForSet && subquery); bool hasSubquery() const { return !subqueries.empty(); } @@ -367,10 +369,8 @@ class DAGContext ConcurrentBoundedQueue warnings; /// warning_count is the actual warning count during the entire execution std::atomic warning_count; - int new_thread_count_of_exchange_receiver = 0; /// key: executor_id of ExchangeReceiver nodes in dag. - std::unordered_map> mpp_exchange_receiver_map; - bool mpp_exchange_receiver_map_inited = false; + ExchangeReceiverMapPtr mpp_exchange_receiver_map; /// vector of SubqueriesForSets(such as join build subquery). /// The order of the vector is also the order of the subquery. std::vector subqueries; diff --git a/dbms/src/Flash/Coprocessor/DAGUtils.cpp b/dbms/src/Flash/Coprocessor/DAGUtils.cpp index bea26fe9f99..9ffa29cd14d 100644 --- a/dbms/src/Flash/Coprocessor/DAGUtils.cpp +++ b/dbms/src/Flash/Coprocessor/DAGUtils.cpp @@ -1432,6 +1432,7 @@ tipb::EncodeType analyzeDAGEncodeType(DAGContext & dag_context) return tipb::EncodeType::TypeDefault; return encode_type; } + tipb::ScalarFuncSig reverseGetFuncSigByFuncName(const String & name) { static std::unordered_map func_name_sig_map = getFuncNameToSigMap(); diff --git a/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp b/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp index a67ebf20aa5..0e767d65d77 100644 --- a/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp +++ b/dbms/src/Flash/Coprocessor/InterpreterDAG.cpp @@ -24,19 +24,8 @@ namespace DB InterpreterDAG::InterpreterDAG(Context & context_, const DAGQuerySource & dag_) : context(context_) , dag(dag_) + , max_streams(context.getMaxStreams()) { - const Settings & settings = context.getSettingsRef(); - if (dagContext().isBatchCop() || (dagContext().isMPPTask() && !dagContext().isTest())) - max_streams = settings.max_threads; - else if (dagContext().isTest()) - max_streams = dagContext().initialize_concurrency; - else - max_streams = 1; - - if (max_streams > 1) - { - max_streams *= settings.max_streams_to_max_threads_ratio; - } } void setRestorePipelineConcurrency(DAGQueryBlock & query_block) @@ -75,10 +64,6 @@ BlockInputStreams InterpreterDAG::executeQueryBlock(DAGQueryBlock & query_block) BlockIO InterpreterDAG::execute() { - /// Due to learner read, DAGQueryBlockInterpreter may take a long time to build - /// the query plan, so we init mpp exchange receiver before executeQueryBlock - dagContext().initExchangeReceiverIfMPP(context, max_streams); - BlockInputStreams streams = executeQueryBlock(*dag.getRootQueryBlock()); DAGPipeline pipeline; pipeline.streams = streams; diff --git a/dbms/src/Flash/Mpp/MPPTask.cpp b/dbms/src/Flash/Mpp/MPPTask.cpp index 8f9ca8e55e5..40f03ff79ba 100644 --- a/dbms/src/Flash/Mpp/MPPTask.cpp +++ b/dbms/src/Flash/Mpp/MPPTask.cpp @@ -22,11 +22,14 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -94,13 +97,73 @@ void MPPTask::run() newThreadManager()->scheduleThenDetach(true, "MPPTask", [self = shared_from_this()] { self->runImpl(); }); } -void MPPTask::registerTunnel(const MPPTaskId & task_id, MPPTunnelPtr tunnel) +void MPPTask::registerTunnels(const mpp::DispatchTaskRequest & task_request) { - if (status == CANCELLED) - throw Exception("the tunnel " + tunnel->id() + " can not been registered, because the task is cancelled"); + tunnel_set = std::make_shared(log->identifier()); + std::chrono::seconds timeout(task_request.timeout()); + const auto & exchange_sender = dag_req.root_executor().exchange_sender(); - RUNTIME_ASSERT(tunnel_set != nullptr, log, "mpp task without tunnel set"); - tunnel_set->registerTunnel(task_id, tunnel); + for (int i = 0; i < exchange_sender.encoded_task_meta_size(); ++i) + { + // exchange sender will register the tunnels and wait receiver to found a connection. + mpp::TaskMeta task_meta; + if (unlikely(!task_meta.ParseFromString(exchange_sender.encoded_task_meta(i)))) + throw TiFlashException("Failed to decode task meta info in ExchangeSender", Errors::Coprocessor::BadRequest); + bool is_local = context->getSettingsRef().enable_local_tunnel && meta.address() == task_meta.address(); + bool is_async = !is_local && context->getSettingsRef().enable_async_server; + MPPTunnelPtr tunnel = std::make_shared(task_meta, task_request.meta(), timeout, context->getSettingsRef().max_threads, is_local, is_async, log->identifier()); + LOG_FMT_DEBUG(log, "begin to register the tunnel {}", tunnel->id()); + if (status != INITIALIZING) + throw Exception(fmt::format("The tunnel {} can not be registered, because the task is not in initializing state", tunnel->id())); + tunnel_set->registerTunnel(MPPTaskId{task_meta.start_ts(), task_meta.task_id()}, tunnel); + if (!dag_context->isRootMPPTask()) + { + FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::exception_during_mpp_register_tunnel_for_non_root_mpp_task); + } + } +} + +void MPPTask::initExchangeReceivers() +{ + mpp_exchange_receiver_map = std::make_shared(); + traverseExecutors(&dag_req, [&](const tipb::Executor & executor) { + if (executor.tp() == tipb::ExecType::TypeExchangeReceiver) + { + assert(executor.has_executor_id()); + const auto & executor_id = executor.executor_id(); + // In order to distinguish different exchange receivers. + auto exchange_receiver = std::make_shared( + std::make_shared( + executor.exchange_receiver(), + dag_context->getMPPTaskMeta(), + context->getTMTContext().getKVCluster(), + context->getTMTContext().getMPPTaskManager(), + context->getSettingsRef().enable_local_tunnel, + context->getSettingsRef().enable_async_grpc_client), + executor.exchange_receiver().encoded_task_meta_size(), + context->getMaxStreams(), + log->identifier(), + executor_id); + if (status != RUNNING) + throw Exception("exchange receiver map can not be initialized, because the task is not in running state"); + + (*mpp_exchange_receiver_map)[executor_id] = exchange_receiver; + new_thread_count_of_exchange_receiver += exchange_receiver->computeNewThreadCount(); + } + return true; + }); + dag_context->setMPPExchangeReceiverMap(mpp_exchange_receiver_map); +} + +void MPPTask::cancelAllExchangeReceivers() +{ + if (likely(mpp_exchange_receiver_map != nullptr)) + { + for (auto & it : *mpp_exchange_receiver_map) + { + it.second->cancel(); + } + } } std::pair MPPTask::getTunnel(const ::mpp::EstablishMPPConnectionRequest * request) @@ -116,7 +179,7 @@ std::pair MPPTask::getTunnel(const ::mpp::EstablishMPPConn MPPTaskId receiver_id{request->receiver_meta().start_ts(), request->receiver_meta().task_id()}; RUNTIME_ASSERT(tunnel_set != nullptr, log, "mpp task without tunnel set"); - auto tunnel_ptr = tunnel_set->getTunnelById(receiver_id); + auto tunnel_ptr = tunnel_set->getTunnelByReceiverTaskId(receiver_id); if (tunnel_ptr == nullptr) { auto err_msg = fmt::format( @@ -207,25 +270,8 @@ void MPPTask::prepare(const mpp::DispatchTaskRequest & task_request) } // register tunnels - tunnel_set = std::make_shared(log->identifier()); - std::chrono::seconds timeout(task_request.timeout()); + registerTunnels(task_request); - for (int i = 0; i < exchange_sender.encoded_task_meta_size(); i++) - { - // exchange sender will register the tunnels and wait receiver to found a connection. - mpp::TaskMeta task_meta; - if (!task_meta.ParseFromString(exchange_sender.encoded_task_meta(i))) - throw TiFlashException("Failed to decode task meta info in ExchangeSender", Errors::Coprocessor::BadRequest); - bool is_local = context->getSettingsRef().enable_local_tunnel && meta.address() == task_meta.address(); - bool is_async = !is_local && context->getSettingsRef().enable_async_server; - MPPTunnelPtr tunnel = std::make_shared(task_meta, task_request.meta(), timeout, context->getSettingsRef().max_threads, is_local, is_async, log->identifier()); - LOG_FMT_DEBUG(log, "begin to register the tunnel {}", tunnel->id()); - registerTunnel(MPPTaskId{task_meta.start_ts(), task_meta.task_id()}, tunnel); - if (!dag_context->isRootMPPTask()) - { - FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::exception_during_mpp_register_tunnel_for_non_root_mpp_task); - } - } dag_context->tunnel_set = tunnel_set; // register task. auto task_manager = tmt_context.getMPPTaskManager(); @@ -251,6 +297,7 @@ void MPPTask::prepare(const mpp::DispatchTaskRequest & task_request) void MPPTask::preprocess() { auto start_time = Clock::now(); + initExchangeReceivers(); DAGQuerySource dag(*context); executeQuery(dag, *context, false, QueryProcessingStage::Complete); auto end_time = Clock::now(); @@ -280,7 +327,7 @@ void MPPTask::runImpl() LOG_FMT_INFO(log, "task starts preprocessing"); preprocess(); needed_threads = estimateCountOfNewThreads(); - LOG_FMT_DEBUG(log, "Estimate new thread count of query :{} including tunnel_threads: {} , receiver_threads: {}", needed_threads, dag_context->tunnel_set->getRemoteTunnelCnt(), dag_context->getNewThreadCountOfExchangeReceiver()); + LOG_FMT_DEBUG(log, "Estimate new thread count of query :{} including tunnel_threads: {} , receiver_threads: {}", needed_threads, dag_context->tunnel_set->getRemoteTunnelCnt(), new_thread_count_of_exchange_receiver); scheduleOrWait(); @@ -346,8 +393,7 @@ void MPPTask::runImpl() else { context->getProcessList().sendCancelToQuery(context->getCurrentQueryId(), context->getClientInfo().current_user, true); - if (dag_context) - dag_context->cancelAllExchangeReceiver(); + cancelAllExchangeReceivers(); writeErrToAllTunnels(err_msg); } LOG_FMT_INFO(log, "task ends, time cost is {} ms.", stopwatch.elapsedMilliseconds()); diff --git a/dbms/src/Flash/Mpp/MPPTask.h b/dbms/src/Flash/Mpp/MPPTask.h index ee434a2f2ff..c8423ac484c 100644 --- a/dbms/src/Flash/Mpp/MPPTask.h +++ b/dbms/src/Flash/Mpp/MPPTask.h @@ -62,8 +62,6 @@ class MPPTask : public std::enable_shared_from_this void run(); - void registerTunnel(const MPPTaskId & id, MPPTunnelPtr tunnel); - int getNeededThreads(); enum class ScheduleState @@ -107,6 +105,12 @@ class MPPTask : public std::enable_shared_from_this int estimateCountOfNewThreads(); + void registerTunnels(const mpp::DispatchTaskRequest & task_request); + + void initExchangeReceivers(); + + void cancelAllExchangeReceivers(); + tipb::DAGRequest dag_req; ContextPtr context; @@ -122,6 +126,10 @@ class MPPTask : public std::enable_shared_from_this MPPTaskId id; MPPTunnelSetPtr tunnel_set; + /// key: executor_id of ExchangeReceiver nodes in dag. + ExchangeReceiverMapPtr mpp_exchange_receiver_map; + + int new_thread_count_of_exchange_receiver = 0; MPPTaskManager * manager = nullptr; diff --git a/dbms/src/Flash/Mpp/MPPTunnelSet.cpp b/dbms/src/Flash/Mpp/MPPTunnelSet.cpp index 500e9501b08..8d709bb7d38 100644 --- a/dbms/src/Flash/Mpp/MPPTunnelSet.cpp +++ b/dbms/src/Flash/Mpp/MPPTunnelSet.cpp @@ -133,12 +133,12 @@ void MPPTunnelSetBase::writeError(const String & msg) } template -void MPPTunnelSetBase::registerTunnel(const MPPTaskId & id, const TunnelPtr & tunnel) +void MPPTunnelSetBase::registerTunnel(const MPPTaskId & receiver_task_id, const TunnelPtr & tunnel) { - if (id_to_index_map.find(id) != id_to_index_map.end()) - throw Exception("the tunnel " + tunnel->id() + " has been registered"); + if (receiver_task_id_to_index_map.find(receiver_task_id) != receiver_task_id_to_index_map.end()) + throw Exception(fmt::format("the tunnel {} has been registered", tunnel->id())); - id_to_index_map[id] = tunnels.size(); + receiver_task_id_to_index_map[receiver_task_id] = tunnels.size(); tunnels.push_back(tunnel); if (!tunnel->isLocal()) { @@ -163,10 +163,10 @@ void MPPTunnelSetBase::finishWrite() } template -typename MPPTunnelSetBase::TunnelPtr MPPTunnelSetBase::getTunnelById(const MPPTaskId & id) +typename MPPTunnelSetBase::TunnelPtr MPPTunnelSetBase::getTunnelByReceiverTaskId(const MPPTaskId & id) { - auto it = id_to_index_map.find(id); - if (it == id_to_index_map.end()) + auto it = receiver_task_id_to_index_map.find(id); + if (it == receiver_task_id_to_index_map.end()) { return nullptr; } diff --git a/dbms/src/Flash/Mpp/MPPTunnelSet.h b/dbms/src/Flash/Mpp/MPPTunnelSet.h index 021c609f516..e4123db1be5 100644 --- a/dbms/src/Flash/Mpp/MPPTunnelSet.h +++ b/dbms/src/Flash/Mpp/MPPTunnelSet.h @@ -59,7 +59,7 @@ class MPPTunnelSetBase : private boost::noncopyable void finishWrite(); void registerTunnel(const MPPTaskId & id, const TunnelPtr & tunnel); - TunnelPtr getTunnelById(const MPPTaskId & id); + TunnelPtr getTunnelByReceiverTaskId(const MPPTaskId & id); uint16_t getPartitionNum() const { return tunnels.size(); } @@ -72,7 +72,7 @@ class MPPTunnelSetBase : private boost::noncopyable private: std::vector tunnels; - std::unordered_map id_to_index_map; + std::unordered_map receiver_task_id_to_index_map; const LoggerPtr log; int remote_tunnel_cnt = 0; diff --git a/dbms/src/Interpreters/Context.cpp b/dbms/src/Interpreters/Context.cpp index a0adef5b50d..3beedbd3601 100644 --- a/dbms/src/Interpreters/Context.cpp +++ b/dbms/src/Interpreters/Context.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -1879,6 +1880,30 @@ SharedQueriesPtr Context::getSharedQueries() return shared->shared_queries; } +size_t Context::getMaxStreams() const +{ + size_t max_streams = settings.max_threads; + bool is_cop_request = false; + if (dag_context != nullptr) + { + if (dag_context->isTest()) + max_streams = dag_context->initialize_concurrency; + else if (!dag_context->isBatchCop() && !dag_context->isMPPTask()) + { + is_cop_request = true; + max_streams = 1; + } + } + if (max_streams > 1) + max_streams *= settings.max_streams_to_max_threads_ratio; + if (max_streams == 0) + max_streams = 1; + if (unlikely(max_streams != 1 && is_cop_request)) + /// for cop request, the max_streams should be 1 + throw Exception("Cop request only support running with max_streams = 1"); + return max_streams; +} + SessionCleaner::~SessionCleaner() { try diff --git a/dbms/src/Interpreters/Context.h b/dbms/src/Interpreters/Context.h index 5d5c39263c6..b6e759e364b 100644 --- a/dbms/src/Interpreters/Context.h +++ b/dbms/src/Interpreters/Context.h @@ -459,6 +459,8 @@ class Context void reloadDeltaTreeConfig(const Poco::Util::AbstractConfiguration & config); + size_t getMaxStreams() const; + private: /** Check if the current client has access to the specified database. * If access is denied, throw an exception. From a79ad91e8b3b8fe8da6b447f4ab46206e94a3971 Mon Sep 17 00:00:00 2001 From: Lloyd-Pottiger <60744015+Lloyd-Pottiger@users.noreply.github.com> Date: Wed, 15 Jun 2022 14:18:33 +0800 Subject: [PATCH 108/127] Revise default background threads size (#4723) close pingcap/tiflash#4702 --- dbms/src/Core/Defines.h | 1 - dbms/src/Interpreters/Context.cpp | 24 ++++++++++---- dbms/src/Interpreters/Context.h | 4 ++- dbms/src/Interpreters/Settings.h | 6 ++-- dbms/src/Server/Server.cpp | 33 +++++++++++-------- .../src/Storages/BackgroundProcessingPool.cpp | 3 ++ dbms/src/Storages/BackgroundProcessingPool.h | 4 +-- dbms/src/TestUtils/TiFlashTestEnv.cpp | 6 ++++ 8 files changed, 54 insertions(+), 27 deletions(-) diff --git a/dbms/src/Core/Defines.h b/dbms/src/Core/Defines.h index 33d116dae33..75f6f16bb25 100644 --- a/dbms/src/Core/Defines.h +++ b/dbms/src/Core/Defines.h @@ -78,7 +78,6 @@ /// too short a period can cause errors to disappear immediately after creation. #define DBMS_CONNECTION_POOL_WITH_FAILOVER_DEFAULT_DECREASE_ERROR_PERIOD (2 * DBMS_DEFAULT_SEND_TIMEOUT_SEC) #define DEFAULT_QUERIES_QUEUE_WAIT_TIME_MS 5000 /// Maximum waiting time in the request queue. -#define DBMS_DEFAULT_BACKGROUND_POOL_SIZE 16 #define DBMS_MIN_REVISION_WITH_CLIENT_INFO 54032 #define DBMS_MIN_REVISION_WITH_SERVER_TIMEZONE 54058 diff --git a/dbms/src/Interpreters/Context.cpp b/dbms/src/Interpreters/Context.cpp index 3beedbd3601..a0345daaa75 100644 --- a/dbms/src/Interpreters/Context.cpp +++ b/dbms/src/Interpreters/Context.cpp @@ -68,10 +68,8 @@ #include #include -#include #include -#include - +#include namespace ProfileEvents { @@ -1443,19 +1441,33 @@ void Context::dropCaches() const } BackgroundProcessingPool & Context::getBackgroundPool() +{ + // Note: shared->background_pool should be initialized first. + auto lock = getLock(); + return *shared->background_pool; +} + +BackgroundProcessingPool & Context::initializeBackgroundPool(UInt16 pool_size) { auto lock = getLock(); if (!shared->background_pool) - shared->background_pool = std::make_shared(settings.background_pool_size); + shared->background_pool = std::make_shared(pool_size); return *shared->background_pool; } BackgroundProcessingPool & Context::getBlockableBackgroundPool() { - // TODO: choose a better thread pool size and maybe a better name for the pool + // TODO: maybe a better name for the pool + // Note: shared->blockable_background_pool should be initialized first. + auto lock = getLock(); + return *shared->blockable_background_pool; +} + +BackgroundProcessingPool & Context::initializeBlockableBackgroundPool(UInt16 pool_size) +{ auto lock = getLock(); if (!shared->blockable_background_pool) - shared->blockable_background_pool = std::make_shared(settings.background_pool_size); + shared->blockable_background_pool = std::make_shared(pool_size); return *shared->blockable_background_pool; } diff --git a/dbms/src/Interpreters/Context.h b/dbms/src/Interpreters/Context.h index b6e759e364b..434179e1ab8 100644 --- a/dbms/src/Interpreters/Context.h +++ b/dbms/src/Interpreters/Context.h @@ -380,7 +380,9 @@ class Context bool useL0Opt() const; BackgroundProcessingPool & getBackgroundPool(); + BackgroundProcessingPool & initializeBackgroundPool(UInt16 pool_size); BackgroundProcessingPool & getBlockableBackgroundPool(); + BackgroundProcessingPool & initializeBlockableBackgroundPool(UInt16 pool_size); void createTMTContext(const TiFlashRaftConfig & raft_config, pingcap::ClusterConfig && cluster_config); @@ -505,7 +507,7 @@ class DDLGuard class SessionCleaner { public: - SessionCleaner(Context & context_) + explicit SessionCleaner(Context & context_) : context{context_} {} ~SessionCleaner(); diff --git a/dbms/src/Interpreters/Settings.h b/dbms/src/Interpreters/Settings.h index 9361e0525d2..f2b3bbbd7fe 100644 --- a/dbms/src/Interpreters/Settings.h +++ b/dbms/src/Interpreters/Settings.h @@ -80,8 +80,8 @@ struct Settings M(SettingBool, extremes, false, "Calculate minimums and maximums of the result columns. They can be output in JSON-formats.") \ M(SettingBool, use_uncompressed_cache, true, "Whether to use the cache of uncompressed blocks.") \ M(SettingBool, replace_running_query, false, "Whether the running request should be canceled with the same id as the new one.") \ - M(SettingUInt64, background_pool_size, DBMS_DEFAULT_BACKGROUND_POOL_SIZE, "Number of threads performing background work for tables (for example, merging in merge tree). Only has meaning at server " \ - "startup.") \ + M(SettingUInt64, background_pool_size, 0, "Number of threads performing background work for tables (for example, merging in merge tree). Only effective at server startup. " \ + "0 means a quarter of the number of logical CPU cores of the machine.") \ \ M(SettingBool, optimize_move_to_prewhere, true, "Allows disabling WHERE to PREWHERE optimization in SELECT queries from MergeTree.") \ \ @@ -356,7 +356,7 @@ struct Settings M(SettingUInt64, elastic_threadpool_shrink_period_ms, 300000, "The shrink period(ms) of elastic thread pool.") \ M(SettingBool, enable_local_tunnel, true, "Enable local data transfer between local MPP tasks.") \ M(SettingBool, enable_async_grpc_client, true, "Enable async grpc in MPP.") \ - M(SettingUInt64, grpc_completion_queue_pool_size, 0, "The size of gRPC completion queue pool. 0 means using hardware_concurrency.")\ + M(SettingUInt64, grpc_completion_queue_pool_size, 0, "The size of gRPC completion queue pool. 0 means the number of logical CPU cores. Only effective at server startup")\ M(SettingBool, enable_async_server, true, "Enable async rpc server.") \ M(SettingUInt64, async_pollers_per_cq, 200, "grpc async pollers per cqs") \ M(SettingUInt64, async_cqs, 1, "grpc async cqs") \ diff --git a/dbms/src/Server/Server.cpp b/dbms/src/Server/Server.cpp index 3e2c29de76c..95c1d5d3f2a 100644 --- a/dbms/src/Server/Server.cpp +++ b/dbms/src/Server/Server.cpp @@ -53,10 +53,15 @@ #include #include #include +#include +#include +#include #include #include #include +#include #include +#include #include #include #include @@ -81,12 +86,6 @@ #include #include -#include "HTTPHandlerFactory.h" -#include "MetricsPrometheus.h" -#include "MetricsTransmitter.h" -#include "StatusFile.h" -#include "TCPHandlerFactory.h" - #if Poco_NetSSL_FOUND #include #include @@ -1135,6 +1134,19 @@ int Server::main(const std::vector & /*args*/) global_context->getPathCapacity(), global_context->getFileProvider()); + /// if default value of background_pool_size is 0 + /// set it to the a quarter of the number of logical CPU cores of machine. + Settings & settings = global_context->getSettingsRef(); + if (settings.background_pool_size == 0) + { + global_context->setSetting("background_pool_size", std::to_string(server_info.cpu_info.logical_cores / 4)); + } + LOG_FMT_INFO(log, "Background & Blockable Background pool size: {}", settings.background_pool_size); + + /// Initialize the background & blockable background thread pool. + auto & bg_pool = global_context->initializeBackgroundPool(settings.background_pool_size); + auto & blockable_bg_pool = global_context->initializeBlockableBackgroundPool(settings.background_pool_size); + global_context->initializePageStorageMode(global_context->getPathPool(), STORAGE_FORMAT_CURRENT.page); global_context->initializeGlobalStoragePoolIfNeed(global_context->getPathPool()); LOG_FMT_INFO(log, "Global PageStorage run mode is {}", static_cast(global_context->getPageStorageRunMode())); @@ -1251,13 +1263,6 @@ int Server::main(const std::vector & /*args*/) /// Load global settings from default_profile and system_profile. /// It internally depends on UserConfig::parseSettings. global_context->setDefaultProfiles(config()); - Settings & settings = global_context->getSettingsRef(); - - /// Initialize the background thread pool. - /// It internally depends on settings.background_pool_size, - /// so must be called after settings has been load. - auto & bg_pool = global_context->getBackgroundPool(); - auto & blockable_bg_pool = global_context->getBlockableBackgroundPool(); /// Initialize RateLimiter. global_context->initializeRateLimiter(config(), bg_pool, blockable_bg_pool); @@ -1409,7 +1414,7 @@ int Server::main(const std::vector & /*args*/) { auto size = settings.grpc_completion_queue_pool_size; if (size == 0) - size = std::thread::hardware_concurrency(); + size = server_info.cpu_info.logical_cores; GRPCCompletionQueuePool::global_instance = std::make_unique(size); } diff --git a/dbms/src/Storages/BackgroundProcessingPool.cpp b/dbms/src/Storages/BackgroundProcessingPool.cpp index 96c2c6cc622..9fb4271ea38 100644 --- a/dbms/src/Storages/BackgroundProcessingPool.cpp +++ b/dbms/src/Storages/BackgroundProcessingPool.cpp @@ -87,6 +87,9 @@ BackgroundProcessingPool::BackgroundProcessingPool(int size_) : size(size_) , thread_ids_counter(size_) { + if (size <= 0) + throw Exception("BackgroundProcessingPool size must be greater than 0", ErrorCodes::LOGICAL_ERROR); + LOG_FMT_INFO(&Poco::Logger::get("BackgroundProcessingPool"), "Create BackgroundProcessingPool with {} threads", size); threads.resize(size); diff --git a/dbms/src/Storages/BackgroundProcessingPool.h b/dbms/src/Storages/BackgroundProcessingPool.h index 1ba6c4efcf8..49a01b3a397 100644 --- a/dbms/src/Storages/BackgroundProcessingPool.h +++ b/dbms/src/Storages/BackgroundProcessingPool.h @@ -81,7 +81,7 @@ class BackgroundProcessingPool using TaskHandle = std::shared_ptr; - BackgroundProcessingPool(int size_); + explicit BackgroundProcessingPool(int size_); size_t getNumberOfThreads() const { return size; } @@ -96,7 +96,7 @@ class BackgroundProcessingPool /// 2. thread B also get the same task /// 3. thread A finish the execution of the task quickly, release the task and try to update the next schedule time of the task /// 4. thread B find the task is not occupied and execute the task again almost immediately - TaskHandle addTask(const Task & task, const bool multi = true, const size_t interval_ms = 0); + TaskHandle addTask(const Task & task, bool multi = true, size_t interval_ms = 0); void removeTask(const TaskHandle & task); ~BackgroundProcessingPool(); diff --git a/dbms/src/TestUtils/TiFlashTestEnv.cpp b/dbms/src/TestUtils/TiFlashTestEnv.cpp index cbd42b57550..a7bcfe43d7a 100644 --- a/dbms/src/TestUtils/TiFlashTestEnv.cpp +++ b/dbms/src/TestUtils/TiFlashTestEnv.cpp @@ -24,6 +24,8 @@ #include #include +#include + namespace DB::tests { std::unique_ptr TiFlashTestEnv::global_context = nullptr; @@ -39,6 +41,10 @@ void TiFlashTestEnv::initializeGlobalContext(Strings testdata_path, PageStorageR KeyManagerPtr key_manager = std::make_shared(false); global_context->initializeFileProvider(key_manager, false); + // initialize background & blockable background thread pool + global_context->initializeBackgroundPool(std::thread::hardware_concurrency() / 4); + global_context->initializeBlockableBackgroundPool(std::thread::hardware_concurrency() / 4); + // Theses global variables should be initialized by the following order // 1. capacity // 2. path pool From 617fe546febdf939c1a40aeefacbd22592ef757c Mon Sep 17 00:00:00 2001 From: guo-shaoge Date: Thu, 16 Jun 2022 11:26:34 +0800 Subject: [PATCH 109/127] add microbenchmark for exchange and window function (#5137) close pingcap/tiflash#4276, close pingcap/tiflash#5138 --- dbms/CMakeLists.txt | 1 + dbms/src/Debug/astToExecutor.h | 2 +- .../Flash/Coprocessor/DAGExpressionAnalyzer.h | 2 + .../Coprocessor/DAGQueryBlockInterpreter.h | 2 + dbms/src/Flash/tests/CMakeLists.txt | 11 - dbms/src/Flash/tests/WindowTestUtil.h | 81 ++ dbms/src/Flash/tests/bench_exchange.cpp | 407 ++++++++++ dbms/src/Flash/tests/bench_exchange.h | 291 ++++++++ dbms/src/Flash/tests/bench_window.cpp | 107 +++ dbms/src/Flash/tests/exchange_perftest.cpp | 699 ------------------ dbms/src/TestUtils/mockExecutor.cpp | 3 +- dbms/src/TestUtils/mockExecutor.h | 2 +- libs/libcommon/include/common/types.h | 1 + 13 files changed, 896 insertions(+), 713 deletions(-) create mode 100644 dbms/src/Flash/tests/WindowTestUtil.h create mode 100644 dbms/src/Flash/tests/bench_exchange.cpp create mode 100644 dbms/src/Flash/tests/bench_exchange.h create mode 100644 dbms/src/Flash/tests/bench_window.cpp delete mode 100644 dbms/src/Flash/tests/exchange_perftest.cpp diff --git a/dbms/CMakeLists.txt b/dbms/CMakeLists.txt index e1e52fab73b..0df79f89a84 100644 --- a/dbms/CMakeLists.txt +++ b/dbms/CMakeLists.txt @@ -316,6 +316,7 @@ if (ENABLE_TESTS) ${TiFlash_SOURCE_DIR}/dbms/src/AggregateFunctions/AggregateFunctionSum.cpp ) target_include_directories(bench_dbms BEFORE PRIVATE ${SPARCEHASH_INCLUDE_DIR} ${benchmark_SOURCE_DIR}/include) + target_compile_definitions(bench_dbms PUBLIC DBMS_PUBLIC_GTEST) target_link_libraries(bench_dbms gtest dbms test_util_bench_main benchmark clickhouse_functions) if (ENABLE_TIFLASH_DTWORKLOAD) diff --git a/dbms/src/Debug/astToExecutor.h b/dbms/src/Debug/astToExecutor.h index cbd2e5ade3a..4d87c0db77e 100644 --- a/dbms/src/Debug/astToExecutor.h +++ b/dbms/src/Debug/astToExecutor.h @@ -350,4 +350,4 @@ ExecutorPtr compileWindow(ExecutorPtr input, size_t & executor_index, ASTPtr fun ExecutorPtr compileSort(ExecutorPtr input, size_t & executor_index, ASTPtr order_by_expr_list, bool is_partial_sort); void literalFieldToTiPBExpr(const ColumnInfo & ci, const Field & field, tipb::Expr * expr, Int32 collator_id); -} // namespace DB \ No newline at end of file +} // namespace DB diff --git a/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h b/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h index 9f201006a88..046088ab2b2 100644 --- a/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h +++ b/dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h @@ -153,7 +153,9 @@ class DAGExpressionAnalyzer : private boost::noncopyable const tipb::Window & window, size_t window_columns_start_index); +#ifndef DBMS_PUBLIC_GTEST private: +#endif NamesAndTypes buildOrderColumns( const ExpressionActionsPtr & actions, const ::google::protobuf::RepeatedPtrField & order_by); diff --git a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h index 0b3b2db9623..e68c4f91cee 100644 --- a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h +++ b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.h @@ -54,7 +54,9 @@ class DAGQueryBlockInterpreter BlockInputStreams execute(); +#ifndef DBMS_PUBLIC_GTEST private: +#endif void executeImpl(DAGPipeline & pipeline); void handleMockTableScan(const TiDBTableScan & table_scan, DAGPipeline & pipeline); void handleTableScan(const TiDBTableScan & table_scan, DAGPipeline & pipeline); diff --git a/dbms/src/Flash/tests/CMakeLists.txt b/dbms/src/Flash/tests/CMakeLists.txt index a34e4b23432..944908dcb25 100644 --- a/dbms/src/Flash/tests/CMakeLists.txt +++ b/dbms/src/Flash/tests/CMakeLists.txt @@ -13,14 +13,3 @@ # limitations under the License. include_directories (${CMAKE_CURRENT_BINARY_DIR}) - -add_executable (exchange_perftest - exchange_perftest.cpp - ${TiFlash_SOURCE_DIR}/dbms/src/Server/StorageConfigParser.cpp - ${TiFlash_SOURCE_DIR}/dbms/src/Functions/FunctionsConversion.cpp) -target_link_libraries (exchange_perftest - gtest_main - dbms - clickhouse_functions - clickhouse_aggregate_functions - tiflash-dttool-lib) diff --git a/dbms/src/Flash/tests/WindowTestUtil.h b/dbms/src/Flash/tests/WindowTestUtil.h new file mode 100644 index 00000000000..3f4cb7d595f --- /dev/null +++ b/dbms/src/Flash/tests/WindowTestUtil.h @@ -0,0 +1,81 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +#include + +namespace DB +{ +namespace tests +{ + +inline std::shared_ptr mockInterpreter(Context & context, const std::vector & source_columns, int concurrency) +{ + std::vector mock_input_streams_vec = {}; + DAGQueryBlock mock_query_block(0, static_cast>(nullptr)); + std::vector mock_subqueries_for_sets = {}; + std::shared_ptr mock_interpreter = std::make_shared(context, + mock_input_streams_vec, + mock_query_block, + concurrency); + mock_interpreter->analyzer = std::make_unique(std::move(source_columns), context); + return mock_interpreter; +} + +inline void mockExecuteProject(std::shared_ptr & mock_interpreter, DAGPipeline & pipeline, NamesWithAliases & final_project) +{ + mock_interpreter->executeProject(pipeline, final_project); +} + +inline void mockExecuteWindowOrder(std::shared_ptr & mock_interpreter, DAGPipeline & pipeline, const tipb::Sort & sort) +{ + mock_interpreter->handleWindowOrder(pipeline, sort); + mock_interpreter->input_streams_vec[0] = pipeline.streams; + NamesWithAliases final_project; + for (const auto & column : (*mock_interpreter->analyzer).source_columns) + { + final_project.push_back({column.name, ""}); + } + mockExecuteProject(mock_interpreter, pipeline, final_project); +} + +inline void mockExecuteWindowOrder(std::shared_ptr & mock_interpreter, DAGPipeline & pipeline, const String & sort_json) +{ + tipb::Sort sort; + ::google::protobuf::util::JsonStringToMessage(sort_json, &sort); + mockExecuteWindowOrder(mock_interpreter, pipeline, sort); +} + +inline void mockExecuteWindow(std::shared_ptr & mock_interpreter, DAGPipeline & pipeline, const tipb::Window & window) +{ + mock_interpreter->handleWindow(pipeline, window); + mock_interpreter->input_streams_vec[0] = pipeline.streams; + NamesWithAliases final_project; + for (const auto & column : (*mock_interpreter->analyzer).source_columns) + { + final_project.push_back({column.name, ""}); + } + mockExecuteProject(mock_interpreter, pipeline, final_project); +} + +inline void mockExecuteWindow(std::shared_ptr & mock_interpreter, DAGPipeline & pipeline, std::string window_json_str) +{ + tipb::Window window; + google::protobuf::util::JsonStringToMessage(window_json_str, &window); + mockExecuteWindow(mock_interpreter, pipeline, window); +} + +} // namespace tests +} // namespace DB diff --git a/dbms/src/Flash/tests/bench_exchange.cpp b/dbms/src/Flash/tests/bench_exchange.cpp new file mode 100644 index 00000000000..fbb53bfd4a4 --- /dev/null +++ b/dbms/src/Flash/tests/bench_exchange.cpp @@ -0,0 +1,407 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include + +#include // to include the implementation of StreamingDAGResponseWriter +#include // to include the implementation of ExchangeReceiver +#include // to include the implementation of MPPTunnel +#include // to include the implementation of MPPTunnelSet +#include +#include + + +namespace DB +{ +namespace tests +{ + +std::random_device rd; + +MockBlockInputStream::MockBlockInputStream(const std::vector & blocks_, StopFlag & stop_flag_) + : blocks(blocks_) + , header(blocks[0].cloneEmpty()) + , mt(rd()) + , dist(0, blocks.size() - 1) + , stop_flag(stop_flag_) +{} + +MockFixedRowsBlockInputStream::MockFixedRowsBlockInputStream(size_t total_rows_, const std::vector & blocks_) + : header(blocks_[0].cloneEmpty()) + , mt(rd()) + , dist(0, blocks_.size() - 1) + , current_rows(0) + , total_rows(total_rows_) + , blocks(blocks_) +{} + +Block makeBlock(int row_num) +{ + std::mt19937 mt(rd()); + std::uniform_int_distribution int64_dist; + std::uniform_int_distribution len_dist(10, 20); + std::uniform_int_distribution char_dist; + + InferredDataVector> int64_vec; + InferredDataVector> int64_vec2; + for (int i = 0; i < row_num; ++i) + { + int64_vec.emplace_back(int64_dist(mt)); + int64_vec2.emplace_back(int64_dist(mt)); + } + + InferredDataVector> string_vec; + for (int i = 0; i < row_num; ++i) + { + int len = len_dist(mt); + String s; + for (int j = 0; j < len; ++j) + s.push_back(char_dist(mt)); + string_vec.push_back(std::move(s)); + } + + auto int64_data_type = makeDataType>(); + ColumnWithTypeAndName int64_column(makeColumn>(int64_data_type, int64_vec), int64_data_type, "int64_1"); + ColumnWithTypeAndName int64_column2(makeColumn>(int64_data_type, int64_vec2), int64_data_type, "int64_2"); + + auto string_data_type = makeDataType>(); + ColumnWithTypeAndName string_column(makeColumn>(string_data_type, string_vec), string_data_type, "string"); + + return Block({int64_column, string_column, int64_column2}); +} + +std::vector makeBlocks(int block_num, int row_num) +{ + std::vector blocks; + for (int i = 0; i < block_num; ++i) + blocks.push_back(makeBlock(row_num)); + return blocks; +} + +mpp::MPPDataPacket makePacket(ChunkCodecStream & codec, int row_num) +{ + auto block = makeBlock(row_num); + codec.encode(block, 0, row_num); + + mpp::MPPDataPacket packet; + packet.add_chunks(codec.getString()); + codec.clear(); + + return packet; +} + +std::vector makePackets(ChunkCodecStream & codec, int packet_num, int row_num) +{ + std::vector packets; + for (int i = 0; i < packet_num; ++i) + packets.push_back(std::make_shared(makePacket(codec, row_num))); + return packets; +} + +std::vector makePacketQueues(int source_num, int queue_size) +{ + std::vector queues(source_num); + for (int i = 0; i < source_num; ++i) + queues[i] = std::make_shared(queue_size); + return queues; +} + +std::vector makeFields() +{ + std::vector fields(3); + fields[0].set_tp(TiDB::TypeLongLong); + fields[1].set_tp(TiDB::TypeString); + fields[2].set_tp(TiDB::TypeLongLong); + return fields; +} + +void printException(const Exception & e) +{ + std::string text = e.displayText(); + + auto embedded_stack_trace_pos = text.find("Stack trace"); + std::cerr << "Code: " << e.code() << ". " << text << std::endl + << std::endl; + if (std::string::npos == embedded_stack_trace_pos) + std::cerr << "Stack trace:" << std::endl + << e.getStackTrace().toString() << std::endl; +} + +void sendPacket(const std::vector & packets, const PacketQueuePtr & queue, StopFlag & stop_flag) +{ + std::mt19937 mt(rd()); + std::uniform_int_distribution dist(0, packets.size() - 1); + + while (!stop_flag.load()) + { + int i = dist(mt); + queue->tryPush(packets[i], std::chrono::milliseconds(10)); + } + queue->finish(); +} + +void receivePacket(const PacketQueuePtr & queue) +{ + while (true) + { + PacketPtr packet; + if (!queue->pop(packet)) + break; + } +} + +ReceiverHelper::ReceiverHelper(int concurrency_, int source_num_) + : concurrency(concurrency_) + , source_num(source_num_) +{ + pb_exchange_receiver.set_tp(tipb::Hash); + for (int i = 0; i < source_num; ++i) + { + mpp::TaskMeta task; + task.set_start_ts(0); + task.set_task_id(i); + task.set_partition_id(i); + task.set_address(""); + + String encoded_task; + task.SerializeToString(&encoded_task); + + pb_exchange_receiver.add_encoded_task_meta(encoded_task); + } + + fields = makeFields(); + *pb_exchange_receiver.add_field_types() = fields[0]; + *pb_exchange_receiver.add_field_types() = fields[1]; + *pb_exchange_receiver.add_field_types() = fields[2]; + + task_meta.set_task_id(100); + + queues = makePacketQueues(source_num, 10); +} + +MockExchangeReceiverPtr ReceiverHelper::buildReceiver() +{ + return std::make_shared( + std::make_shared(queues, fields), + source_num, + concurrency, + "mock_req_id", + "mock_exchange_receiver_id"); +} + +std::vector ReceiverHelper::buildExchangeReceiverStream() +{ + auto receiver = buildReceiver(); + std::vector streams(concurrency); + for (int i = 0; i < concurrency; ++i) + { + streams[i] = std::make_shared(receiver, "mock_req_id", "mock_executor_id" + std::to_string(i)); + } + return streams; +} + +BlockInputStreamPtr ReceiverHelper::buildUnionStream() +{ + auto streams = buildExchangeReceiverStream(); + return std::make_shared>(streams, nullptr, concurrency, /*req_id=*/""); +} + +void ReceiverHelper::finish() +{ + if (join_ptr) + { + join_ptr->setBuildTableState(Join::BuildTableState::SUCCEED); + std::cout << fmt::format("Hash table size: {} bytes", join_ptr->getTotalByteCount()) << std::endl; + } +} + +SenderHelper::SenderHelper( + int source_num_, + int concurrency_, + const std::vector & queues_, + const std::vector & fields) + : source_num(source_num_) + , concurrency(concurrency_) + , queues(queues_) +{ + mpp::TaskMeta task_meta; + tunnel_set = std::make_shared("mock_req_id"); + for (int i = 0; i < source_num; ++i) + { + auto writer = std::make_shared(queues[i]); + mock_writers.push_back(writer); + + auto tunnel = std::make_shared( + task_meta, + task_meta, + std::chrono::seconds(60), + concurrency, + false, + false, + "mock_req_id"); + tunnel->connect(writer.get()); + tunnels.push_back(tunnel); + MPPTaskId id(0, i); + tunnel_set->registerTunnel(id, tunnel); + } + + tipb::DAGRequest dag_request; + tipb::Executor root_executor; + root_executor.set_executor_id("ExchangeSender_100"); + *dag_request.mutable_root_executor() = root_executor; + + dag_context = std::make_unique(dag_request); + dag_context->is_mpp_task = true; + dag_context->is_root_mpp_task = false; + dag_context->encode_type = tipb::EncodeType::TypeCHBlock; + dag_context->result_field_types = fields; +} + +BlockInputStreamPtr SenderHelper::buildUnionStream( + StopFlag & stop_flag, + const std::vector & blocks) +{ + std::vector send_streams; + for (int i = 0; i < concurrency; ++i) + { + BlockInputStreamPtr stream = std::make_shared(blocks, stop_flag); + std::unique_ptr response_writer( + new StreamingDAGResponseWriter( + tunnel_set, + {0, 1, 2}, + TiDB::TiDBCollators(3), + tipb::Hash, + -1, + -1, + true, + *dag_context)); + send_streams.push_back(std::make_shared(stream, std::move(response_writer), /*req_id=*/"")); + } + + return std::make_shared>(send_streams, nullptr, concurrency, /*req_id=*/""); +} + +BlockInputStreamPtr SenderHelper::buildUnionStream(size_t total_rows, const std::vector & blocks) +{ + std::vector send_streams; + for (int i = 0; i < concurrency; ++i) + { + BlockInputStreamPtr stream = std::make_shared(total_rows / concurrency, blocks); + std::unique_ptr response_writer( + new StreamingDAGResponseWriter( + tunnel_set, + {0, 1, 2}, + TiDB::TiDBCollators(3), + tipb::Hash, + -1, + -1, + true, + *dag_context)); + send_streams.push_back(std::make_shared(stream, std::move(response_writer), /*req_id=*/"")); + } + + return std::make_shared>(send_streams, nullptr, concurrency, /*req_id=*/""); +} + +void SenderHelper::finish() +{ + for (size_t i = 0; i < tunnels.size(); ++i) + { + tunnels[i]->writeDone(); + tunnels[i]->waitForFinish(); + mock_writers[i]->finish(); + } +} + +void ExchangeBench::SetUp(const benchmark::State &) +{ + Poco::Logger::root().setLevel("error"); + + DynamicThreadPool::global_instance = std::make_unique( + /*fixed_thread_num=*/300, + std::chrono::milliseconds(100000)); + + input_blocks = makeBlocks(/*block_num=*/100, /*row_num=*/1024); + + try + { + DB::registerWindowFunctions(); + DB::registerFunctions(); + } + catch (DB::Exception &) + { + // Maybe another test has already registered, ignore exception here. + } +} + +void ExchangeBench::TearDown(const benchmark::State &) +{ + input_blocks.clear(); + // NOTE: Must reset here, otherwise DynamicThreadPool::fixedWork() may core because metrics already destroyed. + DynamicThreadPool::global_instance.reset(); +} + +void ExchangeBench::runAndWait(std::shared_ptr receiver_helper, + BlockInputStreamPtr receiver_stream, + std::shared_ptr & sender_helper, + BlockInputStreamPtr sender_stream) +{ + std::future sender_future = DynamicThreadPool::global_instance->schedule(/*memory_tracker=*/false, + [sender_stream, sender_helper] { + sender_stream->readPrefix(); + while (const auto & block = sender_stream->read()) {} + sender_stream->readSuffix(); + sender_helper->finish(); + }); + std::future receiver_future = DynamicThreadPool::global_instance->schedule(/*memory_tracker=*/false, + [receiver_stream, receiver_helper] { + receiver_stream->readPrefix(); + while (const auto & block = receiver_stream->read()) {} + receiver_stream->readSuffix(); + receiver_helper->finish(); + }); + sender_future.get(); + receiver_future.get(); +} + +BENCHMARK_DEFINE_F(ExchangeBench, basic_send_receive) +(benchmark::State & state) +try +{ + const int concurrency = state.range(0); + const int source_num = state.range(1); + const int total_rows = state.range(2); + Context context = TiFlashTestEnv::getContext(); + + for (auto _ : state) + { + std::shared_ptr receiver_helper = std::make_shared(concurrency, source_num); + BlockInputStreamPtr receiver_stream = receiver_helper->buildUnionStream(); + + std::shared_ptr sender_helper = std::make_shared(source_num, + concurrency, + receiver_helper->queues, + receiver_helper->fields); + BlockInputStreamPtr sender_stream = sender_helper->buildUnionStream(total_rows, input_blocks); + + runAndWait(receiver_helper, receiver_stream, sender_helper, sender_stream); + } +} +CATCH +BENCHMARK_REGISTER_F(ExchangeBench, basic_send_receive) + ->Args({8, 1, 1024 * 1000}); + +} // namespace tests +} // namespace DB diff --git a/dbms/src/Flash/tests/bench_exchange.h b/dbms/src/Flash/tests/bench_exchange.h new file mode 100644 index 00000000000..6b09e319613 --- /dev/null +++ b/dbms/src/Flash/tests/bench_exchange.h @@ -0,0 +1,291 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace DB +{ +namespace tests +{ + + +using Packet = mpp::MPPDataPacket; +using PacketPtr = std::shared_ptr; +using PacketQueue = MPMCQueue; +using PacketQueuePtr = std::shared_ptr; +using StopFlag = std::atomic; + +// NOLINTBEGIN(readability-convert-member-functions-to-static) +struct MockReceiverContext +{ + using Status = ::grpc::Status; + struct Request + { + String debugString() const + { + return "{Request}"; + } + + int source_index = 0; + int send_task_id = 0; + int recv_task_id = -1; + }; + + struct Reader + { + explicit Reader(const PacketQueuePtr & queue_) + : queue(queue_) + {} + + void initialize() const {} + + bool read(PacketPtr & packet [[maybe_unused]]) const + { + PacketPtr res; + if (queue->pop(res)) + { + *packet = *res; // avoid change shared packets + return true; + } + return false; + } + + Status finish() const + { + return ::grpc::Status(); + } + + PacketQueuePtr queue; + }; + + struct MockAsyncGrpcExchangePacketReader + { + // Not implement benchmark for Async GRPC for now. + void init(UnaryCallback *) { assert(0); } + void read(MPPDataPacketPtr &, UnaryCallback *) { assert(0); } + void finish(::grpc::Status &, UnaryCallback *) { assert(0); } + }; + + using AsyncReader = MockAsyncGrpcExchangePacketReader; + + MockReceiverContext( + const std::vector & queues_, + const std::vector & field_types_) + : queues(queues_) + , field_types(field_types_) + {} + + void fillSchema(DAGSchema & schema) const + { + schema.clear(); + for (size_t i = 0; i < field_types.size(); ++i) + { + String name = "exchange_receiver_" + std::to_string(i); + ColumnInfo info = TiDB::fieldTypeToColumnInfo(field_types[i]); + schema.emplace_back(std::move(name), std::move(info)); + } + } + + Request makeRequest(int index) const + { + return {index, index, -1}; + } + + std::shared_ptr makeReader(const Request & request) + { + return std::make_shared(queues[request.send_task_id]); + } + + static Status getStatusOK() + { + return ::grpc::Status(); + } + + bool supportAsync(const Request &) const { return false; } + void makeAsyncReader( + const Request &, + std::shared_ptr &, + UnaryCallback *) const {} + + std::vector queues; + std::vector field_types; +}; +// NOLINTEND(readability-convert-member-functions-to-static) + +using MockExchangeReceiver = ExchangeReceiverBase; +using MockExchangeReceiverPtr = std::shared_ptr; +using MockExchangeReceiverInputStream = TiRemoteBlockInputStream; + +struct MockWriter : public PacketWriter +{ + explicit MockWriter(PacketQueuePtr queue_) + : queue(std::move(queue_)) + {} + + bool write(const Packet & packet) override + { + queue->push(std::make_shared(packet)); + return true; + } + + void finish() + { + queue->finish(); + } + + PacketQueuePtr queue; +}; + +using MockWriterPtr = std::shared_ptr; +using MockTunnel = MPPTunnelBase; +using MockTunnelPtr = std::shared_ptr; +using MockTunnelSet = MPPTunnelSetBase; +using MockTunnelSetPtr = std::shared_ptr; + +struct MockBlockInputStream : public IProfilingBlockInputStream +{ + const std::vector & blocks; + Block header; + std::mt19937 mt; + std::uniform_int_distribution dist; + StopFlag & stop_flag; + + MockBlockInputStream(const std::vector & blocks_, StopFlag & stop_flag_); + + String getName() const override { return "MockBlockInputStream"; } + Block getHeader() const override { return header; } + + Block readImpl() override + { + if (stop_flag.load(std::memory_order_relaxed)) + return Block{}; + return blocks[dist(mt)]; + } +}; + +// Similar to MockBlockInputStream, but return fixed count of rows. +struct MockFixedRowsBlockInputStream : public IProfilingBlockInputStream +{ + Block header; + std::mt19937 mt; + std::uniform_int_distribution dist; + size_t current_rows; + size_t total_rows; + const std::vector & blocks; + + MockFixedRowsBlockInputStream(size_t total_rows_, const std::vector & blocks_); + + String getName() const override { return "MockBlockInputStream"; } + Block getHeader() const override { return header; } + + Block readImpl() override + { + if (current_rows >= total_rows) + return Block{}; + Block res = blocks[dist(mt)]; + current_rows += res.rows(); + return res; + } +}; + +Block makeBlock(int row_num); +std::vector makeBlocks(int block_num, int row_num); +mpp::MPPDataPacket makePacket(ChunkCodecStream & codec, int row_num); +std::vector makePackets(ChunkCodecStream & codec, int packet_num, int row_num); +std::vector makePacketQueues(int source_num, int queue_size); +std::vector makeFields(); +void printException(const Exception & e); +void sendPacket(const std::vector & packets, const PacketQueuePtr & queue, StopFlag & stop_flag); +void receivePacket(const PacketQueuePtr & queue); + +struct ReceiverHelper +{ + const int concurrency; + const int source_num; + tipb::ExchangeReceiver pb_exchange_receiver; + std::vector fields; + mpp::TaskMeta task_meta; + std::vector queues; + std::shared_ptr join_ptr; + + explicit ReceiverHelper(int concurrency_, int source_num_); + MockExchangeReceiverPtr buildReceiver(); + std::vector buildExchangeReceiverStream(); + BlockInputStreamPtr buildUnionStream(); + BlockInputStreamPtr buildUnionStreamWithHashJoinBuildStream(); + void finish(); +}; + +struct SenderHelper +{ + const int source_num; + const int concurrency; + + std::vector queues; + std::vector mock_writers; + std::vector tunnels; + MockTunnelSetPtr tunnel_set; + std::unique_ptr dag_context; + + SenderHelper( + int source_num_, + int concurrency_, + const std::vector & queues_, + const std::vector & fields); + + // Using MockBlockInputStream to build streams. + BlockInputStreamPtr buildUnionStream(StopFlag & stop_flag, const std::vector & blocks); + // Using MockFixedRowsBlockInputStream to build streams. + BlockInputStreamPtr buildUnionStream(size_t total_rows, const std::vector & blocks); + + void finish(); +}; + +class ExchangeBench : public benchmark::Fixture +{ +public: + void SetUp(const benchmark::State &) override; + void TearDown(const benchmark::State &) override; + void runAndWait(std::shared_ptr receiver_helper, + BlockInputStreamPtr receiver_stream, + std::shared_ptr & sender_helper, + BlockInputStreamPtr sender_stream); + + std::vector input_blocks; +}; + + +} // namespace tests +} // namespace DB diff --git a/dbms/src/Flash/tests/bench_window.cpp b/dbms/src/Flash/tests/bench_window.cpp new file mode 100644 index 00000000000..da9df20fdf3 --- /dev/null +++ b/dbms/src/Flash/tests/bench_window.cpp @@ -0,0 +1,107 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include + +namespace DB +{ +namespace tests +{ +class WindowFunctionBench : public ExchangeBench +{ +public: + void SetUp(const benchmark::State & state) override + { + // build tipb::Window and tipb::Sort. + // select row_number() over w1 from t1 window w1 as (partition by c1, c2, c3 order by c1, c2, c3); + ExchangeBench::SetUp(state); + MockColumnInfos columns{ + {"c1", TiDB::TP::TypeLongLong}, + {"c2", TiDB::TP::TypeString}, + {"c3", TiDB::TP::TypeLongLong}, + }; + size_t executor_index = 0; + DAGRequestBuilder builder(executor_index); + builder + .mockTable("test", "t1", columns) + .sort({{"c1", false}, {"c2", false}, {"c3", false}}, true) + .window(RowNumber(), + {{"c1", false}, {"c2", false}, {"c3", false}}, + {{"c1", false}, {"c2", false}, {"c3", false}}, + buildDefaultRowsFrame()); + tipb::DAGRequest req; + MPPInfo mpp_info(0, -1, -1, {}, std::unordered_map>{}); + builder.getRoot()->toTiPBExecutor(req.mutable_root_executor(), /*collator_id=*/0, mpp_info, TiFlashTestEnv::getContext()); + assert(req.root_executor().tp() == tipb::TypeWindow); + window = req.root_executor().window(); + assert(window.child().tp() == tipb::TypeSort); + sort = window.child().sort(); + } + + void prepareWindowStream(Context & context, int concurrency, int source_num, int total_rows, const std::vector & blocks, BlockInputStreamPtr & sender_stream, BlockInputStreamPtr & receiver_stream, std::shared_ptr & sender_helper, std::shared_ptr & receiver_helper) const + { + DAGPipeline pipeline; + receiver_helper = std::make_shared(concurrency, source_num); + pipeline.streams = receiver_helper->buildExchangeReceiverStream(); + + sender_helper = std::make_shared(source_num, concurrency, receiver_helper->queues, receiver_helper->fields); + sender_stream = sender_helper->buildUnionStream(total_rows, blocks); + + context.setDAGContext(sender_helper->dag_context.get()); + std::vector source_columns{ + NameAndTypePair("c1", makeNullable(std::make_shared())), + NameAndTypePair("c2", makeNullable(std::make_shared())), + NameAndTypePair("c3", makeNullable(std::make_shared()))}; + auto mock_interpreter = mockInterpreter(context, source_columns, concurrency); + mock_interpreter->input_streams_vec.push_back(pipeline.streams); + mockExecuteWindowOrder(mock_interpreter, pipeline, sort); + mockExecuteWindow(mock_interpreter, pipeline, window); + pipeline.transform([&](auto & stream) { + stream = std::make_shared(stream, 8192, 0, "mock_executor_id_squashing"); + }); + receiver_stream = std::make_shared>(pipeline.streams, nullptr, concurrency, /*req_id=*/""); + } + + tipb::Window window; + tipb::Sort sort; +}; + +BENCHMARK_DEFINE_F(WindowFunctionBench, basic_row_number) +(benchmark::State & state) +try +{ + const int concurrency = state.range(0); + const int source_num = state.range(1); + const int total_rows = state.range(2); + Context context = TiFlashTestEnv::getContext(); + + for (auto _ : state) + { + std::shared_ptr sender_helper; + std::shared_ptr receiver_helper; + BlockInputStreamPtr sender_stream; + BlockInputStreamPtr receiver_stream; + + prepareWindowStream(context, concurrency, source_num, total_rows, input_blocks, sender_stream, receiver_stream, sender_helper, receiver_helper); + + runAndWait(receiver_helper, receiver_stream, sender_helper, sender_stream); + } +} +CATCH +BENCHMARK_REGISTER_F(WindowFunctionBench, basic_row_number) + ->Args({8, 1, 1024 * 1000}); + +} // namespace tests +} // namespace DB diff --git a/dbms/src/Flash/tests/exchange_perftest.cpp b/dbms/src/Flash/tests/exchange_perftest.cpp deleted file mode 100644 index c2e047bec62..00000000000 --- a/dbms/src/Flash/tests/exchange_perftest.cpp +++ /dev/null @@ -1,699 +0,0 @@ -// Copyright 2022 PingCAP, Ltd. -// -// 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. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include // to include the implementation of StreamingDAGResponseWriter -#include // to include the implementation of ExchangeReceiver -#include // to include the implementation of MPPTunnel -#include // to include the implementation of MPPTunnelSet -#include -#include -#include - -namespace DB::tests -{ -namespace -{ -std::random_device rd; - -using Packet = mpp::MPPDataPacket; -using PacketPtr = std::shared_ptr; -using PacketQueue = MPMCQueue; -using PacketQueuePtr = std::shared_ptr; -using StopFlag = std::atomic; - -std::atomic received_data_size{0}; - -struct MockReceiverContext -{ - struct Status - { - int status_code = 0; - String error_msg; - - bool ok() const - { - return status_code == 0; - } - - const String & error_message() const - { - return error_msg; - } - - int error_code() const - { - return status_code; - } - }; - - struct Request - { - String debugString() const - { - return "{Request}"; - } - - int source_index = 0; - int send_task_id = 0; - int recv_task_id = -1; - }; - - struct Reader - { - explicit Reader(const PacketQueuePtr & queue_) - : queue(queue_) - {} - - void initialize() const - { - } - - bool read(PacketPtr & packet [[maybe_unused]]) const - { - PacketPtr res; - if (queue->pop(res)) - { - received_data_size.fetch_add(res->ByteSizeLong()); - *packet = *res; // avoid change shared packets - return true; - } - return false; - } - - Status finish() const - { - return {0, ""}; - } - - PacketQueuePtr queue; - }; - - MockReceiverContext( - const std::vector & queues_, - const std::vector & field_types_) - : queues(queues_) - , field_types(field_types_) - { - } - - void fillSchema(DAGSchema & schema) const - { - schema.clear(); - for (size_t i = 0; i < field_types.size(); ++i) - { - String name = "exchange_receiver_" + std::to_string(i); - ColumnInfo info = TiDB::fieldTypeToColumnInfo(field_types[i]); - schema.emplace_back(std::move(name), std::move(info)); - } - } - - Request makeRequest(int index) const - { - return {index, index, -1}; - } - - std::shared_ptr makeReader(const Request & request) - { - return std::make_shared(queues[request.send_task_id]); - } - - static Status getStatusOK() - { - return {0, ""}; - } - - std::vector queues; - std::vector field_types; -}; - -using MockExchangeReceiver = ExchangeReceiverBase; -using MockExchangeReceiverPtr = std::shared_ptr; -using MockExchangeReceiverInputStream = TiRemoteBlockInputStream; - -struct MockWriter -{ - explicit MockWriter(PacketQueuePtr queue_) - : queue(std::move(queue_)) - {} - - bool Write(const Packet & packet) - { - queue->push(std::make_shared(packet)); - return true; - } - - void finish() - { - queue->finish(); - } - - PacketQueuePtr queue; -}; - -using MockWriterPtr = std::shared_ptr; -using MockTunnel = MPPTunnelBase; -using MockTunnelPtr = std::shared_ptr; -using MockTunnelSet = MPPTunnelSetBase; -using MockTunnelSetPtr = std::shared_ptr; - -struct MockBlockInputStream : public IProfilingBlockInputStream -{ - const std::vector & blocks; - Block header; - std::mt19937 mt; - std::uniform_int_distribution dist; - StopFlag & stop_flag; - - MockBlockInputStream(const std::vector & blocks_, StopFlag & stop_flag_) - : blocks(blocks_) - , header(blocks[0].cloneEmpty()) - , mt(rd()) - , dist(0, blocks.size() - 1) - , stop_flag(stop_flag_) - {} - - String getName() const override { return "MockBlockInputStream"; } - Block getHeader() const override { return header; } - - Block readImpl() override - { - if (stop_flag.load(std::memory_order_relaxed)) - return Block{}; - return blocks[dist(mt)]; - } -}; - -Block makeBlock(int row_num) -{ - std::mt19937 mt(rd()); - std::uniform_int_distribution int64_dist; - std::uniform_int_distribution len_dist(10, 20); - std::uniform_int_distribution char_dist; - - InferredDataVector> int64_vec; - InferredDataVector> int64_vec2; - for (int i = 0; i < row_num; ++i) - { - int64_vec.emplace_back(int64_dist(mt)); - int64_vec2.emplace_back(int64_dist(mt)); - } - - InferredDataVector> string_vec; - for (int i = 0; i < row_num; ++i) - { - int len = len_dist(mt); - String s; - for (int j = 0; j < len; ++j) - s.push_back(char_dist(mt)); - string_vec.push_back(std::move(s)); - } - - auto int64_data_type = makeDataType>(); - ColumnWithTypeAndName int64_column(makeColumn>(int64_data_type, int64_vec), int64_data_type, "int64_1"); - ColumnWithTypeAndName int64_column2(makeColumn>(int64_data_type, int64_vec2), int64_data_type, "int64_2"); - - auto string_data_type = makeDataType>(); - ColumnWithTypeAndName string_column(makeColumn>(string_data_type, string_vec), string_data_type, "string"); - - return Block({int64_column, string_column, int64_column2}); -} - -std::vector makeBlocks(int block_num, int row_num) -{ - std::vector blocks; - for (int i = 0; i < block_num; ++i) - blocks.push_back(makeBlock(row_num)); - return blocks; -} - -mpp::MPPDataPacket makePacket(ChunkCodecStream & codec, int row_num) -{ - auto block = makeBlock(row_num); - codec.encode(block, 0, row_num); - - mpp::MPPDataPacket packet; - packet.add_chunks(codec.getString()); - codec.clear(); - - return packet; -} - -std::vector makePackets(ChunkCodecStream & codec, int packet_num, int row_num) -{ - std::vector packets; - for (int i = 0; i < packet_num; ++i) - packets.push_back(std::make_shared(makePacket(codec, row_num))); - return packets; -} - -std::vector makePacketQueues(int source_num, int queue_size) -{ - std::vector queues; - for (int i = 0; i < source_num; ++i) - queues.push_back(std::make_shared(queue_size)); - return queues; -} - -std::vector makeFields() -{ - std::vector fields(3); - fields[0].set_tp(TiDB::TypeLongLong); - fields[1].set_tp(TiDB::TypeString); - fields[2].set_tp(TiDB::TypeLongLong); - return fields; -} - -void printException(const Exception & e) -{ - std::string text = e.displayText(); - - auto embedded_stack_trace_pos = text.find("Stack trace"); - std::cerr << "Code: " << e.code() << ". " << text << std::endl - << std::endl; - if (std::string::npos == embedded_stack_trace_pos) - std::cerr << "Stack trace:" << std::endl - << e.getStackTrace().toString() << std::endl; -} - -void sendPacket(const std::vector & packets, const PacketQueuePtr & queue, StopFlag & stop_flag) -{ - std::mt19937 mt(rd()); - std::uniform_int_distribution dist(0, packets.size() - 1); - - while (!stop_flag.load()) - { - int i = dist(mt); - queue->tryPush(packets[i], std::chrono::milliseconds(10)); - } - queue->finish(); -} - -void receivePacket(const PacketQueuePtr & queue) -{ - while (true) - { - PacketPtr packet; - if (queue->pop(packet)) - received_data_size.fetch_add(packet->ByteSizeLong()); - else - break; - } -} - -template -void readBlock(BlockInputStreamPtr stream) -{ - [[maybe_unused]] auto get_rate = [](auto count, auto duration) { - return count * 1000 / duration.count(); - }; - - [[maybe_unused]] auto get_mib = [](auto v) { - return v / 1024 / 1024; - }; - - [[maybe_unused]] auto start = std::chrono::high_resolution_clock::now(); - [[maybe_unused]] auto second_ago = start; - [[maybe_unused]] Int64 block_count = 0; - [[maybe_unused]] Int64 last_block_count = 0; - [[maybe_unused]] Int64 last_data_size = received_data_size.load(); - try - { - stream->readPrefix(); - while (auto block = stream->read()) - { - if constexpr (print_progress) - { - ++block_count; - auto cur = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(cur - second_ago); - if (duration.count() >= 1000) - { - Int64 data_size = received_data_size.load(); - std::cout - << fmt::format( - "Blocks: {:<10} Data(MiB): {:<8} Block/s: {:<6} Data/s(MiB): {:<6}", - block_count, - get_mib(data_size), - get_rate(block_count - last_block_count, duration), - get_mib(get_rate(data_size - last_data_size, duration))) - << std::endl; - second_ago = cur; - last_block_count = block_count; - last_data_size = data_size; - } - } - } - stream->readSuffix(); - - if constexpr (print_progress) - { - auto cur = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(cur - start); - Int64 data_size = received_data_size.load(); - std::cout - << fmt::format( - "End. Blocks: {:<10} Data(MiB): {:<8} Block/s: {:<6} Data/s(MiB): {:<6}", - block_count, - get_mib(data_size), - get_rate(block_count, duration), - get_mib(get_rate(data_size, duration))) - << std::endl; - } - } - catch (const Exception & e) - { - printException(e); - throw; - } -} - -struct ReceiverHelper -{ - const int source_num; - tipb::ExchangeReceiver pb_exchange_receiver; - std::vector fields; - mpp::TaskMeta task_meta; - std::vector queues; - std::shared_ptr join_ptr; - - explicit ReceiverHelper(int source_num_) - : source_num(source_num_) - { - pb_exchange_receiver.set_tp(tipb::Hash); - for (int i = 0; i < source_num; ++i) - { - mpp::TaskMeta task; - task.set_start_ts(0); - task.set_task_id(i); - task.set_partition_id(i); - task.set_address(""); - - String encoded_task; - task.SerializeToString(&encoded_task); - - pb_exchange_receiver.add_encoded_task_meta(encoded_task); - } - - fields = makeFields(); - *pb_exchange_receiver.add_field_types() = fields[0]; - *pb_exchange_receiver.add_field_types() = fields[1]; - *pb_exchange_receiver.add_field_types() = fields[2]; - - task_meta.set_task_id(100); - - queues = makePacketQueues(source_num, 10); - } - - MockExchangeReceiverPtr buildReceiver() - { - return std::make_shared( - std::make_shared(queues, fields), - source_num, - source_num * 5, - nullptr); - } - - BlockInputStreamPtr buildUnionStream(int concurrency) - { - auto receiver = buildReceiver(); - std::vector streams; - for (int i = 0; i < concurrency; ++i) - streams.push_back(std::make_shared(receiver, nullptr)); - return std::make_shared>(streams, nullptr, concurrency, /*req_id=*/""); - } - - BlockInputStreamPtr buildUnionStreamWithHashJoinBuildStream(int concurrency) - { - auto receiver = buildReceiver(); - std::vector streams; - for (int i = 0; i < concurrency; ++i) - streams.push_back(std::make_shared(receiver, nullptr)); - - auto receiver_header = streams.front()->getHeader(); - auto key_name = receiver_header.getByPosition(0).name; - - join_ptr = std::make_shared( - Names{key_name}, - Names{key_name}, - true, - SizeLimits(0, 0, OverflowMode::THROW), - ASTTableJoin::Kind::Inner, - ASTTableJoin::Strictness::All, - /*req_id=*/"", - TiDB::TiDBCollators{nullptr}, - "", - "", - "", - "", - nullptr, - 65536); - - join_ptr->init(receiver_header, concurrency); - - for (int i = 0; i < concurrency; ++i) - streams[i] = std::make_shared(streams[i], join_ptr, i, /*req_id=*/""); - - return std::make_shared>(streams, nullptr, concurrency, /*req_id=*/""); - } - - void finish() - { - if (join_ptr) - { - join_ptr->setBuildTableState(Join::BuildTableState::SUCCEED); - std::cout << fmt::format("Hash table size: {} bytes", join_ptr->getTotalByteCount()) << std::endl; - } - } -}; - -struct SenderHelper -{ - const int source_num; - const int concurrency; - - std::vector queues; - std::vector mock_writers; - std::vector tunnels; - MockTunnelSetPtr tunnel_set; - std::unique_ptr dag_context; - - SenderHelper( - int source_num_, - int concurrency_, - const std::vector & queues_, - const std::vector & fields) - : source_num(source_num_) - , concurrency(concurrency_) - , queues(queues_) - { - mpp::TaskMeta task_meta; - tunnel_set = std::make_shared(); - for (int i = 0; i < source_num; ++i) - { - auto writer = std::make_shared(queues[i]); - mock_writers.push_back(writer); - - auto tunnel = std::make_shared( - task_meta, - task_meta, - std::chrono::seconds(60), - concurrency, - false); - tunnel->connect(writer.get()); - tunnels.push_back(tunnel); - tunnel_set->addTunnel(tunnel); - } - - tipb::DAGRequest dag_request; - tipb::Executor root_executor; - root_executor.set_executor_id("ExchangeSender_100"); - *dag_request.mutable_root_executor() = root_executor; - - dag_context = std::make_unique(dag_request); - dag_context->is_mpp_task = true; - dag_context->is_root_mpp_task = false; - dag_context->encode_type = tipb::EncodeType::TypeCHBlock; - dag_context->result_field_types = fields; - } - - BlockInputStreamPtr buildUnionStream( - StopFlag & stop_flag, - const std::vector & blocks) - { - std::vector send_streams; - for (int i = 0; i < concurrency; ++i) - { - BlockInputStreamPtr stream = std::make_shared(blocks, stop_flag); - std::unique_ptr response_writer( - new StreamingDAGResponseWriter( - tunnel_set, - {0, 1, 2}, - TiDB::TiDBCollators(3), - tipb::Hash, - -1, - -1, - true, - *dag_context)); - send_streams.push_back(std::make_shared(stream, std::move(response_writer), /*req_id=*/"")); - } - - return std::make_shared>(send_streams, nullptr, concurrency, /*req_id=*/""); - } - - void finish() - { - for (size_t i = 0; i < tunnels.size(); ++i) - { - tunnels[i]->writeDone(); - tunnels[i]->waitForFinish(); - mock_writers[i]->finish(); - } - } -}; - -void testOnlyReceiver(int concurrency, int source_num, int block_rows, int seconds) -{ - ReceiverHelper receiver_helper(source_num); - auto union_input_stream = receiver_helper.buildUnionStream(concurrency); - - auto chunk_codec_stream = CHBlockChunkCodec().newCodecStream(receiver_helper.fields); - auto packets = makePackets(*chunk_codec_stream, 100, block_rows); - - StopFlag stop_flag(false); - - std::vector threads; - for (const auto & queue : receiver_helper.queues) - threads.emplace_back(sendPacket, std::cref(packets), queue, std::ref(stop_flag)); - threads.emplace_back(readBlock, union_input_stream); - - std::this_thread::sleep_for(std::chrono::seconds(seconds)); - stop_flag.store(true); - for (auto & thread : threads) - thread.join(); - - receiver_helper.finish(); -} - -template -void testSenderReceiver(int concurrency, int source_num, int block_rows, int seconds) -{ - ReceiverHelper receiver_helper(source_num); - BlockInputStreamPtr union_receive_stream; - if constexpr (with_join) - union_receive_stream = receiver_helper.buildUnionStreamWithHashJoinBuildStream(concurrency); - else - union_receive_stream = receiver_helper.buildUnionStream(concurrency); - - StopFlag stop_flag(false); - auto blocks = makeBlocks(100, block_rows); - - SenderHelper sender_helper(source_num, concurrency, receiver_helper.queues, receiver_helper.fields); - auto union_send_stream = sender_helper.buildUnionStream(stop_flag, blocks); - - auto write_thread = std::thread(readBlock, union_send_stream); - auto read_thread = std::thread(readBlock, union_receive_stream); - - std::this_thread::sleep_for(std::chrono::seconds(seconds)); - stop_flag.store(true); - - write_thread.join(); - sender_helper.finish(); - - read_thread.join(); - receiver_helper.finish(); -} - -void testOnlySender(int concurrency, int source_num, int block_rows, int seconds) -{ - auto queues = makePacketQueues(source_num, 10); - auto fields = makeFields(); - - StopFlag stop_flag(false); - auto blocks = makeBlocks(100, block_rows); - - SenderHelper sender_helper(source_num, concurrency, queues, fields); - auto union_send_stream = sender_helper.buildUnionStream(stop_flag, blocks); - - auto write_thread = std::thread(readBlock, union_send_stream); - std::vector read_threads; - for (int i = 0; i < source_num; ++i) - read_threads.emplace_back(receivePacket, queues[i]); - - std::this_thread::sleep_for(std::chrono::seconds(seconds)); - stop_flag.store(true); - - write_thread.join(); - sender_helper.finish(); - - for (auto & t : read_threads) - t.join(); -} - -} // namespace -} // namespace DB::tests - -int main(int argc [[maybe_unused]], char ** argv [[maybe_unused]]) -{ - if (argc < 2 || argc > 6) - { - std::cerr << fmt::format("Usage: {} [receiver|sender|sender_receiver|sender_receiver_join] ", argv[0]) << std::endl; - exit(1); - } - - String method = argv[1]; - int concurrency = argc >= 3 ? atoi(argv[2]) : 5; - int source_num = argc >= 4 ? atoi(argv[3]) : 2; - int block_rows = argc >= 5 ? atoi(argv[4]) : 5000; - int seconds = argc >= 6 ? atoi(argv[5]) : 10; - - using TestHandler = std::function; - std::unordered_map handlers = { - {"receiver", DB::tests::testOnlyReceiver}, - {"sender", DB::tests::testOnlySender}, - {"sender_receiver", DB::tests::testSenderReceiver}, - {"sender_receiver_join", DB::tests::testSenderReceiver}, - }; - - auto it = handlers.find(method); - if (it != handlers.end()) - { - std::cout - << fmt::format( - "{}. concurrency = {}. source_num = {}. block_rows = {}. seconds = {}", - method, - concurrency, - source_num, - block_rows, - seconds) - << std::endl; - it->second(concurrency, source_num, block_rows, seconds); - } - else - { - std::cerr << "Unknown method: " << method << std::endl; - exit(1); - } -} diff --git a/dbms/src/TestUtils/mockExecutor.cpp b/dbms/src/TestUtils/mockExecutor.cpp index e1ccbdbb010..2cf8a939b58 100644 --- a/dbms/src/TestUtils/mockExecutor.cpp +++ b/dbms/src/TestUtils/mockExecutor.cpp @@ -440,4 +440,5 @@ DAGRequestBuilder MockDAGRequestContext::receive(String exchange_name) } return builder; } -} // namespace DB::tests \ No newline at end of file + +} // namespace DB::tests diff --git a/dbms/src/TestUtils/mockExecutor.h b/dbms/src/TestUtils/mockExecutor.h index d52b5ec674a..c11635ac93e 100644 --- a/dbms/src/TestUtils/mockExecutor.h +++ b/dbms/src/TestUtils/mockExecutor.h @@ -188,4 +188,4 @@ MockWindowFrame buildDefaultRowsFrame(); #define Rank() makeASTFunction("Rank") #define DenseRank() makeASTFunction("DenseRank") -} // namespace DB::tests \ No newline at end of file +} // namespace DB::tests diff --git a/libs/libcommon/include/common/types.h b/libs/libcommon/include/common/types.h index 139fc10e980..87c7215d91f 100644 --- a/libs/libcommon/include/common/types.h +++ b/libs/libcommon/include/common/types.h @@ -25,6 +25,7 @@ #if defined(__clang__) #pragma GCC diagnostic ignored "-Wunknown-warning-option" #pragma GCC diagnostic ignored "-Wdeprecated-copy" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" #endif #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" From a9a32b9c751fbc6ebb2aeb6bc5f8254d04c6ff73 Mon Sep 17 00:00:00 2001 From: hongyunyan <649330952@qq.com> Date: Fri, 17 Jun 2022 08:26:35 +0800 Subject: [PATCH 110/127] Fix the rename_table_across_databases.test to make it can run successfully multiple times. (#5157) close pingcap/tiflash#5145 --- tests/fullstack-test2/ddl/rename_table_across_databases.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fullstack-test2/ddl/rename_table_across_databases.test b/tests/fullstack-test2/ddl/rename_table_across_databases.test index c78c27138a0..bc27668bd0c 100644 --- a/tests/fullstack-test2/ddl/rename_table_across_databases.test +++ b/tests/fullstack-test2/ddl/rename_table_across_databases.test @@ -52,7 +52,7 @@ mysql> set session tidb_isolation_read_engines='tiflash'; select * from test_new +------+------+ # check if table info updated. ->> select tidb_database,tidb_name from system.tables where is_tombstone = 0 and (tidb_database = 'test' and tidb_name='t') or (tidb_database='test_new' and tidb_name='t2') +>> select tidb_database,tidb_name from system.tables where is_tombstone = 0 and ((tidb_database = 'test' and tidb_name='t') or (tidb_database='test_new' and tidb_name='t2')) ┌─tidb_database─┬─tidb_name─┐ │ test_new │ t2 │ └───────────────┴───────────┘ From ecd615f205fc6b137d25168a8757b41228dd5efa Mon Sep 17 00:00:00 2001 From: jiaqizho Date: Fri, 17 Jun 2022 10:44:35 +0800 Subject: [PATCH 111/127] Add more test in mix mode (#5017) ref pingcap/tiflash#3594 --- .../V3/tests/gtest_page_storage_mix_mode.cpp | 301 +++++++++++++++++- 1 file changed, 298 insertions(+), 3 deletions(-) diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp index 078daa3e5b4..74e56c929d8 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage_mix_mode.cpp @@ -85,6 +85,16 @@ class PageStorageMixedTest : public DB::base::TiFlashStorageTestBasic return run_mode; } + PageReader newMixedPageReader(PageStorage::SnapshotPtr & snapshot) + { + return storage_pool_mix->newLogReader(nullptr, snapshot); + } + + PageReader newMixedPageReader() + { + return storage_pool_mix->newLogReader(nullptr, true, "PageStorageMixedTest"); + } + void reloadV2StoragePool() { db_context->setPageStorageRunMode(PageStorageRunMode::ONLY_V2); @@ -1035,7 +1045,7 @@ try // Thread A create snapshot for read auto snapshot_mix_before_merge_delta = page_reader_mix->getSnapshot("ReadWithSnapshotAfterMergeDelta"); { - auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_mix_before_merge_delta); + auto page_reader_mix_with_snap = newMixedPageReader(snapshot_mix_before_merge_delta); const auto & page1 = page_reader_mix_with_snap.read(1); const auto & page2 = page_reader_mix_with_snap.read(2); const auto & page3 = page_reader_mix_with_snap.read(3); @@ -1044,7 +1054,7 @@ try ASSERT_PAGE_EQ(c_buff2, buf_sz2, page3, 3); } { - auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, true, "ReadWithSnapshotAfterMergeDelta"); + auto page_reader_mix_with_snap = newMixedPageReader(); const auto & page1 = page_reader_mix_with_snap.read(1); const auto & page2 = page_reader_mix_with_snap.read(2); const auto & page3 = page_reader_mix_with_snap.read(3); @@ -1063,7 +1073,7 @@ try } // Thread A continue to read 1, 3 { - auto page_reader_mix_with_snap = storage_pool_mix->newLogReader(nullptr, snapshot_mix_before_merge_delta); + auto page_reader_mix_with_snap = newMixedPageReader(snapshot_mix_before_merge_delta); // read 1, 3 with snapshot, should be success const auto & page1 = page_reader_mix_with_snap.read(1); const auto & page3 = page_reader_mix_with_snap.read(3); @@ -1071,6 +1081,7 @@ try ASSERT_PAGE_EQ(c_buff2, buf_sz2, page3, 3); ASSERT_THROW(page_reader_mix_with_snap.read(4), DB::Exception); } + { // Revert v3 WriteBatch batch; @@ -1081,6 +1092,290 @@ try } CATCH +TEST_F(PageStorageMixedTest, refWithSnapshot2) +try +{ + UInt64 tag = 0; + const size_t buf_sz = 1024; + char c_buff[buf_sz]; + for (size_t i = 0; i < buf_sz; ++i) + { + c_buff[i] = i % 0xff; + } + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, tag, buff, buf_sz); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.putRefPage(2, 1); + page_writer_v2->write(std::move(batch), nullptr); + } + + // Change to mix mode here + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + + auto snapshot_mix = page_reader_mix->getSnapshot(""); + { + WriteBatch batch; + batch.delPage(1); + batch.delPage(2); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + auto page_maps = newMixedPageReader(snapshot_mix).read({1, 2}); + ASSERT_EQ(page_maps.size(), 2); + + ASSERT_PAGE_EQ(c_buff, buf_sz, page_maps[1], 1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page_maps[2], 2); + } +} +CATCH + +TEST_F(PageStorageMixedTest, refWithSnapshot3) +try +{ + UInt64 tag = 0; + const size_t buf_sz = 1024; + char c_buff[buf_sz]; + for (size_t i = 0; i < buf_sz; ++i) + { + c_buff[i] = i % 0xff; + } + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, tag, buff, buf_sz); + // to keep mix mode + batch.putExternal(10, 1); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.putRefPage(2, 1); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.delPage(1); + batch.delPage(2); + page_writer_v2->write(std::move(batch), nullptr); + } + + // Change to mix mode here + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, tag, buff, buf_sz); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.putRefPage(2, 1); + page_writer_mix->write(std::move(batch), nullptr); + } + + auto snapshot_mix = page_reader_mix->getSnapshot(""); + { + WriteBatch batch; + batch.delPage(1); + batch.delPage(2); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + auto page_maps = newMixedPageReader(snapshot_mix).read({1, 2}); + ASSERT_EQ(page_maps.size(), 2); + + ASSERT_PAGE_EQ(c_buff, buf_sz, page_maps[1], 1); + ASSERT_PAGE_EQ(c_buff, buf_sz, page_maps[2], 2); + } +} +CATCH + +TEST_F(PageStorageMixedTest, refWithSnapshot4) +try +{ + UInt64 tag = 0; + const size_t buf_sz = 1024; + char c_buff[buf_sz]; + for (size_t i = 0; i < buf_sz; ++i) + { + c_buff[i] = i % 0xff; + } + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, tag, buff, buf_sz); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.putRefPage(2, 1); + page_writer_v2->write(std::move(batch), nullptr); + } + + // Change to mix mode here + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + + { + WriteBatch batch; + batch.delPage(2); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + auto page1 = page_reader_mix->read(1); + + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 1); + } +} +CATCH + +TEST_F(PageStorageMixedTest, refWithSnapshot5) +try +{ + UInt64 tag = 0; + const size_t buf_sz = 1024; + char c_buff[buf_sz]; + for (size_t i = 0; i < buf_sz; ++i) + { + c_buff[i] = i % 0xff; + } + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, tag, buff, buf_sz); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.putRefPage(2, 1); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.delPage(1); + page_writer_v2->write(std::move(batch), nullptr); + } + + // Change to mix mode here + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + + { + auto page1 = page_reader_mix->read(2); + + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 2); + } +} +CATCH + +TEST_F(PageStorageMixedTest, refWithSnapshot6) +try +{ + UInt64 tag = 0; + const size_t buf_sz = 1024; + char c_buff[buf_sz]; + for (size_t i = 0; i < buf_sz; ++i) + { + c_buff[i] = i % 0xff; + } + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff, sizeof(c_buff)); + batch.putPage(1, tag, buff, buf_sz); + page_writer_v2->write(std::move(batch), nullptr); + } + + { + WriteBatch batch; + batch.putRefPage(2, 1); + page_writer_v2->write(std::move(batch), nullptr); + } + + // Change to mix mode here + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + + { + WriteBatch batch; + batch.delPage(1); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + auto page1 = page_reader_mix->read(2); + + ASSERT_PAGE_EQ(c_buff, buf_sz, page1, 2); + } +} +CATCH + +TEST_F(PageStorageMixedTest, ReadWithSnapshot2) +try +{ + UInt64 tag = 0; + const size_t buf_sz = 1; + char c_buff1[buf_sz]; + c_buff1[0] = 1; + + char c_buff2[buf_sz]; + c_buff2[0] = 2; + + { + WriteBatch batch; + ReadBufferPtr buff = std::make_shared(c_buff1, buf_sz); + batch.putPage(1, tag, buff, buf_sz); + page_writer_v2->write(std::move(batch), nullptr); + } + + // Change to mix mode here + ASSERT_EQ(reloadMixedStoragePool(), PageStorageRunMode::MIX_MODE); + + auto snapshot_mix = page_reader_mix->getSnapshot(""); + { + WriteBatch batch; + batch.delPage(1); + ReadBufferPtr buff = std::make_shared(c_buff2, buf_sz); + batch.putPage(1, tag, buff, buf_sz); + page_writer_mix->write(std::move(batch), nullptr); + } + + { + auto page1 = newMixedPageReader(snapshot_mix).read(1); + ASSERT_PAGE_EQ(c_buff1, buf_sz, page1, 1); + } + + { + auto page1 = page_reader_mix->read(1); + ASSERT_PAGE_EQ(c_buff2, buf_sz, page1, 1); + } + + { + // Revert v3 + WriteBatch batch; + batch.delPage(1); + page_writer_mix->write(std::move(batch), nullptr); + } +} +CATCH + } // namespace PS::V3::tests } // namespace DB From 164eda5296fe0714ba3e7a30f0ffd7a562b378be Mon Sep 17 00:00:00 2001 From: JaySon Date: Sat, 18 Jun 2022 17:16:35 +0800 Subject: [PATCH 112/127] *: Add some comments about decoding (#5158) ref pingcap/tiflash#4862 --- dbms/src/Debug/dbgFuncMockRaftSnapshot.cpp | 46 +++---- .../Page/V3/tests/gtest_blob_store.cpp | 4 + .../DecodingStorageSchemaSnapshot.h | 4 +- .../Storages/Transaction/PartitionStreams.cpp | 1 + .../Transaction/RegionBlockReader.cpp | 1 + .../Storages/Transaction/RegionBlockReader.h | 2 +- dbms/src/Storages/Transaction/RowCodec.cpp | 43 ++++--- .../Storages/Transaction/TiKVRecordFormat.h | 119 ++++++++++++------ .../Transaction/tests/gtest_kvstore.cpp | 10 +- .../Transaction/tests/region_helper.h | 21 +++- tests/fullstack-test/mpp/mpp_fail.test | 1 + 11 files changed, 161 insertions(+), 91 deletions(-) diff --git a/dbms/src/Debug/dbgFuncMockRaftSnapshot.cpp b/dbms/src/Debug/dbgFuncMockRaftSnapshot.cpp index 7eecbbdf6f7..9d5b848ddea 100644 --- a/dbms/src/Debug/dbgFuncMockRaftSnapshot.cpp +++ b/dbms/src/Debug/dbgFuncMockRaftSnapshot.cpp @@ -60,7 +60,7 @@ RegionPtr GenDbgRegionSnapshotWithData(Context & context, const ASTs & args) { const String & database_name = typeid_cast(*args[0]).name; const String & table_name = typeid_cast(*args[1]).name; - RegionID region_id = static_cast(safeGet(typeid_cast(*args[2]).value)); + auto region_id = static_cast(safeGet(typeid_cast(*args[2]).value)); TableID table_id = RegionBench::getTableID(context, database_name, table_name, ""); MockTiDB::TablePtr table = MockTiDB::instance().getTableByName(database_name, table_name); auto & table_info = table->table_info; @@ -70,8 +70,8 @@ RegionPtr GenDbgRegionSnapshotWithData(Context & context, const ASTs & args) if (!is_common_handle) { - HandleID start = static_cast(safeGet(typeid_cast(*args[3]).value)); - HandleID end = static_cast(safeGet(typeid_cast(*args[4]).value)); + auto start = static_cast(safeGet(typeid_cast(*args[3]).value)); + auto end = static_cast(safeGet(typeid_cast(*args[4]).value)); region = RegionBench::createRegion(table_id, region_id, start, end); } else @@ -105,8 +105,8 @@ RegionPtr GenDbgRegionSnapshotWithData(Context & context, const ASTs & args) for (auto it = args_begin; it != args_end; it += len) { HandleID handle_id = is_common_handle ? 0 : static_cast(safeGet(typeid_cast(*it[0]).value)); - Timestamp tso = static_cast(safeGet(typeid_cast(*it[1]).value)); - UInt8 del = static_cast(safeGet(typeid_cast(*it[2]).value)); + auto tso = static_cast(safeGet(typeid_cast(*it[1]).value)); + auto del = static_cast(safeGet(typeid_cast(*it[2]).value)); { std::vector fields; @@ -168,7 +168,7 @@ void MockRaftCommand::dbgFuncRegionSnapshotWithData(Context & context, const AST // DBGInvoke region_snapshot(region-id, start-key, end-key, database-name, table-name[, partition-id]) void MockRaftCommand::dbgFuncRegionSnapshot(Context & context, const ASTs & args, DBGInvoker::Printer output) { - RegionID region_id = static_cast(safeGet(typeid_cast(*args[0]).value)); + auto region_id = static_cast(safeGet(typeid_cast(*args[0]).value)); bool has_partition_id = false; size_t args_size = args.size(); if (dynamic_cast(args[args_size - 1].get()) != nullptr) @@ -214,15 +214,15 @@ void MockRaftCommand::dbgFuncRegionSnapshot(Context & context, const ASTs & args } else { - HandleID start = static_cast(safeGet(typeid_cast(*args[1]).value)); - HandleID end = static_cast(safeGet(typeid_cast(*args[2]).value)); + auto start = static_cast(safeGet(typeid_cast(*args[1]).value)); + auto end = static_cast(safeGet(typeid_cast(*args[2]).value)); start_key = RecordKVFormat::genKey(table_id, start); end_key = RecordKVFormat::genKey(table_id, end); } region_info.set_start_key(start_key.toString()); region_info.set_end_key(end_key.toString()); - *region_info.add_peers() = createPeer(1, true); - *region_info.add_peers() = createPeer(2, true); + *region_info.add_peers() = tests::createPeer(1, true); + *region_info.add_peers() = tests::createPeer(2, true); auto peer_id = 1; auto start_decoded_key = RecordKVFormat::decodeTiKVKey(start_key); auto end_decoded_key = RecordKVFormat::decodeTiKVKey(end_key); @@ -432,9 +432,9 @@ void MockRaftCommand::dbgFuncIngestSST(Context & context, const ASTs & args, DBG { const String & database_name = typeid_cast(*args[0]).name; const String & table_name = typeid_cast(*args[1]).name; - RegionID region_id = static_cast(safeGet(typeid_cast(*args[2]).value)); - RegionID start_handle = static_cast(safeGet(typeid_cast(*args[3]).value)); - RegionID end_handle = static_cast(safeGet(typeid_cast(*args[4]).value)); + auto region_id = static_cast(safeGet(typeid_cast(*args[2]).value)); + auto start_handle = static_cast(safeGet(typeid_cast(*args[3]).value)); + auto end_handle = static_cast(safeGet(typeid_cast(*args[4]).value)); MockTiDB::TablePtr table = MockTiDB::instance().getTableByName(database_name, table_name); const auto & table_info = RegionBench::getTableInfo(context, database_name, table_name); @@ -555,7 +555,7 @@ void MockRaftCommand::dbgFuncRegionSnapshotApplyBlock(Context & context, const A throw Exception("Args not matched, should be: region-id", ErrorCodes::BAD_ARGUMENTS); } - RegionID region_id = static_cast(safeGet(typeid_cast(*args.front()).value)); + auto region_id = static_cast(safeGet(typeid_cast(*args.front()).value)); auto [region, block_cache] = GLOBAL_REGION_MAP.popRegionCache("__snap_" + std::to_string(region_id)); auto & tmt = context.getTMTContext(); context.getTMTContext().getKVStore()->checkAndApplySnapshot({region, std::move(block_cache)}, tmt); @@ -577,12 +577,12 @@ void MockRaftCommand::dbgFuncRegionSnapshotPreHandleDTFiles(Context & context, c const String & database_name = typeid_cast(*args[0]).name; const String & table_name = typeid_cast(*args[1]).name; - RegionID region_id = static_cast(safeGet(typeid_cast(*args[2]).value)); - RegionID start_handle = static_cast(safeGet(typeid_cast(*args[3]).value)); - RegionID end_handle = static_cast(safeGet(typeid_cast(*args[4]).value)); + auto region_id = static_cast(safeGet(typeid_cast(*args[2]).value)); + auto start_handle = static_cast(safeGet(typeid_cast(*args[3]).value)); + auto end_handle = static_cast(safeGet(typeid_cast(*args[4]).value)); - const String schema_str = safeGet(typeid_cast(*args[5]).value); - String handle_pk_name = safeGet(typeid_cast(*args[6]).value); + const auto schema_str = safeGet(typeid_cast(*args[5]).value); + auto handle_pk_name = safeGet(typeid_cast(*args[6]).value); UInt64 test_fields = 1; if (args.size() > 7) @@ -677,10 +677,10 @@ void MockRaftCommand::dbgFuncRegionSnapshotPreHandleDTFilesWithHandles(Context & const String & database_name = typeid_cast(*args[0]).name; const String & table_name = typeid_cast(*args[1]).name; - RegionID region_id = static_cast(safeGet(typeid_cast(*args[2]).value)); + auto region_id = static_cast(safeGet(typeid_cast(*args[2]).value)); - const String schema_str = safeGet(typeid_cast(*args[3]).value); - String handle_pk_name = safeGet(typeid_cast(*args[4]).value); + const auto schema_str = safeGet(typeid_cast(*args[3]).value); + auto handle_pk_name = safeGet(typeid_cast(*args[4]).value); std::vector handles; for (size_t i = 5; i < args.size(); ++i) @@ -770,7 +770,7 @@ void MockRaftCommand::dbgFuncRegionSnapshotApplyDTFiles(Context & context, const if (args.size() != 1) throw Exception("Args not matched, should be: region-id", ErrorCodes::BAD_ARGUMENTS); - RegionID region_id = static_cast(safeGet(typeid_cast(*args.front()).value)); + auto region_id = static_cast(safeGet(typeid_cast(*args.front()).value)); const auto region_name = "__snap_snap_" + std::to_string(region_id); auto [new_region, ingest_ids] = GLOBAL_REGION_MAP.popRegionSnap(region_name); auto & tmt = context.getTMTContext(); diff --git a/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp b/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp index fdd08c7cb8e..f9daacc4cce 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_blob_store.cpp @@ -306,6 +306,7 @@ try blob_store.blob_stats.restoreByEntry(PageEntryV3{ .file_id = file_id1, .size = 128, + .padded_size = 0, .tag = 0, .offset = 1024, .checksum = 0x4567, @@ -313,6 +314,7 @@ try blob_store.blob_stats.restoreByEntry(PageEntryV3{ .file_id = file_id1, .size = 512, + .padded_size = 0, .tag = 0, .offset = 2048, .checksum = 0x4567, @@ -320,6 +322,7 @@ try blob_store.blob_stats.restoreByEntry(PageEntryV3{ .file_id = file_id2, .size = 512, + .padded_size = 0, .tag = 0, .offset = 2048, .checksum = 0x4567, @@ -402,6 +405,7 @@ try blob_store.blob_stats.restoreByEntry(PageEntryV3{ .file_id = id, .size = 1024, + .padded_size = 0, .tag = 0, .offset = 0, .checksum = 0x4567, diff --git a/dbms/src/Storages/Transaction/DecodingStorageSchemaSnapshot.h b/dbms/src/Storages/Transaction/DecodingStorageSchemaSnapshot.h index c636d9e60ab..e8e0610326c 100644 --- a/dbms/src/Storages/Transaction/DecodingStorageSchemaSnapshot.h +++ b/dbms/src/Storages/Transaction/DecodingStorageSchemaSnapshot.h @@ -125,11 +125,11 @@ struct DecodingStorageSchemaSnapshot { auto pk_pos_iter = pk_pos_map.begin(); size_t column_pos_in_block = 0; - for (auto iter = sorted_column_id_with_pos.begin(); iter != sorted_column_id_with_pos.end(); iter++) + for (auto & column_id_with_pos : sorted_column_id_with_pos) { if (pk_pos_iter == pk_pos_map.end()) break; - if (pk_pos_iter->first == iter->first) + if (pk_pos_iter->first == column_id_with_pos.first) { pk_pos_iter->second = column_pos_in_block; pk_pos_iter++; diff --git a/dbms/src/Storages/Transaction/PartitionStreams.cpp b/dbms/src/Storages/Transaction/PartitionStreams.cpp index 4b2ca6c07a8..456f067fe5e 100644 --- a/dbms/src/Storages/Transaction/PartitionStreams.cpp +++ b/dbms/src/Storages/Transaction/PartitionStreams.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include #include diff --git a/dbms/src/Storages/Transaction/RegionBlockReader.cpp b/dbms/src/Storages/Transaction/RegionBlockReader.cpp index af351f4a6b0..a9384e4a14d 100644 --- a/dbms/src/Storages/Transaction/RegionBlockReader.cpp +++ b/dbms/src/Storages/Transaction/RegionBlockReader.cpp @@ -186,6 +186,7 @@ bool RegionBlockReader::readImpl(Block & block, const RegionDataReadInfoList & d } else { + // For common handle, sometimes we need to decode the value from encoded key instead of encoded value auto * raw_extra_column = const_cast((block.getByPosition(extra_handle_column_pos)).column.get()); raw_extra_column->insertData(pk->data(), pk->size()); /// decode key and insert pk columns if needed diff --git a/dbms/src/Storages/Transaction/RegionBlockReader.h b/dbms/src/Storages/Transaction/RegionBlockReader.h index ec633e805c0..004d9f40447 100644 --- a/dbms/src/Storages/Transaction/RegionBlockReader.h +++ b/dbms/src/Storages/Transaction/RegionBlockReader.h @@ -41,7 +41,7 @@ class Block; class RegionBlockReader : private boost::noncopyable { public: - RegionBlockReader(DecodingStorageSchemaSnapshotConstPtr schema_snapshot_); + explicit RegionBlockReader(DecodingStorageSchemaSnapshotConstPtr schema_snapshot_); /// Read `data_list` as a block. /// diff --git a/dbms/src/Storages/Transaction/RowCodec.cpp b/dbms/src/Storages/Transaction/RowCodec.cpp index 427544a0467..ea7f6b7c2da 100644 --- a/dbms/src/Storages/Transaction/RowCodec.cpp +++ b/dbms/src/Storages/Transaction/RowCodec.cpp @@ -314,7 +314,7 @@ bool appendRowV2ToBlock( ColumnID pk_handle_id, bool force_decode) { - UInt8 row_flag = readLittleEndian(&raw_value[1]); + auto row_flag = readLittleEndian(&raw_value[1]); bool is_big = row_flag & RowV2::BigRowMask; return is_big ? appendRowV2ToBlockImpl(raw_value, column_ids_iter, column_ids_iter_end, block, block_column_pos, column_infos, pk_handle_id, force_decode) : appendRowV2ToBlockImpl(raw_value, column_ids_iter, column_ids_iter_end, block, block_column_pos, column_infos, pk_handle_id, force_decode); @@ -360,9 +360,10 @@ bool appendRowV2ToBlockImpl( decodeUInts::ColumnIDType>(cursor, raw_value, num_null_columns, null_column_ids); decodeUInts::ValueOffsetType>(cursor, raw_value, num_not_null_columns, value_offsets); size_t values_start_pos = cursor; - size_t id_not_null = 0, id_null = 0; + size_t idx_not_null = 0; + size_t idx_null = 0; // Merge ordered not null/null columns to keep order. - while (id_not_null < not_null_column_ids.size() || id_null < null_column_ids.size()) + while (idx_not_null < not_null_column_ids.size() || idx_null < null_column_ids.size()) { if (column_ids_iter == column_ids_iter_end) { @@ -371,24 +372,31 @@ bool appendRowV2ToBlockImpl( } bool is_null; - if (id_not_null < not_null_column_ids.size() && id_null < null_column_ids.size()) - is_null = not_null_column_ids[id_not_null] > null_column_ids[id_null]; + if (idx_not_null < not_null_column_ids.size() && idx_null < null_column_ids.size()) + is_null = not_null_column_ids[idx_not_null] > null_column_ids[idx_null]; else - is_null = id_null < null_column_ids.size(); + is_null = idx_null < null_column_ids.size(); - auto next_datum_column_id = is_null ? null_column_ids[id_null] : not_null_column_ids[id_not_null]; + auto next_datum_column_id = is_null ? null_column_ids[idx_null] : not_null_column_ids[idx_not_null]; if (column_ids_iter->first > next_datum_column_id) { - // extra column + // The next column id to read is bigger than the column id of next datum in encoded row. + // It means this is the datum of extra column. May happen when reading after dropping + // a column. if (!force_decode) return false; + // Ignore the extra column and continue to parse other datum if (is_null) - id_null++; + idx_null++; else - id_not_null++; + idx_not_null++; } else if (column_ids_iter->first < next_datum_column_id) { + // The next column id to read is less than the column id of next datum in encoded row. + // It means this is the datum of missing column. May happen when reading after adding + // a column. + // Fill with default value and continue to read data for next column id. const auto & column_info = column_infos[column_ids_iter->second]; if (!addDefaultValueToColumnIfPossible(column_info, block, block_column_pos, force_decode)) return false; @@ -397,7 +405,7 @@ bool appendRowV2ToBlockImpl( } else { - // if pk_handle_id is a valid column id, then it means the table's pk_is_handle is true + // If pk_handle_id is a valid column id, then it means the table's pk_is_handle is true // we can just ignore the pk value encoded in value part if (unlikely(column_ids_iter->first == pk_handle_id)) { @@ -405,15 +413,16 @@ bool appendRowV2ToBlockImpl( block_column_pos++; if (is_null) { - id_null++; + idx_null++; } else { - id_not_null++; + idx_not_null++; } continue; } + // Parse the datum. auto * raw_column = const_cast((block.getByPosition(block_column_pos)).column.get()); const auto & column_info = column_infos[column_ids_iter->second]; if (is_null) @@ -432,15 +441,15 @@ bool appendRowV2ToBlockImpl( } // ColumnNullable::insertDefault just insert a null value raw_column->insertDefault(); - id_null++; + idx_null++; } else { - size_t start = id_not_null ? value_offsets[id_not_null - 1] : 0; - size_t length = value_offsets[id_not_null] - start; + size_t start = idx_not_null ? value_offsets[idx_not_null - 1] : 0; + size_t length = value_offsets[idx_not_null] - start; if (!raw_column->decodeTiDBRowV2Datum(values_start_pos + start, raw_value, length, force_decode)) return false; - id_not_null++; + idx_not_null++; } column_ids_iter++; block_column_pos++; diff --git a/dbms/src/Storages/Transaction/TiKVRecordFormat.h b/dbms/src/Storages/Transaction/TiKVRecordFormat.h index 4a25b6d9292..c507616f6e9 100644 --- a/dbms/src/Storages/Transaction/TiKVRecordFormat.h +++ b/dbms/src/Storages/Transaction/TiKVRecordFormat.h @@ -30,7 +30,6 @@ namespace DB { - namespace ErrorCodes { extern const int LOGICAL_ERROR; @@ -38,7 +37,6 @@ extern const int LOGICAL_ERROR; namespace RecordKVFormat { - enum CFModifyFlag : UInt8 { PutFlag = 'P', @@ -83,17 +81,35 @@ inline TiKVKey encodeAsTiKVKey(const String & ori_str) return TiKVKey(ss.releaseStr()); } -inline UInt64 encodeUInt64(const UInt64 x) { return toBigEndian(x); } +inline UInt64 encodeUInt64(const UInt64 x) +{ + return toBigEndian(x); +} -inline UInt64 encodeInt64(const Int64 x) { return encodeUInt64(static_cast(x) ^ SIGN_MASK); } +inline UInt64 encodeInt64(const Int64 x) +{ + return encodeUInt64(static_cast(x) ^ SIGN_MASK); +} -inline UInt64 encodeUInt64Desc(const UInt64 x) { return encodeUInt64(~x); } +inline UInt64 encodeUInt64Desc(const UInt64 x) +{ + return encodeUInt64(~x); +} -inline UInt64 decodeUInt64(const UInt64 x) { return toBigEndian(x); } +inline UInt64 decodeUInt64(const UInt64 x) +{ + return toBigEndian(x); +} -inline UInt64 decodeUInt64Desc(const UInt64 x) { return ~decodeUInt64(x); } +inline UInt64 decodeUInt64Desc(const UInt64 x) +{ + return ~decodeUInt64(x); +} -inline Int64 decodeInt64(const UInt64 x) { return static_cast(decodeUInt64(x) ^ SIGN_MASK); } +inline Int64 decodeInt64(const UInt64 x) +{ + return static_cast(decodeUInt64(x) ^ SIGN_MASK); +} inline void encodeInt64(const Int64 x, WriteBuffer & ss) { @@ -125,7 +141,10 @@ inline DecodedTiKVKey genRawKey(const TableID tableId, const HandleID handleId) return key; } -inline TiKVKey genKey(const TableID tableId, const HandleID handleId) { return encodeAsTiKVKey(genRawKey(tableId, handleId)); } +inline TiKVKey genKey(const TableID tableId, const HandleID handleId) +{ + return encodeAsTiKVKey(genRawKey(tableId, handleId)); +} inline TiKVKey genKey(const TiDB::TableInfo & table_info, std::vector keys) { @@ -176,29 +195,50 @@ inline std::tuple decodeTiKVKeyFull(const TiKVKey & key) } } -inline DecodedTiKVKey decodeTiKVKey(const TiKVKey & key) { return std::get<0>(decodeTiKVKeyFull(key)); } +inline DecodedTiKVKey decodeTiKVKey(const TiKVKey & key) +{ + return std::get<0>(decodeTiKVKeyFull(key)); +} -inline Timestamp getTs(const TiKVKey & key) { return decodeUInt64Desc(read(key.data() + key.dataSize() - 8)); } +inline Timestamp getTs(const TiKVKey & key) +{ + return decodeUInt64Desc(read(key.data() + key.dataSize() - 8)); +} -inline TableID getTableId(const DecodedTiKVKey & key) { return decodeInt64(read(key.data() + 1)); } +inline TableID getTableId(const DecodedTiKVKey & key) +{ + return decodeInt64(read(key.data() + 1)); +} -inline HandleID getHandle(const DecodedTiKVKey & key) { return decodeInt64(read(key.data() + RAW_KEY_NO_HANDLE_SIZE)); } +inline HandleID getHandle(const DecodedTiKVKey & key) +{ + return decodeInt64(read(key.data() + RAW_KEY_NO_HANDLE_SIZE)); +} inline RawTiDBPK getRawTiDBPK(const DecodedTiKVKey & key) { return std::make_shared(key.begin() + RAW_KEY_NO_HANDLE_SIZE, key.end()); } -inline TableID getTableId(const TiKVKey & key) { return getTableId(decodeTiKVKey(key)); } +inline TableID getTableId(const TiKVKey & key) +{ + return getTableId(decodeTiKVKey(key)); +} -inline HandleID getHandle(const TiKVKey & key) { return getHandle(decodeTiKVKey(key)); } +inline HandleID getHandle(const TiKVKey & key) +{ + return getHandle(decodeTiKVKey(key)); +} inline bool isRecord(const DecodedTiKVKey & raw_key) { return raw_key.size() >= RAW_KEY_SIZE && raw_key[0] == TABLE_PREFIX && memcmp(raw_key.data() + 9, RECORD_PREFIX_SEP, 2) == 0; } -inline TiKVKey truncateTs(const TiKVKey & key) { return TiKVKey(String(key.data(), key.dataSize() - sizeof(Timestamp))); } +inline TiKVKey truncateTs(const TiKVKey & key) +{ + return TiKVKey(String(key.data(), key.dataSize() - sizeof(Timestamp))); +} inline TiKVKey appendTs(const TiKVKey & key, Timestamp ts) { @@ -215,7 +255,12 @@ inline TiKVKey genKey(TableID tableId, HandleID handleId, Timestamp ts) } inline TiKVValue encodeLockCfValue( - UInt8 lock_type, const String & primary, Timestamp ts, UInt64 ttl, const String * short_value = nullptr, Timestamp min_commit_ts = 0) + UInt8 lock_type, + const String & primary, + Timestamp ts, + UInt64 ttl, + const String * short_value = nullptr, + Timestamp min_commit_ts = 0) { WriteBufferFromOwnString res; res.write(lock_type); @@ -275,7 +320,10 @@ inline R readVarInt(const char *& data, size_t & len) return res; } -inline UInt64 readVarUInt(const char *& data, size_t & len) { return readVarInt(data, len); } +inline UInt64 readVarUInt(const char *& data, size_t & len) +{ + return readVarInt(data, len); +} inline UInt8 readUInt8(const char *& data, size_t & len) { @@ -347,30 +395,29 @@ inline DecodedWriteCFValue decodeWriteCfValue(const TiKVValue & value) auto flag = RecordKVFormat::readUInt8(data, len); switch (flag) { - case RecordKVFormat::SHORT_VALUE_PREFIX: - { - size_t slen = RecordKVFormat::readUInt8(data, len); - if (slen > len) - throw Exception("content len not equal to short value len", ErrorCodes::LOGICAL_ERROR); - short_value = RecordKVFormat::readRawString(data, len, slen); - break; - } - case RecordKVFormat::FLAG_OVERLAPPED_ROLLBACK: - // ignore - break; - case RecordKVFormat::GC_FENCE_PREFIX: - /** + case RecordKVFormat::SHORT_VALUE_PREFIX: + { + size_t slen = RecordKVFormat::readUInt8(data, len); + if (slen > len) + throw Exception("content len not equal to short value len", ErrorCodes::LOGICAL_ERROR); + short_value = RecordKVFormat::readRawString(data, len, slen); + break; + } + case RecordKVFormat::FLAG_OVERLAPPED_ROLLBACK: + // ignore + break; + case RecordKVFormat::GC_FENCE_PREFIX: + /** * according to https://github.com/tikv/tikv/pull/9207, when meet `GC fence` flag, it is definitely a * rewriting record and there must be a complete row written to tikv, just ignore it in tiflash. */ - return std::nullopt; - default: - throw Exception("invalid flag " + std::to_string(flag) + " in write cf", ErrorCodes::LOGICAL_ERROR); + return std::nullopt; + default: + throw Exception("invalid flag " + std::to_string(flag) + " in write cf", ErrorCodes::LOGICAL_ERROR); } } - return InnerDecodedWriteCFValue{write_type, prewrite_ts, - short_value.empty() ? nullptr : std::make_shared(short_value.data(), short_value.length())}; + return InnerDecodedWriteCFValue{write_type, prewrite_ts, short_value.empty() ? nullptr : std::make_shared(short_value.data(), short_value.length())}; } inline TiKVValue encodeWriteCfValue(UInt8 write_type, Timestamp ts, std::string_view short_value = {}, bool gc_fence = false) diff --git a/dbms/src/Storages/Transaction/tests/gtest_kvstore.cpp b/dbms/src/Storages/Transaction/tests/gtest_kvstore.cpp index f0cafce3914..36a91522bb6 100644 --- a/dbms/src/Storages/Transaction/tests/gtest_kvstore.cpp +++ b/dbms/src/Storages/Transaction/tests/gtest_kvstore.cpp @@ -18,10 +18,9 @@ #include #include #include +#include #include -#include "region_helper.h" - namespace DB { namespace RegionBench @@ -37,13 +36,6 @@ extern void ChangeRegionStateRange(RegionState & region_state, bool source_at_le namespace tests { -RegionPtr makeRegion(UInt64 id, const std::string start_key, const std::string end_key, const TiFlashRaftProxyHelper * proxy_helper = nullptr) -{ - return std::make_shared( - RegionMeta(createPeer(2, true), createRegionInfo(id, std::move(start_key), std::move(end_key)), initialApplyState()), - proxy_helper); -} - class RegionKVStoreTest : public ::testing::Test { public: diff --git a/dbms/src/Storages/Transaction/tests/region_helper.h b/dbms/src/Storages/Transaction/tests/region_helper.h index 2808ace0ecb..39bae2669ab 100644 --- a/dbms/src/Storages/Transaction/tests/region_helper.h +++ b/dbms/src/Storages/Transaction/tests/region_helper.h @@ -18,8 +18,10 @@ #include -using namespace DB; - +namespace DB +{ +namespace tests +{ #define ASSERT_CHECK(cond, res) \ do \ { \ @@ -37,7 +39,7 @@ using namespace DB; #define ASSERT_CHECK_EQUAL(a, b, res) \ do \ { \ - if (!(a == b)) \ + if (!((a) == (b))) \ { \ std::cerr << __FILE__ << ":" << __LINE__ << ":" \ << " Assertion " << #a << " == " << #b << " failed.\n"; \ @@ -76,3 +78,16 @@ inline RegionMeta createRegionMeta(UInt64 id, DB::TableID table_id, std::optiona /*region=*/createRegionInfo(id, RecordKVFormat::genKey(table_id, 0), RecordKVFormat::genKey(table_id, 300)), /*apply_state_=*/apply_state.value_or(initialApplyState())); } + +inline RegionPtr makeRegion(UInt64 id, const std::string start_key, const std::string end_key, const TiFlashRaftProxyHelper * proxy_helper = nullptr) +{ + return std::make_shared( + RegionMeta( + createPeer(2, true), + createRegionInfo(id, std::move(start_key), std::move(end_key)), + initialApplyState()), + proxy_helper); +} + +} // namespace tests +} // namespace DB diff --git a/tests/fullstack-test/mpp/mpp_fail.test b/tests/fullstack-test/mpp/mpp_fail.test index 02259a90681..7af5fef3f89 100644 --- a/tests/fullstack-test/mpp/mpp_fail.test +++ b/tests/fullstack-test/mpp/mpp_fail.test @@ -21,6 +21,7 @@ mysql> insert into test.t values(1,'a'),(2,'b'),(3,'c') mysql> alter table test.t set tiflash replica 1 func> wait_table test t +mysql> analyze table test.t # Data. From 604b0de112fdb8a80b4c0157de03c8db3498444f Mon Sep 17 00:00:00 2001 From: hehechen Date: Mon, 20 Jun 2022 17:20:36 +0800 Subject: [PATCH 113/127] MinMax Index Supports Nullable DataType (#5153) close pingcap/tiflash#4787 --- dbms/src/DataTypes/IDataType.h | 1 - .../Storages/DeltaMerge/File/DMFileWriter.cpp | 13 +- .../Storages/DeltaMerge/Index/MinMaxIndex.cpp | 191 +++++++++++++++++- .../Storages/DeltaMerge/Index/MinMaxIndex.h | 3 + .../tests/gtest_dm_minmax_index.cpp | 82 +++++++- 5 files changed, 268 insertions(+), 22 deletions(-) diff --git a/dbms/src/DataTypes/IDataType.h b/dbms/src/DataTypes/IDataType.h index 120d0b1ba30..71fda0615e4 100644 --- a/dbms/src/DataTypes/IDataType.h +++ b/dbms/src/DataTypes/IDataType.h @@ -471,7 +471,6 @@ class IDataType : private boost::noncopyable virtual bool isEnum() const { return false; }; virtual bool isNullable() const { return false; } - /** Is this type can represent only NULL value? (It also implies isNullable) */ virtual bool onlyNull() const { return false; } diff --git a/dbms/src/Storages/DeltaMerge/File/DMFileWriter.cpp b/dbms/src/Storages/DeltaMerge/File/DMFileWriter.cpp index 3bff05ef19f..272d548eee1 100644 --- a/dbms/src/Storages/DeltaMerge/File/DMFileWriter.cpp +++ b/dbms/src/Storages/DeltaMerge/File/DMFileWriter.cpp @@ -72,10 +72,9 @@ DMFileWriter::DMFileWriter(const DMFilePtr & dmfile_, for (auto & cd : write_columns) { // TODO: currently we only generate index for Integers, Date, DateTime types, and this should be configurable by user. - // TODO: If column type is nullable, we won't generate index for it /// for handle column always generate index - bool do_index = cd.id == EXTRA_HANDLE_COLUMN_ID || cd.type->isInteger() || cd.type->isDateOrDateTime(); - + auto type = removeNullable(cd.type); + bool do_index = cd.id == EXTRA_HANDLE_COLUMN_ID || type->isInteger() || type->isDateOrDateTime(); if (options.flags.isSingleFile()) { if (do_index) @@ -122,7 +121,7 @@ void DMFileWriter::addStreams(ColId col_id, DataTypePtr type, bool do_index) void DMFileWriter::write(const Block & block, const BlockProperty & block_property) { is_empty_file = false; - DMFile::PackStat stat; + DMFile::PackStat stat{}; stat.rows = block.rows(); stat.not_clean = block_property.not_clean_rows; stat.bytes = block.bytes(); // This is bytes of pack data in memory. @@ -219,7 +218,7 @@ void DMFileWriter::writeColumn(ColId col_id, const IDataType & type, const IColu "Type shouldn be nullable when substream_path's type is NullMap.", Errors::DeltaTree::Internal); - const ColumnNullable & col = static_cast(column); + const auto & col = static_cast(column); col.checkConsistency(); DataTypeUInt8().serializeBinaryBulk(col.getNullMapColumn(), single_file_stream->original_layer, 0, rows); } @@ -230,8 +229,8 @@ void DMFileWriter::writeColumn(ColId col_id, const IDataType & type, const IColu "Type shouldn be nullable when substream_path's type is NullableElements.", Errors::DeltaTree::Internal); - const DataTypeNullable & nullable_type = static_cast(type); - const ColumnNullable & col = static_cast(column); + const auto & nullable_type = static_cast(type); + const auto & col = static_cast(column); nullable_type.getNestedType()->serializeBinaryBulk(col.getNestedColumn(), single_file_stream->original_layer, 0, rows); } else diff --git a/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.cpp b/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.cpp index 2681284948c..6229d54c169 100644 --- a/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.cpp +++ b/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.cpp @@ -61,7 +61,6 @@ inline std::pair minmax(const IColumn & column, const ColumnVect void MinMaxIndex::addPack(const IColumn & column, const ColumnVector * del_mark) { - const IColumn * column_ptr = &column; auto size = column.size(); bool has_null = false; if (column.isColumnNullable()) @@ -70,7 +69,6 @@ void MinMaxIndex::addPack(const IColumn & column, const ColumnVector * de const auto & nullable_column = static_cast(column); const auto & null_mark_data = nullable_column.getNullMapColumn().getData(); - column_ptr = &nullable_column.getNestedColumn(); for (size_t i = 0; i < size; ++i) { @@ -82,14 +80,13 @@ void MinMaxIndex::addPack(const IColumn & column, const ColumnVector * de } } - const IColumn & updated_column = *column_ptr; - auto [min_index, max_index] = details::minmax(updated_column, del_mark, 0, updated_column.size()); + auto [min_index, max_index] = details::minmax(column, del_mark, 0, column.size()); if (min_index != NONE_EXIST) { has_null_marks->push_back(has_null); has_value_marks->push_back(1); - minmaxes->insertFrom(updated_column, min_index); - minmaxes->insertFrom(updated_column, max_index); + minmaxes->insertFrom(column, min_index); + minmaxes->insertFrom(column, max_index); } else { @@ -158,6 +155,62 @@ std::pair MinMaxIndex::getUInt64MinMax(size_t pack_index) return {minmaxes->get64(pack_index * 2), minmaxes->get64(pack_index * 2 + 1)}; } +RSResult MinMaxIndex::checkNullableEqual(size_t pack_index, const Field & value, const DataTypePtr & type) +{ + const auto & column_nullable = static_cast(*minmaxes); + + const auto * raw_type = type.get(); + +#define DISPATCH(TYPE) \ + if (typeid_cast(raw_type)) \ + { \ + auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); \ + auto min = minmaxes_data[pack_index * 2]; \ + auto max = minmaxes_data[pack_index * 2 + 1]; \ + return RoughCheck::checkEqual(value, type, min, max); \ + } + FOR_NUMERIC_TYPES(DISPATCH) +#undef DISPATCH + if (typeid_cast(raw_type)) + { + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkEqual(value, type, min, max); + } + if (typeid_cast(raw_type)) + { + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkEqual(value, type, min, max); + } + if (typeid_cast(raw_type) || typeid_cast(raw_type)) + { + // For DataTypeMyDateTime / DataTypeMyDate, simply compare them as comparing UInt64 is OK. + // Check `struct MyTimeBase` for more details. + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkEqual(value, type, min, max); + } + if (typeid_cast(raw_type)) + { + const auto * string_column = checkAndGetColumn(column_nullable.getNestedColumnPtr().get()); + const auto & chars = string_column->getChars(); + const auto & offsets = string_column->getOffsets(); + size_t pos = pack_index * 2; + size_t prev_offset = pos == 0 ? 0 : offsets[pos - 1]; + // todo use StringRef instead of String + auto min = String(chars[prev_offset], offsets[pos] - prev_offset - 1); + pos = pack_index * 2 + 1; + prev_offset = offsets[pos - 1]; + auto max = String(chars[prev_offset], offsets[pos] - prev_offset - 1); + return RoughCheck::checkEqual(value, type, min, max); + } + return RSResult::Some; +} + RSResult MinMaxIndex::checkEqual(size_t pack_index, const Field & value, const DataTypePtr & type) { if ((*has_null_marks)[pack_index] || value.isNull()) @@ -165,7 +218,13 @@ RSResult MinMaxIndex::checkEqual(size_t pack_index, const Field & value, const D if (!(*has_value_marks)[pack_index]) return RSResult::None; + // if minmaxes_data has null value, the value of minmaxes_data[i] is meaningless and maybe just some random value. + // But we have checked the has_null_marks above and ensured that there is no null value in MinMax Indexes. const auto * raw_type = type.get(); + if (typeid_cast(raw_type)) + { + return checkNullableEqual(pack_index, value, removeNullable(type)); + } #define DISPATCH(TYPE) \ if (typeid_cast(raw_type)) \ { \ @@ -215,6 +274,62 @@ RSResult MinMaxIndex::checkEqual(size_t pack_index, const Field & value, const D } return RSResult::Some; } + +RSResult MinMaxIndex::checkNullableGreater(size_t pack_index, const Field & value, const DataTypePtr & type) +{ + const auto & column_nullable = static_cast(*minmaxes); + const auto * raw_type = type.get(); + +#define DISPATCH(TYPE) \ + if (typeid_cast(raw_type)) \ + { \ + auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); \ + auto min = minmaxes_data[pack_index * 2]; \ + auto max = minmaxes_data[pack_index * 2 + 1]; \ + return RoughCheck::checkGreater(value, type, min, max); \ + } + FOR_NUMERIC_TYPES(DISPATCH) +#undef DISPATCH + if (typeid_cast(raw_type)) + { + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkGreater(value, type, min, max); + } + if (typeid_cast(raw_type)) + { + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkGreater(value, type, min, max); + } + if (typeid_cast(raw_type) || typeid_cast(raw_type)) + { + // For DataTypeMyDateTime / DataTypeMyDate, simply compare them as comparing UInt64 is OK. + // Check `struct MyTimeBase` for more details. + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkGreater(value, type, min, max); + } + if (typeid_cast(raw_type)) + { + const auto * string_column = checkAndGetColumn(column_nullable.getNestedColumnPtr().get()); + const auto & chars = string_column->getChars(); + const auto & offsets = string_column->getOffsets(); + size_t pos = pack_index * 2; + size_t prev_offset = pos == 0 ? 0 : offsets[pos - 1]; + // todo use StringRef instead of String + auto min = String(chars[prev_offset], offsets[pos] - prev_offset - 1); + pos = pack_index * 2 + 1; + prev_offset = offsets[pos - 1]; + auto max = String(chars[prev_offset], offsets[pos] - prev_offset - 1); + return RoughCheck::checkGreater(value, type, min, max); + } + return RSResult::Some; +} + RSResult MinMaxIndex::checkGreater(size_t pack_index, const Field & value, const DataTypePtr & type, int /*nan_direction_hint*/) { if ((*has_null_marks)[pack_index] || value.isNull()) @@ -223,6 +338,10 @@ RSResult MinMaxIndex::checkGreater(size_t pack_index, const Field & value, const return RSResult::None; const auto * raw_type = type.get(); + if (typeid_cast(raw_type)) + { + return checkNullableGreater(pack_index, value, removeNullable(type)); + } #define DISPATCH(TYPE) \ if (typeid_cast(raw_type)) \ { \ @@ -272,6 +391,62 @@ RSResult MinMaxIndex::checkGreater(size_t pack_index, const Field & value, const } return RSResult::Some; } + +RSResult MinMaxIndex::checkNullableGreaterEqual(size_t pack_index, const Field & value, const DataTypePtr & type) +{ + const auto & column_nullable = static_cast(*minmaxes); + + const auto * raw_type = type.get(); +#define DISPATCH(TYPE) \ + if (typeid_cast(raw_type)) \ + { \ + auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); \ + auto min = minmaxes_data[pack_index * 2]; \ + auto max = minmaxes_data[pack_index * 2 + 1]; \ + return RoughCheck::checkGreaterEqual(value, type, min, max); \ + } + FOR_NUMERIC_TYPES(DISPATCH) +#undef DISPATCH + if (typeid_cast(raw_type)) + { + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkGreaterEqual(value, type, min, max); + } + if (typeid_cast(raw_type)) + { + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkGreaterEqual(value, type, min, max); + } + if (typeid_cast(raw_type) || typeid_cast(raw_type)) + { + // For DataTypeMyDateTime / DataTypeMyDate, simply compare them as comparing UInt64 is OK. + // Check `struct MyTimeBase` for more details. + const auto & minmaxes_data = toColumnVectorData(column_nullable.getNestedColumnPtr()); + auto min = minmaxes_data[pack_index * 2]; + auto max = minmaxes_data[pack_index * 2 + 1]; + return RoughCheck::checkGreaterEqual(value, type, min, max); + } + if (typeid_cast(raw_type)) + { + const auto * string_column = checkAndGetColumn(column_nullable.getNestedColumnPtr().get()); + const auto & chars = string_column->getChars(); + const auto & offsets = string_column->getOffsets(); + size_t pos = pack_index * 2; + size_t prev_offset = pos == 0 ? 0 : offsets[pos - 1]; + // todo use StringRef instead of String + auto min = String(reinterpret_cast(&chars[prev_offset]), offsets[pos] - prev_offset - 1); + pos = pack_index * 2 + 1; + prev_offset = offsets[pos - 1]; + auto max = String(reinterpret_cast(&chars[prev_offset]), offsets[pos] - prev_offset - 1); + return RoughCheck::checkGreaterEqual(value, type, min, max); + } + return RSResult::Some; +} + RSResult MinMaxIndex::checkGreaterEqual(size_t pack_index, const Field & value, const DataTypePtr & type, int /*nan_direction_hint*/) { if ((*has_null_marks)[pack_index] || value.isNull()) @@ -280,6 +455,10 @@ RSResult MinMaxIndex::checkGreaterEqual(size_t pack_index, const Field & value, return RSResult::None; const auto * raw_type = type.get(); + if (typeid_cast(raw_type)) + { + return checkNullableGreaterEqual(pack_index, value, removeNullable(type)); + } #define DISPATCH(TYPE) \ if (typeid_cast(raw_type)) \ { \ diff --git a/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.h b/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.h index 7efd37fafa4..73284333c73 100644 --- a/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.h +++ b/dbms/src/Storages/DeltaMerge/Index/MinMaxIndex.h @@ -81,6 +81,9 @@ class MinMaxIndex RSResult checkGreaterEqual(size_t pack_index, const Field & value, const DataTypePtr & type, int nan_direction); static String toString(); + RSResult checkNullableEqual(size_t pack_index, const Field & value, const DataTypePtr & type); + RSResult checkNullableGreater(size_t pack_index, const Field & value, const DataTypePtr & type); + RSResult checkNullableGreaterEqual(size_t pack_index, const Field & value, const DataTypePtr & type); }; diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp index 96c0070b73b..bb31b687186 100644 --- a/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_dm_minmax_index.cpp @@ -214,14 +214,6 @@ try ASSERT_EQ(true, checkMatch(case_name, *context, "MyDateTime", "2020-09-27", createLessEqual(attr("MyDateTime"), parseMyDateTime("2020-09-27"), 0))); ASSERT_EQ(false, checkMatch(case_name, *context, "MyDateTime", "2020-09-27", createLessEqual(attr("MyDateTime"), parseMyDateTime("2020-09-26"), 0))); - /// Currently we don't do filtering for null values. i.e. if a pack contains any null values, then the pack will pass the filter. - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createEqual(attr("Nullable(Int64)"), Field((Int64)101)))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createIn(attr("Nullable(Int64)"), {Field((Int64)101)}))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createGreater(attr("Nullable(Int64)"), Field((Int64)100), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)101), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createLess(attr("Nullable(Int64)"), Field((Int64)100), 0))); - ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createLessEqual(attr("Nullable(Int64)"), Field((Int64)99), 0))); - ASSERT_EQ(false, checkDelMatch(case_name, *context, "Int64", "100", createEqual(attr("Int64"), Field((Int64)100)))); ASSERT_EQ(true, checkPkMatch(case_name, *context, "Int64", "100", createEqual(pkAttr(), Field((Int64)100)), true)); ASSERT_EQ(true, checkPkMatch(case_name, *context, "Int64", "100", createGreater(pkAttr(), Field((Int64)99), 0), true)); @@ -236,6 +228,80 @@ try } CATCH +TEST_F(DMMinMaxIndexTest, NullableToNullable) +try +{ + const auto * case_name = ::testing::UnitTest::GetInstance()->current_test_info()->name(); + // clang-format off + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createEqual(attr("Nullable(Int64)"), Field((Int64)101)))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createEqual(attr("Nullable(Int64)"), Field((Int64)100)))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createIn(attr("Nullable(Int64)"), {Field((Int64)100)}))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createIn(attr("Nullable(Int64)"), {Field((Int64)101)}))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createGreater(attr("Nullable(Int64)"), Field((Int64)99), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createGreater(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)101), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createLess(attr("Nullable(Int64)"), Field((Int64)101), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createLess(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", "100", createLessEqual(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Int64)", "100", createLessEqual(attr("Nullable(Int64)"), Field((Int64)99), 0))); + + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createEqual(attr("Nullable(Date)"), Field((String) "2020-09-27")))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createEqual(attr("Nullable(Date)"), Field((String) "2020-09-28")))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createIn(attr("Nullable(Date)"), {Field((String) "2020-09-27")}))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createIn(attr("Nullable(Date)"), {Field((String) "2020-09-28")}))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createGreater(attr("Nullable(Date)"), Field((String) "2020-09-26"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createGreater(attr("Nullable(Date)"), Field((String) "2020-09-27"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createGreaterEqual(attr("Nullable(Date)"), Field((String) "2020-09-27"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createGreaterEqual(attr("Nullable(Date)"), Field((String) "2020-09-28"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createLess(attr("Nullable(Date)"), Field((String) "2020-09-28"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createLess(attr("Nullable(Date)"), Field((String) "2020-09-27"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createLessEqual(attr("Nullable(Date)"), Field((String) "2020-09-27"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(Date)", "2020-09-27", createLessEqual(attr("Nullable(Date)"), Field((String) "2020-09-26"), 0))); + + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01")))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:02")))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createIn(attr("Nullable(DateTime)"), {Field((String) "2020-01-01 05:00:01")}))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createIn(attr("Nullable(DateTime)"), {Field((String) "2020-01-01 05:00:02")}))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createGreater(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:00"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createGreater(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createGreaterEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createGreaterEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:02"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createLess(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:02"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createLess(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createLessEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:01"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(DateTime)", "2020-01-01 05:00:01", createLessEqual(attr("Nullable(DateTime)"), Field((String) "2020-01-01 05:00:00"), 0))); + + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27")))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-28")))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createIn(attr("Nullable(MyDateTime)"), {parseMyDateTime("2020-09-27")}))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createIn(attr("Nullable(MyDateTime)"), {parseMyDateTime("2020-09-28")}))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createGreater(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-26"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createGreater(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createGreaterEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createGreaterEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-28"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createLess(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-28"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createLess(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27"), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createLessEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-27"), 0))); + ASSERT_EQ(false, checkMatch(case_name, *context, "Nullable(MyDateTime)", "2020-09-27", createLessEqual(attr("Nullable(MyDateTime)"), parseMyDateTime("2020-09-26"), 0))); + + // has null + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createEqual(attr("Nullable(Int64)"), Field((Int64)101)))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createIn(attr("Nullable(Int64)"), {Field((Int64)101)}))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createGreater(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)101), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createLess(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "100"}, {"1", "1", "0", "\\N"}}, createLessEqual(attr("Nullable(Int64)"), Field((Int64)99), 0))); + + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createEqual(attr("Nullable(Int64)"), Field((Int64)101)))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createIn(attr("Nullable(Int64)"), {Field((Int64)101)}))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createGreater(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createGreaterEqual(attr("Nullable(Int64)"), Field((Int64)101), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createLess(attr("Nullable(Int64)"), Field((Int64)100), 0))); + ASSERT_EQ(true, checkMatch(case_name, *context, "Nullable(Int64)", {{"0", "0", "0", "\\N"}}, createLessEqual(attr("Nullable(Int64)"), Field((Int64)99), 0))); +} +CATCH + TEST_F(DMMinMaxIndexTest, Logical) try { From 40baecabe6563aef92b210018d1893f8236416c5 Mon Sep 17 00:00:00 2001 From: Meng Xin Date: Mon, 20 Jun 2022 18:02:37 +0800 Subject: [PATCH 114/127] Reduce some unnecessary prometheus metrics. (#5006) close pingcap/tiflash#5080 --- dbms/src/Client/Connection.cpp | 8 - .../src/Client/ConnectionPoolWithFailover.cpp | 11 +- dbms/src/Common/Arena.h | 10 - dbms/src/Common/CurrentMetrics.cpp | 25 - dbms/src/Common/PoolWithFailoverBase.h | 8 - dbms/src/Common/ProfileEvents.cpp | 246 +++----- dbms/src/Common/TiFlashMetrics.h | 71 +-- .../AggregatingBlockInputStream.cpp | 8 - .../AsynchronousBlockInputStream.h | 8 - .../DataStreams/CountingBlockOutputStream.cpp | 16 +- .../MergeSortingBlockInputStream.cpp | 9 - ...regatedMemoryEfficientBlockInputStream.cpp | 13 +- .../ParallelAggregatingBlockInputStream.cpp | 9 - .../src/DataStreams/ParallelInputsProcessor.h | 7 - dbms/src/Dictionaries/CacheDictionary.cpp | 596 +++++++++--------- .../ComplexKeyCacheDictionary.cpp | 148 ++--- .../Dictionaries/ComplexKeyCacheDictionary.h | 178 +++--- .../WriteBufferFromFileProvider.cpp | 4 +- ...teReadBufferFromFileBaseByFileProvider.cpp | 6 - ...eWriteBufferFromFileBaseByFileProvider.cpp | 7 - dbms/src/Functions/FunctionsGeo.cpp | 44 +- dbms/src/Functions/Regexps.h | 8 - dbms/src/IO/BufferWithOwnMemory.h | 11 - dbms/src/IO/ChecksumBuffer.h | 4 - dbms/src/IO/CompressedReadBufferBase.cpp | 13 - dbms/src/IO/ReadBufferFromFileDescriptor.cpp | 6 +- dbms/src/IO/WriteBufferFromFileDescriptor.cpp | 2 - dbms/src/IO/createReadBufferFromFileBase.cpp | 9 - dbms/src/IO/createWriteBufferFromFileBase.cpp | 9 - dbms/src/Interpreters/Aggregator.cpp | 23 +- dbms/src/Interpreters/Context.cpp | 6 +- dbms/src/Interpreters/ExpressionActions.cpp | 7 - .../Interpreters/InterpreterInsertQuery.cpp | 11 +- .../Interpreters/InterpreterSelectQuery.cpp | 2 - dbms/src/Interpreters/ProcessList.h | 8 - dbms/src/Interpreters/QueryPriorities.h | 8 - dbms/src/Server/HTTPHandler.h | 20 +- dbms/src/Server/Server.cpp | 7 - dbms/src/Server/TCPHandler.h | 7 - .../src/Storages/BackgroundProcessingPool.cpp | 3 - dbms/src/Storages/MarkCache.h | 18 +- dbms/src/Storages/StorageBuffer.cpp | 37 -- dbms/src/TableFunctions/ITableFunction.cpp | 7 - 43 files changed, 612 insertions(+), 1046 deletions(-) diff --git a/dbms/src/Client/Connection.cpp b/dbms/src/Client/Connection.cpp index 61a2843ac59..e21bde19a47 100644 --- a/dbms/src/Client/Connection.cpp +++ b/dbms/src/Client/Connection.cpp @@ -38,12 +38,6 @@ #include #endif - -namespace CurrentMetrics -{ -extern const Metric SendExternalTables; -} - namespace DB { namespace ErrorCodes @@ -434,8 +428,6 @@ void Connection::sendExternalTablesData(ExternalTablesData & data) size_t maybe_compressed_out_bytes = maybe_compressed_out ? maybe_compressed_out->count() : 0; size_t rows = 0; - CurrentMetrics::Increment metric_increment{CurrentMetrics::SendExternalTables}; - for (auto & elem : data) { elem.first->readPrefix(); diff --git a/dbms/src/Client/ConnectionPoolWithFailover.cpp b/dbms/src/Client/ConnectionPoolWithFailover.cpp index a9b6825a3fe..179b2d92c0e 100644 --- a/dbms/src/Client/ConnectionPoolWithFailover.cpp +++ b/dbms/src/Client/ConnectionPoolWithFailover.cpp @@ -20,13 +20,6 @@ #include #include - -namespace ProfileEvents -{ -extern const Event DistributedConnectionMissingTable; -extern const Event DistributedConnectionStaleReplica; -} // namespace ProfileEvents - namespace DB { namespace ErrorCodes @@ -50,7 +43,7 @@ ConnectionPoolWithFailover::ConnectionPoolWithFailover( hostname_differences.resize(nested_pools.size()); for (size_t i = 0; i < nested_pools.size(); ++i) { - ConnectionPool & connection_pool = dynamic_cast(*nested_pools[i]); + auto & connection_pool = dynamic_cast(*nested_pools[i]); hostname_differences[i] = getHostNameDifference(local_hostname, connection_pool.getHost()); } } @@ -187,7 +180,6 @@ ConnectionPoolWithFailover::tryGetEntry( fail_message = "There is no table " + table_to_check->database + "." + table_to_check->table + " on server: " + result.entry->getDescription(); LOG_WARNING(log, fail_message); - ProfileEvents::increment(ProfileEvents::DistributedConnectionMissingTable); return result; } @@ -217,7 +209,6 @@ ConnectionPoolWithFailover::tryGetEntry( table_to_check->database, table_to_check->table, delay); - ProfileEvents::increment(ProfileEvents::DistributedConnectionStaleReplica); } } catch (const Exception & e) diff --git a/dbms/src/Common/Arena.h b/dbms/src/Common/Arena.h index c61ebfca8aa..ebaaf607a6d 100644 --- a/dbms/src/Common/Arena.h +++ b/dbms/src/Common/Arena.h @@ -24,13 +24,6 @@ #include #include - -namespace ProfileEvents -{ -extern const Event ArenaAllocChunks; -extern const Event ArenaAllocBytes; -} // namespace ProfileEvents - namespace DB { /** Memory pool to append something. For example, short strings. @@ -55,9 +48,6 @@ class Arena : private boost::noncopyable Chunk(size_t size_, Chunk * prev_) { - ProfileEvents::increment(ProfileEvents::ArenaAllocChunks); - ProfileEvents::increment(ProfileEvents::ArenaAllocBytes, size_); - begin = reinterpret_cast(Allocator::alloc(size_)); pos = begin; end = begin + size_; diff --git a/dbms/src/Common/CurrentMetrics.cpp b/dbms/src/Common/CurrentMetrics.cpp index 8a2f111d882..b7ce9fd1e89 100644 --- a/dbms/src/Common/CurrentMetrics.cpp +++ b/dbms/src/Common/CurrentMetrics.cpp @@ -17,36 +17,11 @@ /// Available metrics. Add something here as you wish. #define APPLY_FOR_METRICS(M) \ - M(Query) \ - M(Merge) \ - M(ReplicatedFetch) \ - M(ReplicatedSend) \ - M(ReplicatedChecks) \ - M(BackgroundPoolTask) \ - M(DiskSpaceReservedForMerge) \ - M(DistributedSend) \ - M(QueryPreempted) \ - M(TCPConnection) \ - M(HTTPConnection) \ - M(InterserverConnection) \ M(OpenFileForRead) \ M(OpenFileForWrite) \ M(OpenFileForReadWrite) \ - M(SendExternalTables) \ - M(QueryThread) \ - M(ReadonlyReplica) \ - M(LeaderReplica) \ M(MemoryTracking) \ M(MemoryTrackingInBackgroundProcessingPool) \ - M(MemoryTrackingForMerges) \ - M(LeaderElection) \ - M(EphemeralNode) \ - M(DelayedInserts) \ - M(ContextLockWait) \ - M(StorageBufferRows) \ - M(StorageBufferBytes) \ - M(DictCacheRequests) \ - M(Revision) \ M(PSMVCCNumSnapshots) \ M(PSMVCCSnapshotsList) \ M(RWLockWaitingReaders) \ diff --git a/dbms/src/Common/PoolWithFailoverBase.h b/dbms/src/Common/PoolWithFailoverBase.h index a5483587e3c..04e6474c0fe 100644 --- a/dbms/src/Common/PoolWithFailoverBase.h +++ b/dbms/src/Common/PoolWithFailoverBase.h @@ -40,12 +40,6 @@ extern const int LOGICAL_ERROR; } // namespace ErrorCodes } // namespace DB -namespace ProfileEvents -{ -extern const Event DistributedConnectionFailTry; -extern const Event DistributedConnectionFailAtAll; -} // namespace ProfileEvents - /// This class provides a pool with fault tolerance. It is used for pooling of connections to replicated DB. /// Initialized by several PoolBase objects. /// When a connection is requested, tries to create or choose an alive connection from one of the nested pools. @@ -254,14 +248,12 @@ PoolWithFailoverBase::getMany( else { LOG_FMT_WARNING(log, "Connection failed at try No.{}, reason: {}", shuffled_pool.error_count + 1, fail_message); - ProfileEvents::increment(ProfileEvents::DistributedConnectionFailTry); ++shuffled_pool.error_count; if (shuffled_pool.error_count >= max_tries) { ++failed_pools_count; - ProfileEvents::increment(ProfileEvents::DistributedConnectionFailAtAll); } } } diff --git a/dbms/src/Common/ProfileEvents.cpp b/dbms/src/Common/ProfileEvents.cpp index 0ec1ce438a6..7507ff0b1f8 100644 --- a/dbms/src/Common/ProfileEvents.cpp +++ b/dbms/src/Common/ProfileEvents.cpp @@ -16,160 +16,98 @@ /// Available events. Add something here as you wish. -#define APPLY_FOR_EVENTS(M) \ - M(Query) \ - M(SelectQuery) \ - M(InsertQuery) \ - M(DeleteQuery) \ - M(FileOpen) \ - M(FileOpenFailed) \ - M(Seek) \ - M(ReadBufferFromFileDescriptorRead) \ - M(ReadBufferFromFileDescriptorReadFailed) \ - M(ReadBufferFromFileDescriptorReadBytes) \ - M(WriteBufferFromFileDescriptorWrite) \ - M(WriteBufferFromFileDescriptorWriteFailed) \ - M(WriteBufferFromFileDescriptorWriteBytes) \ - M(ReadBufferAIORead) \ - M(ReadBufferAIOReadBytes) \ - M(WriteBufferAIOWrite) \ - M(WriteBufferAIOWriteBytes) \ - M(ReadCompressedBytes) \ - M(CompressedReadBufferBlocks) \ - M(CompressedReadBufferBytes) \ - M(UncompressedCacheHits) \ - M(UncompressedCacheMisses) \ - M(UncompressedCacheWeightLost) \ - M(IOBufferAllocs) \ - M(IOBufferAllocBytes) \ - M(ArenaAllocChunks) \ - M(ArenaAllocBytes) \ - M(FunctionExecute) \ - M(TableFunctionExecute) \ - M(MarkCacheHits) \ - M(MarkCacheMisses) \ - M(CreatedReadBufferOrdinary) \ - M(CreatedReadBufferAIO) \ - M(CreatedWriteBufferOrdinary) \ - M(CreatedWriteBufferAIO) \ - \ - M(InsertedRows) \ - M(InsertedBytes) \ - M(DelayedInserts) \ - M(RejectedInserts) \ - M(DelayedInsertsMilliseconds) \ - M(DuplicatedInsertedBlocks) \ - \ - M(DistributedConnectionFailTry) \ - M(DistributedConnectionMissingTable) \ - M(DistributedConnectionStaleReplica) \ - M(DistributedConnectionFailAtAll) \ - \ - M(CompileAttempt) \ - M(CompileSuccess) \ - \ - M(ExternalSortWritePart) \ - M(ExternalSortMerge) \ - M(ExternalAggregationWritePart) \ - M(ExternalAggregationMerge) \ - M(ExternalAggregationCompressedBytes) \ - M(ExternalAggregationUncompressedBytes) \ - \ - M(SlowRead) \ - M(ReadBackoff) \ - \ - M(RegexpCreated) \ - M(ContextLock) \ - \ - M(StorageBufferFlush) \ - M(StorageBufferErrorOnFlush) \ - M(StorageBufferPassedAllMinThresholds) \ - M(StorageBufferPassedTimeMaxThreshold) \ - M(StorageBufferPassedRowsMaxThreshold) \ - M(StorageBufferPassedBytesMaxThreshold) \ - \ - M(DictCacheKeysRequested) \ - M(DictCacheKeysRequestedMiss) \ - M(DictCacheKeysRequestedFound) \ - M(DictCacheKeysExpired) \ - M(DictCacheKeysNotFound) \ - M(DictCacheKeysHit) \ - M(DictCacheRequestTimeNs) \ - M(DictCacheRequests) \ - M(DictCacheLockWriteNs) \ - M(DictCacheLockReadNs) \ - \ - M(DistributedSyncInsertionTimeoutExceeded) \ - M(DataAfterMergeDiffersFromReplica) \ - M(PolygonsAddedToPool) \ - M(PolygonsInPoolAllocatedBytes) \ - M(RWLockAcquiredReadLocks) \ - M(RWLockAcquiredWriteLocks) \ - M(RWLockReadersWaitMilliseconds) \ - M(RWLockWritersWaitMilliseconds) \ - \ - M(PSMWritePages) \ - M(PSMWriteIOCalls) \ - M(PSV3MBlobExpansion) \ - M(PSV3MBlobReused) \ - M(PSMWriteBytes) \ - M(PSMBackgroundWriteBytes) \ - M(PSMReadPages) \ - M(PSMBackgroundReadBytes) \ - \ - M(PSMReadIOCalls) \ - M(PSMReadBytes) \ - M(PSMWriteFailed) \ - M(PSMReadFailed) \ - \ - M(PSMVCCApplyOnCurrentBase) \ - M(PSMVCCApplyOnCurrentDelta) \ - M(PSMVCCApplyOnNewDelta) \ - M(PSMVCCCompactOnDelta) \ - M(PSMVCCCompactOnDeltaRebaseRejected) \ - M(PSMVCCCompactOnBase) \ - \ - M(DMWriteBytes) \ - M(DMWriteBlock) \ - M(DMWriteBlockNS) \ - M(DMWriteFile) \ - M(DMWriteFileNS) \ - M(DMDeleteRange) \ - M(DMDeleteRangeNS) \ - M(DMAppendDeltaPrepare) \ - M(DMAppendDeltaPrepareNS) \ - M(DMAppendDeltaCommitMemory) \ - M(DMAppendDeltaCommitMemoryNS) \ - M(DMAppendDeltaCommitDisk) \ - M(DMAppendDeltaCommitDiskNS) \ - M(DMAppendDeltaCleanUp) \ - M(DMAppendDeltaCleanUpNS) \ - M(DMPlace) \ - M(DMPlaceNS) \ - M(DMPlaceUpsert) \ - M(DMPlaceUpsertNS) \ - M(DMPlaceDeleteRange) \ - M(DMPlaceDeleteRangeNS) \ - M(DMDeltaMerge) \ - M(DMDeltaMergeNS) \ - M(DMSegmentSplit) \ - M(DMSegmentSplitNS) \ - M(DMSegmentGetSplitPoint) \ - M(DMSegmentGetSplitPointNS) \ - M(DMSegmentMerge) \ - M(DMSegmentMergeNS) \ - M(DMFlushDeltaCache) \ - M(DMFlushDeltaCacheNS) \ - M(DMCleanReadRows) \ - \ - M(FileFSync) \ - \ - M(DMFileFilterNoFilter) \ - M(DMFileFilterAftPKAndPackSet) \ - M(DMFileFilterAftRoughSet) \ - \ - M(ChecksumDigestBytes) \ - \ +#define APPLY_FOR_EVENTS(M) \ + M(Query) \ + M(FileOpen) \ + M(FileOpenFailed) \ + M(ReadBufferFromFileDescriptorRead) \ + M(ReadBufferFromFileDescriptorReadFailed) \ + M(ReadBufferFromFileDescriptorReadBytes) \ + M(WriteBufferFromFileDescriptorWrite) \ + M(WriteBufferFromFileDescriptorWriteBytes) \ + M(ReadBufferAIORead) \ + M(ReadBufferAIOReadBytes) \ + M(WriteBufferAIOWrite) \ + M(WriteBufferAIOWriteBytes) \ + \ + M(UncompressedCacheHits) \ + M(UncompressedCacheMisses) \ + M(UncompressedCacheWeightLost) \ + M(MarkCacheHits) \ + M(MarkCacheMisses) \ + \ + M(ExternalAggregationCompressedBytes) \ + M(ExternalAggregationUncompressedBytes) \ + \ + M(ContextLock) \ + \ + M(RWLockAcquiredReadLocks) \ + M(RWLockAcquiredWriteLocks) \ + M(RWLockReadersWaitMilliseconds) \ + M(RWLockWritersWaitMilliseconds) \ + \ + M(PSMWritePages) \ + M(PSMWriteIOCalls) \ + M(PSV3MBlobExpansion) \ + M(PSV3MBlobReused) \ + M(PSMWriteBytes) \ + M(PSMBackgroundWriteBytes) \ + M(PSMReadPages) \ + M(PSMBackgroundReadBytes) \ + \ + M(PSMReadIOCalls) \ + M(PSMReadBytes) \ + M(PSMWriteFailed) \ + M(PSMReadFailed) \ + \ + M(PSMVCCApplyOnCurrentBase) \ + M(PSMVCCApplyOnCurrentDelta) \ + M(PSMVCCApplyOnNewDelta) \ + M(PSMVCCCompactOnDelta) \ + M(PSMVCCCompactOnDeltaRebaseRejected) \ + M(PSMVCCCompactOnBase) \ + \ + M(DMWriteBytes) \ + M(DMWriteBlock) \ + M(DMWriteBlockNS) \ + M(DMWriteFile) \ + M(DMWriteFileNS) \ + M(DMDeleteRange) \ + M(DMDeleteRangeNS) \ + M(DMAppendDeltaPrepare) \ + M(DMAppendDeltaPrepareNS) \ + M(DMAppendDeltaCommitMemory) \ + M(DMAppendDeltaCommitMemoryNS) \ + M(DMAppendDeltaCommitDisk) \ + M(DMAppendDeltaCommitDiskNS) \ + M(DMAppendDeltaCleanUp) \ + M(DMAppendDeltaCleanUpNS) \ + M(DMPlace) \ + M(DMPlaceNS) \ + M(DMPlaceUpsert) \ + M(DMPlaceUpsertNS) \ + M(DMPlaceDeleteRange) \ + M(DMPlaceDeleteRangeNS) \ + M(DMDeltaMerge) \ + M(DMDeltaMergeNS) \ + M(DMSegmentSplit) \ + M(DMSegmentSplitNS) \ + M(DMSegmentGetSplitPoint) \ + M(DMSegmentGetSplitPointNS) \ + M(DMSegmentMerge) \ + M(DMSegmentMergeNS) \ + M(DMFlushDeltaCache) \ + M(DMFlushDeltaCacheNS) \ + M(DMCleanReadRows) \ + \ + M(FileFSync) \ + \ + M(DMFileFilterNoFilter) \ + M(DMFileFilterAftPKAndPackSet) \ + M(DMFileFilterAftRoughSet) \ + \ + M(ChecksumDigestBytes) \ + \ M(RaftWaitIndexTimeout) namespace ProfileEvents diff --git a/dbms/src/Common/TiFlashMetrics.h b/dbms/src/Common/TiFlashMetrics.h index 9aa826e0e30..c0ce60af01e 100644 --- a/dbms/src/Common/TiFlashMetrics.h +++ b/dbms/src/Common/TiFlashMetrics.h @@ -60,27 +60,27 @@ namespace DB F(type_partition_ts, {"type", "partition_table_scan"}), \ F(type_window, {"type", "window"}), F(type_window_sort, {"type", "window_sort"})) \ M(tiflash_coprocessor_request_duration_seconds, "Bucketed histogram of request duration", Histogram, \ - F(type_batch, {{"type", "batch"}}, ExpBuckets{0.0005, 2, 30}), F(type_cop, {{"type", "cop"}}, ExpBuckets{0.0005, 2, 30}), \ - F(type_super_batch, {{"type", "super_batch"}}, ExpBuckets{0.0005, 2, 30}), \ - F(type_dispatch_mpp_task, {{"type", "dispatch_mpp_task"}}, ExpBuckets{0.0005, 2, 30}), \ - F(type_mpp_establish_conn, {{"type", "mpp_establish_conn"}}, ExpBuckets{0.0005, 2, 30}), \ - F(type_cancel_mpp_task, {{"type", "cancel_mpp_task"}}, ExpBuckets{0.0005, 2, 30}), \ - F(type_run_mpp_task, {{"type", "run_mpp_task"}}, ExpBuckets{0.0005, 2, 30})) \ + F(type_batch, {{"type", "batch"}}, ExpBuckets{0.001, 2, 20}), F(type_cop, {{"type", "cop"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_super_batch, {{"type", "super_batch"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_dispatch_mpp_task, {{"type", "dispatch_mpp_task"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_mpp_establish_conn, {{"type", "mpp_establish_conn"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_cancel_mpp_task, {{"type", "cancel_mpp_task"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_run_mpp_task, {{"type", "run_mpp_task"}}, ExpBuckets{0.001, 2, 20})) \ M(tiflash_coprocessor_request_memory_usage, "Bucketed histogram of request memory usage", Histogram, \ F(type_cop, {{"type", "cop"}}, ExpBuckets{1024 * 1024, 2, 16}), \ - F(type_super_batch, {{"type", "super_batch"}}, ExpBuckets{1024 * 1024, 2, 16}), \ - F(type_run_mpp_task, {{"type", "run_mpp_task"}}, ExpBuckets{1024 * 1024, 2, 16})) \ + F(type_super_batch, {{"type", "super_batch"}}, ExpBuckets{1024 * 1024, 2, 20}), \ + F(type_run_mpp_task, {{"type", "run_mpp_task"}}, ExpBuckets{1024 * 1024, 2, 20})) \ M(tiflash_coprocessor_request_error, "Total number of request error", Counter, F(reason_meet_lock, {"reason", "meet_lock"}), \ F(reason_region_not_found, {"reason", "region_not_found"}), F(reason_epoch_not_match, {"reason", "epoch_not_match"}), \ F(reason_kv_client_error, {"reason", "kv_client_error"}), F(reason_internal_error, {"reason", "internal_error"}), \ F(reason_other_error, {"reason", "other_error"})) \ M(tiflash_coprocessor_request_handle_seconds, "Bucketed histogram of request handle duration", Histogram, \ - F(type_batch, {{"type", "batch"}}, ExpBuckets{0.0005, 2, 30}), F(type_cop, {{"type", "cop"}}, ExpBuckets{0.0005, 2, 30}), \ - F(type_super_batch, {{"type", "super_batch"}}, ExpBuckets{0.0005, 2, 30}), \ - F(type_dispatch_mpp_task, {{"type", "dispatch_mpp_task"}}, ExpBuckets{0.0005, 2, 30}), \ - F(type_mpp_establish_conn, {{"type", "mpp_establish_conn"}}, ExpBuckets{0.0005, 2, 30}), \ - F(type_cancel_mpp_task, {{"type", "cancel_mpp_task"}}, ExpBuckets{0.0005, 2, 30}), \ - F(type_run_mpp_task, {{"type", "run_mpp_task"}}, ExpBuckets{0.0005, 2, 30})) \ + F(type_batch, {{"type", "batch"}}, ExpBuckets{0.001, 2, 20}), F(type_cop, {{"type", "cop"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_super_batch, {{"type", "super_batch"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_dispatch_mpp_task, {{"type", "dispatch_mpp_task"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_mpp_establish_conn, {{"type", "mpp_establish_conn"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_cancel_mpp_task, {{"type", "cancel_mpp_task"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_run_mpp_task, {{"type", "run_mpp_task"}}, ExpBuckets{0.001, 2, 20})) \ M(tiflash_coprocessor_response_bytes, "Total bytes of response body", Counter) \ M(tiflash_schema_version, "Current version of tiflash cached schema", Gauge) \ M(tiflash_schema_applying, "Whether the schema is applying or not (holding lock)", Gauge) \ @@ -95,21 +95,14 @@ namespace DB F(type_alter_column_tp, {"type", "alter_column_type"}), F(type_rename_column, {"type", "rename_column"}), \ F(type_exchange_partition, {"type", "exchange_partition"})) \ M(tiflash_schema_apply_duration_seconds, "Bucketed histogram of ddl apply duration", Histogram, \ - F(type_ddl_apply_duration, {{"req", "ddl_apply_duration"}}, ExpBuckets{0.0005, 2, 20})) \ - M(tiflash_tmt_merge_count, "Total number of TMT engine merge", Counter) \ - M(tiflash_tmt_merge_duration_seconds, "Bucketed histogram of TMT engine merge duration", Histogram, \ - F(type_tmt_merge_duration, {{"type", "tmt_merge_duration"}}, ExpBuckets{0.0005, 2, 20})) \ - M(tiflash_tmt_write_parts_count, "Total number of TMT engine write parts", Counter) \ - M(tiflash_tmt_write_parts_duration_seconds, "Bucketed histogram of TMT engine write parts duration", Histogram, \ - F(type_tmt_write_duration, {{"type", "tmt_write_parts_duration"}}, ExpBuckets{0.0005, 2, 20})) \ - M(tiflash_tmt_read_parts_count, "Total number of TMT engine read parts", Gauge) \ + F(type_ddl_apply_duration, {{"req", "ddl_apply_duration"}}, ExpBuckets{0.001, 2, 20})) \ M(tiflash_raft_read_index_count, "Total number of raft read index", Counter) \ M(tiflash_raft_read_index_duration_seconds, "Bucketed histogram of raft read index duration", Histogram, \ - F(type_raft_read_index_duration, {{"type", "tmt_raft_read_index_duration"}}, ExpBuckets{0.0005, 2, 20})) \ + F(type_raft_read_index_duration, {{"type", "tmt_raft_read_index_duration"}}, ExpBuckets{0.001, 2, 20})) \ M(tiflash_raft_wait_index_duration_seconds, "Bucketed histogram of raft wait index duration", Histogram, \ - F(type_raft_wait_index_duration, {{"type", "tmt_raft_wait_index_duration"}}, ExpBuckets{0.0005, 2, 20})) \ + F(type_raft_wait_index_duration, {{"type", "tmt_raft_wait_index_duration"}}, ExpBuckets{0.001, 2, 20})) \ M(tiflash_syncing_data_freshness, "The freshness of tiflash data with tikv data", Histogram, \ - F(type_syncing_data_freshness, {{"type", "data_freshness"}}, ExpBuckets{0.0005, 2, 20})) \ + F(type_syncing_data_freshness, {{"type", "data_freshness"}}, ExpBuckets{0.001, 2, 20})) \ M(tiflash_storage_write_amplification, "The data write amplification in storage engine", Gauge) \ M(tiflash_storage_read_tasks_count, "Total number of storage engine read tasks", Counter) \ M(tiflash_storage_command_count, "Total number of storage's command, such as delete range / shutdown /startup", Counter, \ @@ -122,16 +115,16 @@ namespace DB F(type_seg_split, {"type", "seg_split"}), F(type_seg_split_fg, {"type", "seg_split_fg"}), \ F(type_seg_merge, {"type", "seg_merge"}), F(type_place_index_update, {"type", "place_index_update"})) \ M(tiflash_storage_subtask_duration_seconds, "Bucketed histogram of storage's sub task duration", Histogram, \ - F(type_delta_merge, {{"type", "delta_merge"}}, ExpBuckets{0.0005, 2, 20}), \ - F(type_delta_merge_fg, {{"type", "delta_merge_fg"}}, ExpBuckets{0.0005, 2, 20}), \ - F(type_delta_merge_fg_rpc, {{"type", "delta_merge_fg_rpc"}}, ExpBuckets{0.0005, 2, 20}), \ - F(type_delta_merge_bg_gc, {{"type", "delta_merge_bg_gc"}}, ExpBuckets{0.0005, 2, 20}), \ - F(type_delta_compact, {{"type", "delta_compact"}}, ExpBuckets{0.0005, 2, 20}), \ - F(type_delta_flush, {{"type", "delta_flush"}}, ExpBuckets{0.0005, 2, 20}), \ - F(type_seg_split, {{"type", "seg_split"}}, ExpBuckets{0.0005, 2, 20}), \ - F(type_seg_split_fg, {{"type", "seg_split_fg"}}, ExpBuckets{0.0005, 2, 20}), \ - F(type_seg_merge, {{"type", "seg_merge"}}, ExpBuckets{0.0005, 2, 20}), \ - F(type_place_index_update, {{"type", "place_index_update"}}, ExpBuckets{0.0005, 2, 20})) \ + F(type_delta_merge, {{"type", "delta_merge"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_delta_merge_fg, {{"type", "delta_merge_fg"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_delta_merge_fg_rpc, {{"type", "delta_merge_fg_rpc"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_delta_merge_bg_gc, {{"type", "delta_merge_bg_gc"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_delta_compact, {{"type", "delta_compact"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_delta_flush, {{"type", "delta_flush"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_seg_split, {{"type", "seg_split"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_seg_split_fg, {{"type", "seg_split_fg"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_seg_merge, {{"type", "seg_merge"}}, ExpBuckets{0.001, 2, 20}), \ + F(type_place_index_update, {{"type", "place_index_update"}}, ExpBuckets{0.001, 2, 20})) \ M(tiflash_storage_throughput_bytes, "Calculate the throughput of tasks of storage in bytes", Gauge, /**/ \ F(type_write, {"type", "write"}), /**/ \ F(type_ingest, {"type", "ingest"}), /**/ \ @@ -145,8 +138,8 @@ namespace DB F(type_split, {"type", "split"}), /**/ \ F(type_merge, {"type", "merge"})) /**/ \ M(tiflash_storage_write_stall_duration_seconds, "The write stall duration of storage, in seconds", Histogram, /**/ \ - F(type_write, {{"type", "write"}}, ExpBuckets{0.0005, 2, 20}), /**/ \ - F(type_delete_range, {{"type", "delete_range"}}, ExpBuckets{0.0005, 2, 20})) /**/ \ + F(type_write, {{"type", "write"}}, ExpBuckets{0.001, 2, 20}), /**/ \ + F(type_delete_range, {{"type", "delete_range"}}, ExpBuckets{0.001, 2, 20})) /**/ \ M(tiflash_storage_page_gc_count, "Total number of page's gc execution.", Counter, \ F(type_exec, {"type", "exec"}), \ F(type_low_write, {"type", "low_write"}), \ @@ -170,7 +163,7 @@ namespace DB Histogram, /* these command usually cost servel seconds, increase the start bucket to 50ms */ \ F(type_ingest_sst, {{"type", "ingest_sst"}}, ExpBuckets{0.05, 2, 10}), \ F(type_apply_snapshot_predecode, {{"type", "snapshot_predecode"}}, ExpBuckets{0.05, 2, 10}), \ - F(type_apply_snapshot_predecode_sst2dt, {{"type", "snapshot_predecode_sst2dt"}}, ExpBuckets{0.05, 2, 10}), \ + F(type_apply_snapshot_predecode_sst2dt, {{"type", "snapshot_predecode_sst2dt"}}, ExpBuckets{0.05, 2, 10}), \ F(type_apply_snapshot_flush, {{"type", "snapshot_flush"}}, ExpBuckets{0.05, 2, 10})) \ M(tiflash_raft_process_keys, "Total number of keys processed in some types of Raft commands", Counter, \ F(type_apply_snapshot, {"type", "apply_snapshot"}), F(type_ingest_sst, {"type", "ingest_sst"})) \ @@ -212,7 +205,7 @@ namespace DB F(type_thread_hard_limit, {"type", "thread_hard_limit"}), \ F(type_hard_limit_exceeded_count, {"type", "hard_limit_exceeded_count"})) \ M(tiflash_task_scheduler_waiting_duration_seconds, "Bucketed histogram of task waiting for scheduling duration", Histogram, \ - F(type_task_scheduler_waiting_duration, {{"type", "task_waiting_duration"}}, ExpBuckets{0.0005, 2, 20})) + F(type_task_scheduler_waiting_duration, {{"type", "task_waiting_duration"}}, ExpBuckets{0.001, 2, 20})) // clang-format on diff --git a/dbms/src/DataStreams/AggregatingBlockInputStream.cpp b/dbms/src/DataStreams/AggregatingBlockInputStream.cpp index 0d9e907c5f4..4cd09d1ea63 100644 --- a/dbms/src/DataStreams/AggregatingBlockInputStream.cpp +++ b/dbms/src/DataStreams/AggregatingBlockInputStream.cpp @@ -17,12 +17,6 @@ #include #include - -namespace ProfileEvents -{ -extern const Event ExternalAggregationMerge; -} - namespace DB { Block AggregatingBlockInputStream::getHeader() const @@ -56,8 +50,6 @@ Block AggregatingBlockInputStream::readImpl() * then read and merge them, spending the minimum amount of memory. */ - ProfileEvents::increment(ProfileEvents::ExternalAggregationMerge); - if (!isCancelled()) { /// Flush data in the RAM to disk also. It's easier than merging on-disk and RAM data. diff --git a/dbms/src/DataStreams/AsynchronousBlockInputStream.h b/dbms/src/DataStreams/AsynchronousBlockInputStream.h index e75d1603648..5b373c26e95 100644 --- a/dbms/src/DataStreams/AsynchronousBlockInputStream.h +++ b/dbms/src/DataStreams/AsynchronousBlockInputStream.h @@ -22,12 +22,6 @@ #include #include - -namespace CurrentMetrics -{ -extern const Metric QueryThread; -} - namespace DB { /** Executes another BlockInputStream in a separate thread. @@ -141,8 +135,6 @@ class AsynchronousBlockInputStream : public IProfilingBlockInputStream /// Calculations that can be performed in a separate thread void calculate() { - CurrentMetrics::Increment metric_increment{CurrentMetrics::QueryThread}; - try { if (first) diff --git a/dbms/src/DataStreams/CountingBlockOutputStream.cpp b/dbms/src/DataStreams/CountingBlockOutputStream.cpp index 26bc5a4566f..52dc6b598b9 100644 --- a/dbms/src/DataStreams/CountingBlockOutputStream.cpp +++ b/dbms/src/DataStreams/CountingBlockOutputStream.cpp @@ -12,20 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include #include - - -namespace ProfileEvents -{ - extern const Event InsertedRows; - extern const Event InsertedBytes; -} - +#include namespace DB { - void CountingBlockOutputStream::write(const Block & block) { stream->write(block); @@ -33,9 +24,6 @@ void CountingBlockOutputStream::write(const Block & block) Progress local_progress(block.rows(), block.bytes(), 0); progress.incrementPiecewiseAtomically(local_progress); - ProfileEvents::increment(ProfileEvents::InsertedRows, local_progress.rows); - ProfileEvents::increment(ProfileEvents::InsertedBytes, local_progress.bytes); - if (process_elem) process_elem->updateProgressOut(local_progress); @@ -43,4 +31,4 @@ void CountingBlockOutputStream::write(const Block & block) progress_callback(local_progress); } -} +} // namespace DB diff --git a/dbms/src/DataStreams/MergeSortingBlockInputStream.cpp b/dbms/src/DataStreams/MergeSortingBlockInputStream.cpp index e79426f686e..cf8db3f8711 100644 --- a/dbms/src/DataStreams/MergeSortingBlockInputStream.cpp +++ b/dbms/src/DataStreams/MergeSortingBlockInputStream.cpp @@ -19,13 +19,6 @@ #include #include - -namespace ProfileEvents -{ -extern const Event ExternalSortWritePart; -extern const Event ExternalSortMerge; -} // namespace ProfileEvents - namespace DB { /** Remove constant columns from block. @@ -136,7 +129,6 @@ Block MergeSortingBlockInputStream::readImpl() MergeSortingBlocksBlockInputStream block_in(blocks, description, log->identifier(), max_merged_block_size, limit); LOG_FMT_INFO(log, "Sorting and writing part of data into temporary file {}", path); - ProfileEvents::increment(ProfileEvents::ExternalSortWritePart); copyData(block_in, block_out, &is_cancelled); /// NOTE. Possibly limit disk usage. LOG_FMT_INFO(log, "Done writing part of data into temporary file {}", path); @@ -155,7 +147,6 @@ Block MergeSortingBlockInputStream::readImpl() else { /// If there was temporary files. - ProfileEvents::increment(ProfileEvents::ExternalSortMerge); LOG_FMT_INFO(log, "There are {} temporary sorted parts to merge.", temporary_files.size()); diff --git a/dbms/src/DataStreams/MergingAggregatedMemoryEfficientBlockInputStream.cpp b/dbms/src/DataStreams/MergingAggregatedMemoryEfficientBlockInputStream.cpp index 5d0b677b792..3a1cc1eed31 100644 --- a/dbms/src/DataStreams/MergingAggregatedMemoryEfficientBlockInputStream.cpp +++ b/dbms/src/DataStreams/MergingAggregatedMemoryEfficientBlockInputStream.cpp @@ -19,13 +19,6 @@ #include - -namespace CurrentMetrics -{ -extern const Metric QueryThread; -} - - namespace DB { /** Scheme of operation: @@ -156,7 +149,7 @@ void MergingAggregatedMemoryEfficientBlockInputStream::cancel(bool kill) for (auto & input : inputs) { - if (IProfilingBlockInputStream * child = dynamic_cast(input.stream.get())) + if (auto * child = dynamic_cast(input.stream.get())) { try { @@ -198,7 +191,6 @@ void MergingAggregatedMemoryEfficientBlockInputStream::start() reading_pool->schedule( wrapInvocable(true, [&child] { - CurrentMetrics::Increment metric_increment{CurrentMetrics::QueryThread}; child->readPrefix(); })); } @@ -309,8 +301,6 @@ void MergingAggregatedMemoryEfficientBlockInputStream::finalize() void MergingAggregatedMemoryEfficientBlockInputStream::mergeThread() { - CurrentMetrics::Increment metric_increment{CurrentMetrics::QueryThread}; - try { while (!parallel_merge_data->finish) @@ -490,7 +480,6 @@ MergingAggregatedMemoryEfficientBlockInputStream::BlocksToMerge MergingAggregate if (need_that_input(input)) { reading_pool->schedule(wrapInvocable(true, [&input, &read_from_input] { - CurrentMetrics::Increment metric_increment{CurrentMetrics::QueryThread}; read_from_input(input); })); } diff --git a/dbms/src/DataStreams/ParallelAggregatingBlockInputStream.cpp b/dbms/src/DataStreams/ParallelAggregatingBlockInputStream.cpp index 1a59b979c29..f4f8dfc1338 100644 --- a/dbms/src/DataStreams/ParallelAggregatingBlockInputStream.cpp +++ b/dbms/src/DataStreams/ParallelAggregatingBlockInputStream.cpp @@ -20,13 +20,6 @@ #include #include - -namespace ProfileEvents -{ -extern const Event ExternalAggregationMerge; -} - - namespace DB { ParallelAggregatingBlockInputStream::ParallelAggregatingBlockInputStream( @@ -101,8 +94,6 @@ Block ParallelAggregatingBlockInputStream::readImpl() * then read and merge them, spending the minimum amount of memory. */ - ProfileEvents::increment(ProfileEvents::ExternalAggregationMerge); - const auto & files = aggregator.getTemporaryFiles(); BlockInputStreams input_streams; for (const auto & file : files.files) diff --git a/dbms/src/DataStreams/ParallelInputsProcessor.h b/dbms/src/DataStreams/ParallelInputsProcessor.h index 0e839093cd7..34c70a7085e 100644 --- a/dbms/src/DataStreams/ParallelInputsProcessor.h +++ b/dbms/src/DataStreams/ParallelInputsProcessor.h @@ -46,11 +46,6 @@ * then read block from source and then put source back to queue of available sources. */ -namespace CurrentMetrics -{ -extern const Metric QueryThread; -} - namespace DB { /** Union mode. @@ -208,8 +203,6 @@ class ParallelInputsProcessor { std::exception_ptr exception; - CurrentMetrics::Increment metric_increment{CurrentMetrics::QueryThread}; - try { while (!finish) diff --git a/dbms/src/Dictionaries/CacheDictionary.cpp b/dbms/src/Dictionaries/CacheDictionary.cpp index 8573bdad6bd..0d7243ede8f 100644 --- a/dbms/src/Dictionaries/CacheDictionary.cpp +++ b/dbms/src/Dictionaries/CacheDictionary.cpp @@ -12,57 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include -#include -#include -#include #include +#include +#include #include -#include +#include #include -#include -#include #include -#include +#include +#include +#include #include #include #include -#include -#include -#include - - -namespace ProfileEvents -{ - extern const Event DictCacheKeysRequested; - extern const Event DictCacheKeysRequestedMiss; - extern const Event DictCacheKeysRequestedFound; - extern const Event DictCacheKeysExpired; - extern const Event DictCacheKeysNotFound; - extern const Event DictCacheKeysHit; - extern const Event DictCacheRequestTimeNs; - extern const Event DictCacheRequests; - extern const Event DictCacheLockWriteNs; - extern const Event DictCacheLockReadNs; -} - -namespace CurrentMetrics -{ - extern const Metric DictCacheRequests; -} +#include +#include +#include +#include +#include +#include namespace DB { - namespace ErrorCodes { - extern const int TYPE_MISMATCH; - extern const int BAD_ARGUMENTS; - extern const int UNSUPPORTED_METHOD; - extern const int LOGICAL_ERROR; -} +extern const int TYPE_MISMATCH; +extern const int BAD_ARGUMENTS; +extern const int UNSUPPORTED_METHOD; +extern const int LOGICAL_ERROR; +} // namespace ErrorCodes inline size_t CacheDictionary::getCellIdx(const Key id) const @@ -73,15 +52,15 @@ inline size_t CacheDictionary::getCellIdx(const Key id) const } -CacheDictionary::CacheDictionary(const std::string & name, const DictionaryStructure & dict_struct, - DictionarySourcePtr source_ptr, const DictionaryLifetime dict_lifetime, - const size_t size) - : name{name}, dict_struct(dict_struct), - source_ptr{std::move(source_ptr)}, dict_lifetime(dict_lifetime), - size{roundUpToPowerOfTwoOrZero(std::max(size, size_t(max_collision_length)))}, - size_overlap_mask{this->size - 1}, - cells{this->size}, - rnd_engine(randomSeed()) +CacheDictionary::CacheDictionary(const std::string & name, const DictionaryStructure & dict_struct, DictionarySourcePtr source_ptr, const DictionaryLifetime dict_lifetime, const size_t size) + : name{name} + , dict_struct(dict_struct) + , source_ptr{std::move(source_ptr)} + , dict_lifetime(dict_lifetime) + , size{roundUpToPowerOfTwoOrZero(std::max(size, size_t(max_collision_length)))} + , size_overlap_mask{this->size - 1} + , cells{this->size} + , rnd_engine(randomSeed()) { if (!this->source_ptr->supportsSelectiveLoad()) throw Exception{ @@ -100,13 +79,19 @@ void CacheDictionary::toParent(const PaddedPODArray & ids, PaddedPODArray(hierarchical_attribute->null_values); - getItemsNumber(*hierarchical_attribute, ids, out, [&] (const size_t) { return null_value; }); + getItemsNumber(*hierarchical_attribute, ids, out, [&](const size_t) { return null_value; }); } /// Allow to use single value in same way as array. -static inline CacheDictionary::Key getAt(const PaddedPODArray & arr, const size_t idx) { return arr[idx]; } -static inline CacheDictionary::Key getAt(const CacheDictionary::Key & value, const size_t) { return value; } +static inline CacheDictionary::Key getAt(const PaddedPODArray & arr, const size_t idx) +{ + return arr[idx]; +} +static inline CacheDictionary::Key getAt(const CacheDictionary::Key & value, const size_t) +{ + return value; +} template @@ -118,7 +103,7 @@ void CacheDictionary::isInImpl( /// Transform all children to parents until ancestor id or null_value will be reached. size_t size = out.size(); - memset(out.data(), 0xFF, size); /// 0xFF means "not calculated" + memset(out.data(), 0xFF, size); /// 0xFF means "not calculated" const auto null_value = std::get(hierarchical_attribute->null_values); @@ -224,19 +209,19 @@ void CacheDictionary::isInConstantVector( } -#define DECLARE(TYPE)\ -void CacheDictionary::get##TYPE(const std::string & attribute_name, const PaddedPODArray & ids, PaddedPODArray & out) const\ -{\ - auto & attribute = getAttribute(attribute_name);\ - if (!isAttributeTypeConvertibleTo(attribute.type, AttributeUnderlyingType::TYPE))\ - throw Exception{\ - name + ": type mismatch: attribute " + attribute_name + " has type " + toString(attribute.type),\ - ErrorCodes::TYPE_MISMATCH};\ - \ - const auto null_value = std::get(attribute.null_values);\ - \ - getItemsNumber(attribute, ids, out, [&] (const size_t) { return null_value; });\ -} +#define DECLARE(TYPE) \ + void CacheDictionary::get##TYPE(const std::string & attribute_name, const PaddedPODArray & ids, PaddedPODArray & out) const \ + { \ + auto & attribute = getAttribute(attribute_name); \ + if (!isAttributeTypeConvertibleTo(attribute.type, AttributeUnderlyingType::TYPE)) \ + throw Exception{ \ + name + ": type mismatch: attribute " + attribute_name + " has type " + toString(attribute.type), \ + ErrorCodes::TYPE_MISMATCH}; \ + \ + const auto null_value = std::get(attribute.null_values); \ + \ + getItemsNumber(attribute, ids, out, [&](const size_t) { return null_value; }); \ + } DECLARE(UInt8) DECLARE(UInt16) DECLARE(UInt32) @@ -260,22 +245,24 @@ void CacheDictionary::getString(const std::string & attribute_name, const Padded const auto null_value = StringRef{std::get(attribute.null_values)}; - getItemsString(attribute, ids, out, [&] (const size_t) { return null_value; }); + getItemsString(attribute, ids, out, [&](const size_t) { return null_value; }); } -#define DECLARE(TYPE)\ -void CacheDictionary::get##TYPE(\ - const std::string & attribute_name, const PaddedPODArray & ids, const PaddedPODArray & def,\ - PaddedPODArray & out) const\ -{\ - auto & attribute = getAttribute(attribute_name);\ - if (!isAttributeTypeConvertibleTo(attribute.type, AttributeUnderlyingType::TYPE))\ - throw Exception{\ - name + ": type mismatch: attribute " + attribute_name + " has type " + toString(attribute.type),\ - ErrorCodes::TYPE_MISMATCH};\ - \ - getItemsNumber(attribute, ids, out, [&] (const size_t row) { return def[row]; });\ -} +#define DECLARE(TYPE) \ + void CacheDictionary::get##TYPE( \ + const std::string & attribute_name, \ + const PaddedPODArray & ids, \ + const PaddedPODArray & def, \ + PaddedPODArray & out) const \ + { \ + auto & attribute = getAttribute(attribute_name); \ + if (!isAttributeTypeConvertibleTo(attribute.type, AttributeUnderlyingType::TYPE)) \ + throw Exception{ \ + name + ": type mismatch: attribute " + attribute_name + " has type " + toString(attribute.type), \ + ErrorCodes::TYPE_MISMATCH}; \ + \ + getItemsNumber(attribute, ids, out, [&](const size_t row) { return def[row]; }); \ + } DECLARE(UInt8) DECLARE(UInt16) DECLARE(UInt32) @@ -290,7 +277,9 @@ DECLARE(Float64) #undef DECLARE void CacheDictionary::getString( - const std::string & attribute_name, const PaddedPODArray & ids, const ColumnString * const def, + const std::string & attribute_name, + const PaddedPODArray & ids, + const ColumnString * const def, ColumnString * const out) const { auto & attribute = getAttribute(attribute_name); @@ -299,21 +288,24 @@ void CacheDictionary::getString( name + ": type mismatch: attribute " + attribute_name + " has type " + toString(attribute.type), ErrorCodes::TYPE_MISMATCH}; - getItemsString(attribute, ids, out, [&] (const size_t row) { return def->getDataAt(row); }); + getItemsString(attribute, ids, out, [&](const size_t row) { return def->getDataAt(row); }); } -#define DECLARE(TYPE)\ -void CacheDictionary::get##TYPE(\ - const std::string & attribute_name, const PaddedPODArray & ids, const TYPE def, PaddedPODArray & out) const\ -{\ - auto & attribute = getAttribute(attribute_name);\ - if (!isAttributeTypeConvertibleTo(attribute.type, AttributeUnderlyingType::TYPE))\ - throw Exception{\ - name + ": type mismatch: attribute " + attribute_name + " has type " + toString(attribute.type),\ - ErrorCodes::TYPE_MISMATCH};\ - \ - getItemsNumber(attribute, ids, out, [&] (const size_t) { return def; });\ -} +#define DECLARE(TYPE) \ + void CacheDictionary::get##TYPE( \ + const std::string & attribute_name, \ + const PaddedPODArray & ids, \ + const TYPE def, \ + PaddedPODArray & out) const \ + { \ + auto & attribute = getAttribute(attribute_name); \ + if (!isAttributeTypeConvertibleTo(attribute.type, AttributeUnderlyingType::TYPE)) \ + throw Exception{ \ + name + ": type mismatch: attribute " + attribute_name + " has type " + toString(attribute.type), \ + ErrorCodes::TYPE_MISMATCH}; \ + \ + getItemsNumber(attribute, ids, out, [&](const size_t) { return def; }); \ + } DECLARE(UInt8) DECLARE(UInt16) DECLARE(UInt32) @@ -328,7 +320,9 @@ DECLARE(Float64) #undef DECLARE void CacheDictionary::getString( - const std::string & attribute_name, const PaddedPODArray & ids, const String & def, + const std::string & attribute_name, + const PaddedPODArray & ids, + const String & def, ColumnString * const out) const { auto & attribute = getAttribute(attribute_name); @@ -337,7 +331,7 @@ void CacheDictionary::getString( name + ": type mismatch: attribute " + attribute_name + " has type " + toString(attribute.type), ErrorCodes::TYPE_MISMATCH}; - getItemsString(attribute, ids, out, [&] (const size_t) { return StringRef{def}; }); + getItemsString(attribute, ids, out, [&](const size_t) { return StringRef{def}; }); } @@ -390,8 +384,6 @@ void CacheDictionary::has(const PaddedPODArray & ids, PaddedPODArray const auto rows = ext::size(ids); { - const ProfilingScopedReadRWLock read_lock{rw_lock, ProfileEvents::DictCacheLockReadNs}; - const auto now = std::chrono::system_clock::now(); /// fetch up-to-date values, decide which ones require update for (const auto row : ext::range(0, rows)) @@ -416,10 +408,6 @@ void CacheDictionary::has(const PaddedPODArray & ids, PaddedPODArray } } - ProfileEvents::increment(ProfileEvents::DictCacheKeysExpired, cache_expired); - ProfileEvents::increment(ProfileEvents::DictCacheKeysNotFound, cache_not_found); - ProfileEvents::increment(ProfileEvents::DictCacheKeysHit, cache_hit); - query_count.fetch_add(rows, std::memory_order_relaxed); hit_count.fetch_add(rows - outdated_ids.size(), std::memory_order_release); @@ -427,21 +415,19 @@ void CacheDictionary::has(const PaddedPODArray & ids, PaddedPODArray return; std::vector required_ids(outdated_ids.size()); - std::transform(std::begin(outdated_ids), std::end(outdated_ids), std::begin(required_ids), - [] (auto & pair) { return pair.first; }); + std::transform(std::begin(outdated_ids), std::end(outdated_ids), std::begin(required_ids), [](auto & pair) { return pair.first; }); /// request new values - update(required_ids, - [&] (const auto id, const auto) - { - for (const auto row : outdated_ids[id]) - out[row] = true; - }, - [&] (const auto id, const auto) - { - for (const auto row : outdated_ids[id]) - out[row] = false; - }); + update( + required_ids, + [&](const auto id, const auto) { + for (const auto row : outdated_ids[id]) + out[row] = true; + }, + [&](const auto id, const auto) { + for (const auto row : outdated_ids[id]) + out[row] = false; + }); } @@ -476,68 +462,68 @@ CacheDictionary::Attribute CacheDictionary::createAttributeWithType(const Attrib switch (type) { - case AttributeUnderlyingType::UInt8: - std::get(attr.null_values) = null_value.get(); - std::get>(attr.arrays) = std::make_unique>(size); - bytes_allocated += size * sizeof(UInt8); - break; - case AttributeUnderlyingType::UInt16: - std::get(attr.null_values) = null_value.get(); - std::get>(attr.arrays) = std::make_unique>(size); - bytes_allocated += size * sizeof(UInt16); - break; - case AttributeUnderlyingType::UInt32: - std::get(attr.null_values) = null_value.get(); - std::get>(attr.arrays) = std::make_unique>(size); - bytes_allocated += size * sizeof(UInt32); - break; - case AttributeUnderlyingType::UInt64: - std::get(attr.null_values) = null_value.get(); - std::get>(attr.arrays) = std::make_unique>(size); - bytes_allocated += size * sizeof(UInt64); - break; - case AttributeUnderlyingType::UInt128: - std::get(attr.null_values) = null_value.get(); - std::get>(attr.arrays) = std::make_unique>(size); - bytes_allocated += size * sizeof(UInt128); - break; - case AttributeUnderlyingType::Int8: - std::get(attr.null_values) = null_value.get(); - std::get>(attr.arrays) = std::make_unique>(size); - bytes_allocated += size * sizeof(Int8); - break; - case AttributeUnderlyingType::Int16: - std::get(attr.null_values) = null_value.get(); - std::get>(attr.arrays) = std::make_unique>(size); - bytes_allocated += size * sizeof(Int16); - break; - case AttributeUnderlyingType::Int32: - std::get(attr.null_values) = null_value.get(); - std::get>(attr.arrays) = std::make_unique>(size); - bytes_allocated += size * sizeof(Int32); - break; - case AttributeUnderlyingType::Int64: - std::get(attr.null_values) = null_value.get(); - std::get>(attr.arrays) = std::make_unique>(size); - bytes_allocated += size * sizeof(Int64); - break; - case AttributeUnderlyingType::Float32: - std::get(attr.null_values) = null_value.get(); - std::get>(attr.arrays) = std::make_unique>(size); - bytes_allocated += size * sizeof(Float32); - break; - case AttributeUnderlyingType::Float64: - std::get(attr.null_values) = null_value.get(); - std::get>(attr.arrays) = std::make_unique>(size); - bytes_allocated += size * sizeof(Float64); - break; - case AttributeUnderlyingType::String: - std::get(attr.null_values) = null_value.get(); - std::get>(attr.arrays) = std::make_unique>(size); - bytes_allocated += size * sizeof(StringRef); - if (!string_arena) - string_arena = std::make_unique(); - break; + case AttributeUnderlyingType::UInt8: + std::get(attr.null_values) = null_value.get(); + std::get>(attr.arrays) = std::make_unique>(size); + bytes_allocated += size * sizeof(UInt8); + break; + case AttributeUnderlyingType::UInt16: + std::get(attr.null_values) = null_value.get(); + std::get>(attr.arrays) = std::make_unique>(size); + bytes_allocated += size * sizeof(UInt16); + break; + case AttributeUnderlyingType::UInt32: + std::get(attr.null_values) = null_value.get(); + std::get>(attr.arrays) = std::make_unique>(size); + bytes_allocated += size * sizeof(UInt32); + break; + case AttributeUnderlyingType::UInt64: + std::get(attr.null_values) = null_value.get(); + std::get>(attr.arrays) = std::make_unique>(size); + bytes_allocated += size * sizeof(UInt64); + break; + case AttributeUnderlyingType::UInt128: + std::get(attr.null_values) = null_value.get(); + std::get>(attr.arrays) = std::make_unique>(size); + bytes_allocated += size * sizeof(UInt128); + break; + case AttributeUnderlyingType::Int8: + std::get(attr.null_values) = null_value.get(); + std::get>(attr.arrays) = std::make_unique>(size); + bytes_allocated += size * sizeof(Int8); + break; + case AttributeUnderlyingType::Int16: + std::get(attr.null_values) = null_value.get(); + std::get>(attr.arrays) = std::make_unique>(size); + bytes_allocated += size * sizeof(Int16); + break; + case AttributeUnderlyingType::Int32: + std::get(attr.null_values) = null_value.get(); + std::get>(attr.arrays) = std::make_unique>(size); + bytes_allocated += size * sizeof(Int32); + break; + case AttributeUnderlyingType::Int64: + std::get(attr.null_values) = null_value.get(); + std::get>(attr.arrays) = std::make_unique>(size); + bytes_allocated += size * sizeof(Int64); + break; + case AttributeUnderlyingType::Float32: + std::get(attr.null_values) = null_value.get(); + std::get>(attr.arrays) = std::make_unique>(size); + bytes_allocated += size * sizeof(Float32); + break; + case AttributeUnderlyingType::Float64: + std::get(attr.null_values) = null_value.get(); + std::get>(attr.arrays) = std::make_unique>(size); + bytes_allocated += size * sizeof(Float64); + break; + case AttributeUnderlyingType::String: + std::get(attr.null_values) = null_value.get(); + std::get>(attr.arrays) = std::make_unique>(size); + bytes_allocated += size * sizeof(StringRef); + if (!string_arena) + string_arena = std::make_unique(); + break; } return attr; @@ -551,8 +537,8 @@ void CacheDictionary::getItemsNumber( PaddedPODArray & out, DefaultGetter && get_default) const { - if (false) {} -#define DISPATCH(TYPE) \ + if (false) {} // NOLINT +#define DISPATCH(TYPE) \ else if (attribute.type == AttributeUnderlyingType::TYPE) \ getItemsNumberImpl(attribute, ids, out, std::forward(get_default)); DISPATCH(UInt8) @@ -567,8 +553,7 @@ void CacheDictionary::getItemsNumber( DISPATCH(Float32) DISPATCH(Float64) #undef DISPATCH - else - throw Exception("Unexpected type of attribute: " + toString(attribute.type), ErrorCodes::LOGICAL_ERROR); + else throw Exception("Unexpected type of attribute: " + toString(attribute.type), ErrorCodes::LOGICAL_ERROR); } template @@ -586,8 +571,6 @@ void CacheDictionary::getItemsNumberImpl( size_t cache_expired = 0, cache_not_found = 0, cache_hit = 0; { - const ProfilingScopedReadRWLock read_lock{rw_lock, ProfileEvents::DictCacheLockReadNs}; - const auto now = std::chrono::system_clock::now(); /// fetch up-to-date values, decide which ones require update for (const auto row : ext::range(0, rows)) @@ -618,10 +601,6 @@ void CacheDictionary::getItemsNumberImpl( } } - ProfileEvents::increment(ProfileEvents::DictCacheKeysExpired, cache_expired); - ProfileEvents::increment(ProfileEvents::DictCacheKeysNotFound, cache_not_found); - ProfileEvents::increment(ProfileEvents::DictCacheKeysHit, cache_hit); - query_count.fetch_add(rows, std::memory_order_relaxed); hit_count.fetch_add(rows - outdated_ids.size(), std::memory_order_release); @@ -629,23 +608,21 @@ void CacheDictionary::getItemsNumberImpl( return; std::vector required_ids(outdated_ids.size()); - std::transform(std::begin(outdated_ids), std::end(outdated_ids), std::begin(required_ids), - [] (auto & pair) { return pair.first; }); + std::transform(std::begin(outdated_ids), std::end(outdated_ids), std::begin(required_ids), [](auto & pair) { return pair.first; }); /// request new values - update(required_ids, - [&] (const auto id, const auto cell_idx) - { - const auto attribute_value = attribute_array[cell_idx]; + update( + required_ids, + [&](const auto id, const auto cell_idx) { + const auto attribute_value = attribute_array[cell_idx]; - for (const auto row : outdated_ids[id]) - out[row] = static_cast(attribute_value); - }, - [&] (const auto id, const auto) - { - for (const auto row : outdated_ids[id]) - out[row] = get_default(row); - }); + for (const auto row : outdated_ids[id]) + out[row] = static_cast(attribute_value); // NOLINT + }, + [&](const auto id, const auto) { + for (const auto row : outdated_ids[id]) + out[row] = get_default(row); + }); } template @@ -666,8 +643,6 @@ void CacheDictionary::getItemsString( /// perform optimistic version, fallback to pessimistic if failed { - const ProfilingScopedReadRWLock read_lock{rw_lock, ProfileEvents::DictCacheLockReadNs}; - const auto now = std::chrono::system_clock::now(); /// fetch up-to-date values, discard on fail for (const auto row : ext::range(0, rows)) @@ -710,8 +685,6 @@ void CacheDictionary::getItemsString( size_t total_length = 0; size_t cache_expired = 0, cache_not_found = 0, cache_hit = 0; { - const ProfilingScopedReadRWLock read_lock{rw_lock, ProfileEvents::DictCacheLockReadNs}; - const auto now = std::chrono::system_clock::now(); for (const auto row : ext::range(0, ids.size())) { @@ -741,10 +714,6 @@ void CacheDictionary::getItemsString( } } - ProfileEvents::increment(ProfileEvents::DictCacheKeysExpired, cache_expired); - ProfileEvents::increment(ProfileEvents::DictCacheKeysNotFound, cache_not_found); - ProfileEvents::increment(ProfileEvents::DictCacheKeysHit, cache_hit); - query_count.fetch_add(rows, std::memory_order_relaxed); hit_count.fetch_add(rows - outdated_ids.size(), std::memory_order_release); @@ -752,22 +721,20 @@ void CacheDictionary::getItemsString( if (!outdated_ids.empty()) { std::vector required_ids(outdated_ids.size()); - std::transform(std::begin(outdated_ids), std::end(outdated_ids), std::begin(required_ids), - [] (auto & pair) { return pair.first; }); - - update(required_ids, - [&] (const auto id, const auto cell_idx) - { - const auto attribute_value = attribute_array[cell_idx]; - - map[id] = String{attribute_value}; - total_length += (attribute_value.size + 1) * outdated_ids[id].size(); - }, - [&] (const auto id, const auto) - { - for (const auto row : outdated_ids[id]) - total_length += get_default(row).size + 1; - }); + std::transform(std::begin(outdated_ids), std::end(outdated_ids), std::begin(required_ids), [](auto & pair) { return pair.first; }); + + update( + required_ids, + [&](const auto id, const auto cell_idx) { + const auto attribute_value = attribute_array[cell_idx]; + + map[id] = String{attribute_value}; + total_length += (attribute_value.size + 1) * outdated_ids[id].size(); + }, + [&](const auto id, const auto) { + for (const auto row : outdated_ids[id]) + total_length += get_default(row).size + 1; + }); } out->getChars().reserve(total_length); @@ -790,18 +757,13 @@ void CacheDictionary::update( { std::unordered_map remaining_ids{requested_ids.size()}; for (const auto id : requested_ids) - remaining_ids.insert({ id, 0 }); + remaining_ids.insert({id, 0}); - std::uniform_int_distribution distribution - { + std::uniform_int_distribution distribution{ dict_lifetime.min_sec, - dict_lifetime.max_sec - }; - - const ProfilingScopedWriteRWLock write_lock{rw_lock, ProfileEvents::DictCacheLockWriteNs}; + dict_lifetime.max_sec}; { - CurrentMetrics::Increment metric_increment{CurrentMetrics::DictCacheRequests}; Stopwatch watch; auto stream = source_ptr->loadIds(requested_ids); stream->readPrefix(); @@ -810,7 +772,7 @@ void CacheDictionary::update( while (const auto block = stream->read()) { - const auto id_column = typeid_cast(block.safeGetByPosition(0).column.get()); + const auto * id_column = typeid_cast(block.safeGetByPosition(0).column.get()); if (!id_column) throw Exception{ name + ": id column has type different from UInt64.", @@ -819,8 +781,7 @@ void CacheDictionary::update( const auto & ids = id_column->getData(); /// cache column pointers - const auto column_ptrs = ext::map(ext::range(0, attributes.size()), [&block] (size_t i) - { + const auto column_ptrs = ext::map(ext::range(0, attributes.size()), [&block](size_t i) { return block.safeGetByPosition(i + 1).column.get(); }); @@ -859,9 +820,6 @@ void CacheDictionary::update( } stream->readSuffix(); - - ProfileEvents::increment(ProfileEvents::DictCacheKeysRequested, requested_ids.size()); - ProfileEvents::increment(ProfileEvents::DictCacheRequestTimeNs, watch.elapsed()); } size_t not_found_num = 0, found_num = 0; @@ -903,10 +861,6 @@ void CacheDictionary::update( /// inform caller that the cell has not been found on_id_not_found(id, cell_idx); } - - ProfileEvents::increment(ProfileEvents::DictCacheKeysRequestedMiss, not_found_num); - ProfileEvents::increment(ProfileEvents::DictCacheKeysRequestedFound, found_num); - ProfileEvents::increment(ProfileEvents::DictCacheRequests); } @@ -914,32 +868,54 @@ void CacheDictionary::setDefaultAttributeValue(Attribute & attribute, const Key { switch (attribute.type) { - case AttributeUnderlyingType::UInt8: std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); break; - case AttributeUnderlyingType::UInt16: std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); break; - case AttributeUnderlyingType::UInt32: std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); break; - case AttributeUnderlyingType::UInt64: std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); break; - case AttributeUnderlyingType::UInt128: std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); break; - case AttributeUnderlyingType::Int8: std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); break; - case AttributeUnderlyingType::Int16: std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); break; - case AttributeUnderlyingType::Int32: std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); break; - case AttributeUnderlyingType::Int64: std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); break; - case AttributeUnderlyingType::Float32: std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); break; - case AttributeUnderlyingType::Float64: std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); break; - case AttributeUnderlyingType::String: - { - const auto & null_value_ref = std::get(attribute.null_values); - auto & string_ref = std::get>(attribute.arrays)[idx]; - - if (string_ref.data != null_value_ref.data()) - { - if (string_ref.data) - string_arena->free(const_cast(string_ref.data), string_ref.size); + case AttributeUnderlyingType::UInt8: + std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); + break; + case AttributeUnderlyingType::UInt16: + std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); + break; + case AttributeUnderlyingType::UInt32: + std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); + break; + case AttributeUnderlyingType::UInt64: + std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); + break; + case AttributeUnderlyingType::UInt128: + std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); + break; + case AttributeUnderlyingType::Int8: + std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); + break; + case AttributeUnderlyingType::Int16: + std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); + break; + case AttributeUnderlyingType::Int32: + std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); + break; + case AttributeUnderlyingType::Int64: + std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); + break; + case AttributeUnderlyingType::Float32: + std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); + break; + case AttributeUnderlyingType::Float64: + std::get>(attribute.arrays)[idx] = std::get(attribute.null_values); + break; + case AttributeUnderlyingType::String: + { + const auto & null_value_ref = std::get(attribute.null_values); + auto & string_ref = std::get>(attribute.arrays)[idx]; - string_ref = StringRef{null_value_ref}; - } + if (string_ref.data != null_value_ref.data()) + { + if (string_ref.data) + string_arena->free(const_cast(string_ref.data), string_ref.size); - break; + string_ref = StringRef{null_value_ref}; } + + break; + } } } @@ -947,39 +923,61 @@ void CacheDictionary::setAttributeValue(Attribute & attribute, const Key idx, co { switch (attribute.type) { - case AttributeUnderlyingType::UInt8: std::get>(attribute.arrays)[idx] = value.get(); break; - case AttributeUnderlyingType::UInt16: std::get>(attribute.arrays)[idx] = value.get(); break; - case AttributeUnderlyingType::UInt32: std::get>(attribute.arrays)[idx] = value.get(); break; - case AttributeUnderlyingType::UInt64: std::get>(attribute.arrays)[idx] = value.get(); break; - case AttributeUnderlyingType::UInt128: std::get>(attribute.arrays)[idx] = value.get(); break; - case AttributeUnderlyingType::Int8: std::get>(attribute.arrays)[idx] = value.get(); break; - case AttributeUnderlyingType::Int16: std::get>(attribute.arrays)[idx] = value.get(); break; - case AttributeUnderlyingType::Int32: std::get>(attribute.arrays)[idx] = value.get(); break; - case AttributeUnderlyingType::Int64: std::get>(attribute.arrays)[idx] = value.get(); break; - case AttributeUnderlyingType::Float32: std::get>(attribute.arrays)[idx] = value.get(); break; - case AttributeUnderlyingType::Float64: std::get>(attribute.arrays)[idx] = value.get(); break; - case AttributeUnderlyingType::String: - { - const auto & string = value.get(); - auto & string_ref = std::get>(attribute.arrays)[idx]; - const auto & null_value_ref = std::get(attribute.null_values); - - /// free memory unless it points to a null_value - if (string_ref.data && string_ref.data != null_value_ref.data()) - string_arena->free(const_cast(string_ref.data), string_ref.size); + case AttributeUnderlyingType::UInt8: + std::get>(attribute.arrays)[idx] = value.get(); + break; + case AttributeUnderlyingType::UInt16: + std::get>(attribute.arrays)[idx] = value.get(); + break; + case AttributeUnderlyingType::UInt32: + std::get>(attribute.arrays)[idx] = value.get(); + break; + case AttributeUnderlyingType::UInt64: + std::get>(attribute.arrays)[idx] = value.get(); + break; + case AttributeUnderlyingType::UInt128: + std::get>(attribute.arrays)[idx] = value.get(); + break; + case AttributeUnderlyingType::Int8: + std::get>(attribute.arrays)[idx] = value.get(); + break; + case AttributeUnderlyingType::Int16: + std::get>(attribute.arrays)[idx] = value.get(); + break; + case AttributeUnderlyingType::Int32: + std::get>(attribute.arrays)[idx] = value.get(); + break; + case AttributeUnderlyingType::Int64: + std::get>(attribute.arrays)[idx] = value.get(); + break; + case AttributeUnderlyingType::Float32: + std::get>(attribute.arrays)[idx] = value.get(); + break; + case AttributeUnderlyingType::Float64: + std::get>(attribute.arrays)[idx] = value.get(); + break; + case AttributeUnderlyingType::String: + { + const auto & string = value.get(); + auto & string_ref = std::get>(attribute.arrays)[idx]; + const auto & null_value_ref = std::get(attribute.null_values); - const auto size = string.size(); - if (size != 0) - { - auto string_ptr = string_arena->alloc(size + 1); - std::copy(string.data(), string.data() + size + 1, string_ptr); - string_ref = StringRef{string_ptr, size}; - } - else - string_ref = {}; + /// free memory unless it points to a null_value + if (string_ref.data && string_ref.data != null_value_ref.data()) + string_arena->free(const_cast(string_ref.data), string_ref.size); - break; + const auto size = string.size(); + if (size != 0) + { + auto * string_ptr = string_arena->alloc(size + 1); + std::copy(string.data(), string.data() + size + 1, string_ptr); + string_ref = StringRef{string_ptr, size}; } + else + string_ref = {}; + + break; + } } } @@ -989,22 +987,18 @@ CacheDictionary::Attribute & CacheDictionary::getAttribute(const std::string & a if (it == std::end(attribute_index_by_name)) throw Exception{ name + ": no such attribute '" + attribute_name + "'", - ErrorCodes::BAD_ARGUMENTS - }; + ErrorCodes::BAD_ARGUMENTS}; return attributes[it->second]; } bool CacheDictionary::isEmptyCell(const UInt64 idx) const { - return (idx != zero_cell_idx && cells[idx].id == 0) || (cells[idx].data - == ext::safe_bit_cast(CellMetadata::time_point_t())); + return (idx != zero_cell_idx && cells[idx].id == 0) || (cells[idx].data == ext::safe_bit_cast(CellMetadata::time_point_t())); } PaddedPODArray CacheDictionary::getCachedIds() const { - const ProfilingScopedReadRWLock read_lock{rw_lock, ProfileEvents::DictCacheLockReadNs}; - PaddedPODArray array; for (size_t idx = 0; idx < cells.size(); ++idx) { @@ -1024,4 +1018,4 @@ BlockInputStreamPtr CacheDictionary::getBlockInputStream(const Names & column_na } -} +} // namespace DB diff --git a/dbms/src/Dictionaries/ComplexKeyCacheDictionary.cpp b/dbms/src/Dictionaries/ComplexKeyCacheDictionary.cpp index 330ee036136..fb9a94b29a0 100644 --- a/dbms/src/Dictionaries/ComplexKeyCacheDictionary.cpp +++ b/dbms/src/Dictionaries/ComplexKeyCacheDictionary.cpp @@ -12,48 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include #include #include -#include -#include -#include -#include #include -#include -#include - - -namespace ProfileEvents -{ - - extern const Event DictCacheKeysRequested; - extern const Event DictCacheKeysRequestedMiss; - extern const Event DictCacheKeysRequestedFound; - extern const Event DictCacheKeysExpired; - extern const Event DictCacheKeysNotFound; - extern const Event DictCacheKeysHit; - extern const Event DictCacheRequestTimeNs; - extern const Event DictCacheLockWriteNs; - extern const Event DictCacheLockReadNs; -} - -namespace CurrentMetrics -{ - extern const Metric DictCacheRequests; -} +#include +#include +#include +#include +#include +#include +#include +#include namespace DB { - namespace ErrorCodes { - extern const int TYPE_MISMATCH; - extern const int BAD_ARGUMENTS; - extern const int UNSUPPORTED_METHOD; -} +extern const int TYPE_MISMATCH; +extern const int BAD_ARGUMENTS; +extern const int UNSUPPORTED_METHOD; +} // namespace ErrorCodes inline UInt64 ComplexKeyCacheDictionary::getCellIdx(const StringRef key) const @@ -64,13 +43,14 @@ inline UInt64 ComplexKeyCacheDictionary::getCellIdx(const StringRef key) const } -ComplexKeyCacheDictionary::ComplexKeyCacheDictionary(const std::string & name, const DictionaryStructure & dict_struct, - DictionarySourcePtr source_ptr, const DictionaryLifetime dict_lifetime, - const size_t size) - : name{name}, dict_struct(dict_struct), source_ptr{std::move(source_ptr)}, dict_lifetime(dict_lifetime), - size{roundUpToPowerOfTwoOrZero(std::max(size, size_t(max_collision_length)))}, - size_overlap_mask{this->size - 1}, - rnd_engine(randomSeed()) +ComplexKeyCacheDictionary::ComplexKeyCacheDictionary(const std::string & name, const DictionaryStructure & dict_struct, DictionarySourcePtr source_ptr, const DictionaryLifetime dict_lifetime, const size_t size) + : name{name} + , dict_struct(dict_struct) + , source_ptr{std::move(source_ptr)} + , dict_lifetime(dict_lifetime) + , size{roundUpToPowerOfTwoOrZero(std::max(size, size_t(max_collision_length)))} + , size_overlap_mask{this->size - 1} + , rnd_engine(randomSeed()) { if (!this->source_ptr->supportsSelectiveLoad()) throw Exception{ @@ -85,7 +65,9 @@ ComplexKeyCacheDictionary::ComplexKeyCacheDictionary(const ComplexKeyCacheDictio {} void ComplexKeyCacheDictionary::getString( - const std::string & attribute_name, const Columns & key_columns, const DataTypes & key_types, + const std::string & attribute_name, + const Columns & key_columns, + const DataTypes & key_types, ColumnString * out) const { dict_struct.validateKeyTypes(key_types); @@ -98,12 +80,15 @@ void ComplexKeyCacheDictionary::getString( const auto null_value = StringRef{std::get(attribute.null_values)}; - getItemsString(attribute, key_columns, out, [&] (const size_t) { return null_value; }); + getItemsString(attribute, key_columns, out, [&](const size_t) { return null_value; }); } void ComplexKeyCacheDictionary::getString( - const std::string & attribute_name, const Columns & key_columns, const DataTypes & key_types, - const ColumnString * const def, ColumnString * const out) const + const std::string & attribute_name, + const Columns & key_columns, + const DataTypes & key_types, + const ColumnString * const def, + ColumnString * const out) const { dict_struct.validateKeyTypes(key_types); @@ -113,12 +98,15 @@ void ComplexKeyCacheDictionary::getString( name + ": type mismatch: attribute " + attribute_name + " has type " + toString(attribute.type), ErrorCodes::TYPE_MISMATCH}; - getItemsString(attribute, key_columns, out, [&] (const size_t row) { return def->getDataAt(row); }); + getItemsString(attribute, key_columns, out, [&](const size_t row) { return def->getDataAt(row); }); } void ComplexKeyCacheDictionary::getString( - const std::string & attribute_name, const Columns & key_columns, const DataTypes & key_types, - const String & def, ColumnString * const out) const + const std::string & attribute_name, + const Columns & key_columns, + const DataTypes & key_types, + const String & def, + ColumnString * const out) const { dict_struct.validateKeyTypes(key_types); @@ -128,7 +116,7 @@ void ComplexKeyCacheDictionary::getString( name + ": type mismatch: attribute " + attribute_name + " has type " + toString(attribute.type), ErrorCodes::TYPE_MISMATCH}; - getItemsString(attribute, key_columns, out, [&] (const size_t) { return StringRef{def}; }); + getItemsString(attribute, key_columns, out, [&](const size_t) { return StringRef{def}; }); } /// returns cell_idx (always valid for replacing), 'cell is valid' flag, 'cell is outdated' flag, @@ -190,8 +178,6 @@ void ComplexKeyCacheDictionary::has(const Columns & key_columns, const DataTypes size_t cache_expired = 0, cache_not_found = 0, cache_hit = 0; { - const ProfilingScopedReadRWLock read_lock{rw_lock, ProfileEvents::DictCacheLockReadNs}; - const auto now = std::chrono::system_clock::now(); /// fetch up-to-date values, decide which ones require update for (const auto row : ext::range(0, rows_num)) @@ -220,9 +206,6 @@ void ComplexKeyCacheDictionary::has(const Columns & key_columns, const DataTypes } } } - ProfileEvents::increment(ProfileEvents::DictCacheKeysExpired, cache_expired); - ProfileEvents::increment(ProfileEvents::DictCacheKeysNotFound, cache_not_found); - ProfileEvents::increment(ProfileEvents::DictCacheKeysHit, cache_hit); query_count.fetch_add(rows_num, std::memory_order_relaxed); hit_count.fetch_add(rows_num - outdated_keys.size(), std::memory_order_release); @@ -231,18 +214,18 @@ void ComplexKeyCacheDictionary::has(const Columns & key_columns, const DataTypes return; std::vector required_rows(outdated_keys.size()); - std::transform(std::begin(outdated_keys), std::end(outdated_keys), std::begin(required_rows), - [] (auto & pair) { return pair.getMapped().front(); }); + std::transform(std::begin(outdated_keys), std::end(outdated_keys), std::begin(required_rows), [](auto & pair) { return pair.getMapped().front(); }); /// request new values - update(key_columns, keys_array, required_rows, - [&] (const StringRef key, const auto) - { + update( + key_columns, + keys_array, + required_rows, + [&](const StringRef key, const auto) { for (const auto out_idx : outdated_keys[key]) out[out_idx] = true; }, - [&] (const StringRef key, const auto) - { + [&](const StringRef key, const auto) { for (const auto out_idx : outdated_keys[key]) out[out_idx] = false; }); @@ -297,8 +280,11 @@ void ComplexKeyCacheDictionary::freeKey(const StringRef key) const template StringRef ComplexKeyCacheDictionary::placeKeysInPool( - const size_t row, const Columns & key_columns, StringRefs & keys, - const std::vector & key_attributes, Pool & pool) + const size_t row, + const Columns & key_columns, + StringRefs & keys, + const std::vector & key_attributes, + Pool & pool) { const auto keys_size = key_columns.size(); size_t sum_keys_size{}; @@ -337,25 +323,32 @@ StringRef ComplexKeyCacheDictionary::placeKeysInPool( } } - return { place, sum_keys_size }; + return {place, sum_keys_size}; } /// Explicit instantiations. template StringRef ComplexKeyCacheDictionary::placeKeysInPool( - const size_t row, const Columns & key_columns, StringRefs & keys, - const std::vector & key_attributes, Arena & pool); + const size_t row, + const Columns & key_columns, + StringRefs & keys, + const std::vector & key_attributes, + Arena & pool); template StringRef ComplexKeyCacheDictionary::placeKeysInPool( - const size_t row, const Columns & key_columns, StringRefs & keys, - const std::vector & key_attributes, ArenaWithFreeLists & pool); + const size_t row, + const Columns & key_columns, + StringRefs & keys, + const std::vector & key_attributes, + ArenaWithFreeLists & pool); StringRef ComplexKeyCacheDictionary::placeKeysInFixedSizePool( - const size_t row, const Columns & key_columns) const + const size_t row, + const Columns & key_columns) const { - const auto res = fixed_size_keys_pool->alloc(); - auto place = res; + auto * const res = fixed_size_keys_pool->alloc(); + auto * place = res; for (const auto & key_column : key_columns) { @@ -364,36 +357,33 @@ StringRef ComplexKeyCacheDictionary::placeKeysInFixedSizePool( place += key.size; } - return { res, key_size }; + return {res, key_size}; } StringRef ComplexKeyCacheDictionary::copyIntoArena(StringRef src, Arena & arena) { char * allocated = arena.alloc(src.size); memcpy(allocated, src.data, src.size); - return { allocated, src.size }; + return {allocated, src.size}; } StringRef ComplexKeyCacheDictionary::copyKey(const StringRef key) const { - const auto res = key_size_is_fixed ? fixed_size_keys_pool->alloc() : keys_pool->alloc(key.size); + auto * const res = key_size_is_fixed ? fixed_size_keys_pool->alloc() : keys_pool->alloc(key.size); memcpy(res, key.data, key.size); - return { res, key.size }; + return {res, key.size}; } bool ComplexKeyCacheDictionary::isEmptyCell(const UInt64 idx) const { - return (cells[idx].key == StringRef{} && (idx != zero_cell_idx - || cells[idx].data == ext::safe_bit_cast(CellMetadata::time_point_t()))); + return (cells[idx].key == StringRef{} && (idx != zero_cell_idx || cells[idx].data == ext::safe_bit_cast(CellMetadata::time_point_t()))); } BlockInputStreamPtr ComplexKeyCacheDictionary::getBlockInputStream(const Names & column_names, size_t max_block_size) const { std::vector keys; { - const ProfilingScopedReadRWLock read_lock{rw_lock, ProfileEvents::DictCacheLockReadNs}; - for (auto idx : ext::range(0, cells.size())) if (!isEmptyCell(idx) && !cells[idx].isDefault()) @@ -404,4 +394,4 @@ BlockInputStreamPtr ComplexKeyCacheDictionary::getBlockInputStream(const Names & return std::make_shared(shared_from_this(), max_block_size, keys, column_names); } -} +} // namespace DB diff --git a/dbms/src/Dictionaries/ComplexKeyCacheDictionary.h b/dbms/src/Dictionaries/ComplexKeyCacheDictionary.h index feb61261f1d..19fe5214fef 100644 --- a/dbms/src/Dictionaries/ComplexKeyCacheDictionary.h +++ b/dbms/src/Dictionaries/ComplexKeyCacheDictionary.h @@ -14,12 +14,6 @@ #pragma once -#include -#include -#include -#include -#include -#include #include #include #include @@ -29,24 +23,17 @@ #include #include #include + +#include +#include #include #include #include +#include #include - - -namespace ProfileEvents -{ -extern const Event DictCacheKeysRequested; -extern const Event DictCacheKeysRequestedMiss; -extern const Event DictCacheKeysRequestedFound; -extern const Event DictCacheKeysExpired; -extern const Event DictCacheKeysNotFound; -extern const Event DictCacheKeysHit; -extern const Event DictCacheRequestTimeNs; -extern const Event DictCacheLockWriteNs; -extern const Event DictCacheLockReadNs; -} +#include +#include +#include namespace DB { @@ -54,10 +41,10 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase { public: ComplexKeyCacheDictionary(const std::string & name, - const DictionaryStructure & dict_struct, - DictionarySourcePtr source_ptr, - const DictionaryLifetime dict_lifetime, - const size_t size); + const DictionaryStructure & dict_struct, + DictionarySourcePtr source_ptr, + const DictionaryLifetime dict_lifetime, + const size_t size); ComplexKeyCacheDictionary(const ComplexKeyCacheDictionary & other); @@ -144,9 +131,12 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase /// In all functions below, key_columns must be full (non-constant) columns. /// See the requirement in IDataType.h for text-serialization functions. -#define DECLARE(TYPE) \ - void get##TYPE( \ - const std::string & attribute_name, const Columns & key_columns, const DataTypes & key_types, PaddedPODArray & out) const; +#define DECLARE(TYPE) \ + void get##TYPE( \ + const std::string & attribute_name, \ + const Columns & key_columns, \ + const DataTypes & key_types, \ + PaddedPODArray & out) const; DECLARE(UInt8) DECLARE(UInt16) DECLARE(UInt32) @@ -164,10 +154,10 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase #define DECLARE(TYPE) \ void get##TYPE(const std::string & attribute_name, \ - const Columns & key_columns, \ - const DataTypes & key_types, \ - const PaddedPODArray & def, \ - PaddedPODArray & out) const; + const Columns & key_columns, \ + const DataTypes & key_types, \ + const PaddedPODArray & def, \ + PaddedPODArray & out) const; DECLARE(UInt8) DECLARE(UInt16) DECLARE(UInt32) @@ -182,17 +172,17 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase #undef DECLARE void getString(const std::string & attribute_name, - const Columns & key_columns, - const DataTypes & key_types, - const ColumnString * const def, - ColumnString * const out) const; + const Columns & key_columns, + const DataTypes & key_types, + const ColumnString * const def, + ColumnString * const out) const; #define DECLARE(TYPE) \ void get##TYPE(const std::string & attribute_name, \ - const Columns & key_columns, \ - const DataTypes & key_types, \ - const TYPE def, \ - PaddedPODArray & out) const; + const Columns & key_columns, \ + const DataTypes & key_types, \ + const TYPE def, \ + PaddedPODArray & out) const; DECLARE(UInt8) DECLARE(UInt16) DECLARE(UInt32) @@ -207,10 +197,10 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase #undef DECLARE void getString(const std::string & attribute_name, - const Columns & key_columns, - const DataTypes & key_types, - const String & def, - ColumnString * const out) const; + const Columns & key_columns, + const DataTypes & key_types, + const String & def, + ColumnString * const out) const; void has(const Columns & key_columns, const DataTypes & key_types, PaddedPODArray & out) const; @@ -263,17 +253,17 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase AttributeUnderlyingType type; std::tuple null_values; std::tuple, - ContainerPtrType, - ContainerPtrType, - ContainerPtrType, - ContainerPtrType, - ContainerPtrType, - ContainerPtrType, - ContainerPtrType, - ContainerPtrType, - ContainerPtrType, - ContainerPtrType, - ContainerPtrType> + ContainerPtrType, + ContainerPtrType, + ContainerPtrType, + ContainerPtrType, + ContainerPtrType, + ContainerPtrType, + ContainerPtrType, + ContainerPtrType, + ContainerPtrType, + ContainerPtrType, + ContainerPtrType> arrays; }; @@ -283,7 +273,10 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase template void getItemsNumber( - Attribute & attribute, const Columns & key_columns, PaddedPODArray & out, DefaultGetter && get_default) const + Attribute & attribute, + const Columns & key_columns, + PaddedPODArray & out, + DefaultGetter && get_default) const { if (false) { @@ -308,7 +301,10 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase template void getItemsNumberImpl( - Attribute & attribute, const Columns & key_columns, PaddedPODArray & out, DefaultGetter && get_default) const + Attribute & attribute, + const Columns & key_columns, + PaddedPODArray & out, + DefaultGetter && get_default) const { /// Mapping: -> { all indices `i` of `key_columns` such that `key_columns[i]` = } MapType> outdated_keys; @@ -322,8 +318,6 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase size_t cache_expired = 0, cache_not_found = 0, cache_hit = 0; { - const ProfilingScopedReadRWLock read_lock{rw_lock, ProfileEvents::DictCacheLockReadNs}; - const auto now = std::chrono::system_clock::now(); /// fetch up-to-date values, decide which ones require update for (const auto row : ext::range(0, rows_num)) @@ -354,9 +348,6 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase } } } - ProfileEvents::increment(ProfileEvents::DictCacheKeysExpired, cache_expired); - ProfileEvents::increment(ProfileEvents::DictCacheKeysNotFound, cache_not_found); - ProfileEvents::increment(ProfileEvents::DictCacheKeysHit, cache_hit); query_count.fetch_add(rows_num, std::memory_order_relaxed); hit_count.fetch_add(rows_num - outdated_keys.size(), std::memory_order_release); @@ -365,19 +356,21 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase std::vector required_rows(outdated_keys.size()); std::transform( - std::begin(outdated_keys), std::end(outdated_keys), std::begin(required_rows), [](auto & pair) { return pair.getMapped().front(); }); + std::begin(outdated_keys), + std::end(outdated_keys), + std::begin(required_rows), + [](auto & pair) { return pair.getMapped().front(); }); /// request new values - update(key_columns, + update( + key_columns, keys_array, required_rows, - [&](const StringRef key, const size_t cell_idx) - { + [&](const StringRef key, const size_t cell_idx) { for (const auto row : outdated_keys[key]) out[row] = static_cast(attribute_array[cell_idx]); }, - [&](const StringRef key, const size_t) - { + [&](const StringRef key, const size_t) { for (const auto row : outdated_keys[key]) out[row] = get_default(row); }); @@ -400,8 +393,6 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase /// perform optimistic version, fallback to pessimistic if failed { - const ProfilingScopedReadRWLock read_lock{rw_lock, ProfileEvents::DictCacheLockReadNs}; - const auto now = std::chrono::system_clock::now(); /// fetch up-to-date values, discard on fail for (const auto row : ext::range(0, rows_num)) @@ -446,8 +437,6 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase size_t total_length = 0; size_t cache_expired = 0, cache_not_found = 0, cache_hit = 0; { - const ProfilingScopedReadRWLock read_lock{rw_lock, ProfileEvents::DictCacheLockReadNs}; - const auto now = std::chrono::system_clock::now(); for (const auto row : ext::range(0, rows_num)) { @@ -477,9 +466,6 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase } } } - ProfileEvents::increment(ProfileEvents::DictCacheKeysExpired, cache_expired); - ProfileEvents::increment(ProfileEvents::DictCacheKeysNotFound, cache_not_found); - ProfileEvents::increment(ProfileEvents::DictCacheKeysHit, cache_hit); query_count.fetch_add(rows_num, std::memory_order_relaxed); hit_count.fetch_add(rows_num - outdated_keys.size(), std::memory_order_release); @@ -488,16 +474,15 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase if (!outdated_keys.empty()) { std::vector required_rows(outdated_keys.size()); - std::transform(std::begin(outdated_keys), std::end(outdated_keys), std::begin(required_rows), [](auto & pair) - { + std::transform(std::begin(outdated_keys), std::end(outdated_keys), std::begin(required_rows), [](auto & pair) { return pair.getMapped().front(); }); - update(key_columns, + update( + key_columns, keys_array, required_rows, - [&](const StringRef key, const size_t cell_idx) - { + [&](const StringRef key, const size_t cell_idx) { const StringRef attribute_value = attribute_array[cell_idx]; /// We must copy key and value to own memory, because it may be replaced with another @@ -508,8 +493,7 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase map[copied_key] = copied_value; total_length += (attribute_value.size + 1) * outdated_keys[key].size(); }, - [&](const StringRef key, const size_t) - { + [&](const StringRef key, const size_t) { for (const auto row : outdated_keys[key]) total_length += get_default(row).size + 1; }); @@ -521,17 +505,17 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase { const StringRef key = keys_array[row]; const auto it = map.find(key); - const auto string_ref = it != std::end(map) ? it->getMapped(): get_default(row); + const auto string_ref = it != std::end(map) ? it->getMapped() : get_default(row); out->insertData(string_ref.data, string_ref.size); } }; template void update(const Columns & in_key_columns, - const PODArray & in_keys, - const std::vector & in_requested_rows, - PresentKeyHandler && on_cell_updated, - AbsentKeyHandler && on_key_not_found) const + const PODArray & in_keys, + const std::vector & in_requested_rows, + PresentKeyHandler && on_cell_updated, + AbsentKeyHandler && on_key_not_found) const { MapType remaining_keys{in_requested_rows.size()}; for (const auto row : in_requested_rows) @@ -539,7 +523,6 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase std::uniform_int_distribution distribution(dict_lifetime.min_sec, dict_lifetime.max_sec); - const ProfilingScopedWriteRWLock write_lock{rw_lock, ProfileEvents::DictCacheLockWriteNs}; { Stopwatch watch; auto stream = source_ptr->loadKeys(in_key_columns, in_requested_rows); @@ -555,10 +538,11 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase { /// cache column pointers const auto key_columns = ext::map( - ext::range(0, keys_size), [&](const size_t attribute_idx) { return block.safeGetByPosition(attribute_idx).column; }); + ext::range(0, keys_size), + [&](const size_t attribute_idx) { return block.safeGetByPosition(attribute_idx).column; }); const auto attribute_columns = ext::map(ext::range(0, attributes_size), - [&](const size_t attribute_idx) { return block.safeGetByPosition(keys_size + attribute_idx).column; }); + [&](const size_t attribute_idx) { return block.safeGetByPosition(keys_size + attribute_idx).column; }); const auto rows_num = block.rows(); @@ -612,9 +596,6 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase } stream->readSuffix(); - - ProfileEvents::increment(ProfileEvents::DictCacheKeysRequested, in_requested_rows.size()); - ProfileEvents::increment(ProfileEvents::DictCacheRequestTimeNs, watch.elapsed()); } size_t found_num = 0; @@ -671,9 +652,6 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase /// inform caller that the cell has not been found on_key_not_found(key, cell_idx); } - - ProfileEvents::increment(ProfileEvents::DictCacheKeysRequestedMiss, found_num); - ProfileEvents::increment(ProfileEvents::DictCacheKeysRequestedMiss, not_found_num); }; UInt64 getCellIdx(const StringRef key) const; @@ -690,10 +668,10 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase template static StringRef placeKeysInPool(const size_t row, - const Columns & key_columns, - StringRefs & keys, - const std::vector & key_attributes, - Arena & pool); + const Columns & key_columns, + StringRefs & keys, + const std::vector & key_attributes, + Arena & pool); StringRef placeKeysInFixedSizePool(const size_t row, const Columns & key_columns) const; @@ -752,4 +730,4 @@ class ComplexKeyCacheDictionary final : public IDictionaryBase const std::chrono::time_point creation_time = std::chrono::system_clock::now(); }; -} +} // namespace DB diff --git a/dbms/src/Encryption/WriteBufferFromFileProvider.cpp b/dbms/src/Encryption/WriteBufferFromFileProvider.cpp index 4c99b8e24b1..a17dd85d379 100644 --- a/dbms/src/Encryption/WriteBufferFromFileProvider.cpp +++ b/dbms/src/Encryption/WriteBufferFromFileProvider.cpp @@ -19,7 +19,6 @@ namespace ProfileEvents { extern const Event WriteBufferFromFileDescriptorWrite; -extern const Event WriteBufferFromFileDescriptorWriteFailed; extern const Event WriteBufferFromFileDescriptorWriteBytes; } // namespace ProfileEvents @@ -72,8 +71,7 @@ void WriteBufferFromFileProvider::nextImpl() if ((-1 == res || 0 == res) && errno != EINTR) { - ProfileEvents::increment(ProfileEvents::WriteBufferFromFileDescriptorWriteFailed); - throwFromErrno("Cannot write to file " + getFileName(), ErrorCodes::CANNOT_WRITE_TO_FILE_DESCRIPTOR); + throwFromErrno("Cannot write to file " + getFileName(), ErrorCodes::CANNOT_WRITE_TO_FILE_DESCRIPTOR); // NOLINT } if (res > 0) diff --git a/dbms/src/Encryption/createReadBufferFromFileBaseByFileProvider.cpp b/dbms/src/Encryption/createReadBufferFromFileBaseByFileProvider.cpp index b76d58c20cd..1858d474d60 100644 --- a/dbms/src/Encryption/createReadBufferFromFileBaseByFileProvider.cpp +++ b/dbms/src/Encryption/createReadBufferFromFileBaseByFileProvider.cpp @@ -20,10 +20,6 @@ #endif #include #include -namespace ProfileEvents -{ -extern const Event CreatedReadBufferOrdinary; -} namespace DB { @@ -46,7 +42,6 @@ std::unique_ptr createReadBufferFromFileBaseByFileProvid { if ((aio_threshold == 0) || (estimated_size < aio_threshold)) { - ProfileEvents::increment(ProfileEvents::CreatedReadBufferOrdinary); return std::make_unique( file_provider, filename_, @@ -75,7 +70,6 @@ createReadBufferFromFileBaseByFileProvider( size_t checksum_frame_size, int flags_) { - ProfileEvents::increment(ProfileEvents::CreatedReadBufferOrdinary); auto file = file_provider->newRandomAccessFile(filename_, encryption_path_, read_limiter, flags_); auto allocation_size = std::min(estimated_size, checksum_frame_size); switch (checksum_algorithm) diff --git a/dbms/src/Encryption/createWriteBufferFromFileBaseByFileProvider.cpp b/dbms/src/Encryption/createWriteBufferFromFileBaseByFileProvider.cpp index 2f1a2cbaeb8..5e8a6940598 100644 --- a/dbms/src/Encryption/createWriteBufferFromFileBaseByFileProvider.cpp +++ b/dbms/src/Encryption/createWriteBufferFromFileBaseByFileProvider.cpp @@ -20,11 +20,6 @@ #include #include -namespace ProfileEvents -{ -extern const Event CreatedWriteBufferOrdinary; -} - namespace DB { namespace ErrorCodes @@ -49,7 +44,6 @@ createWriteBufferFromFileBaseByFileProvider( { if ((aio_threshold == 0) || (estimated_size < aio_threshold)) { - ProfileEvents::increment(ProfileEvents::CreatedWriteBufferOrdinary); return std::make_unique( file_provider, filename_, @@ -81,7 +75,6 @@ createWriteBufferFromFileBaseByFileProvider( int flags_, mode_t mode) { - ProfileEvents::increment(ProfileEvents::CreatedWriteBufferOrdinary); auto file_ptr = file_provider->newWritableFile(filename_, encryption_path_, true, create_new_encryption_info_, write_limiter_, flags_, mode); switch (checksum_algorithm) diff --git a/dbms/src/Functions/FunctionsGeo.cpp b/dbms/src/Functions/FunctionsGeo.cpp index a6fd2ff522e..02e11b66d77 100644 --- a/dbms/src/Functions/FunctionsGeo.cpp +++ b/dbms/src/Functions/FunctionsGeo.cpp @@ -28,13 +28,6 @@ #include #include - -namespace ProfileEvents -{ -extern const Event PolygonsAddedToPool; -extern const Event PolygonsInPoolAllocatedBytes; -} // namespace ProfileEvents - namespace DB { namespace ErrorCodes @@ -60,9 +53,6 @@ ColumnPtr callPointInPolygonImplWithPool(const IColumn & x, const IColumn & y, P /// To allocate memory. ptr->init(); - ProfileEvents::increment(ProfileEvents::PolygonsAddedToPool); - ProfileEvents::increment(ProfileEvents::PolygonsInPoolAllocatedBytes, ptr->getAllocatedBytes()); - return ptr.release(); }; @@ -121,30 +111,30 @@ class FunctionPointInPolygon : public IFunction throw Exception("Too few arguments", ErrorCodes::TOO_LESS_ARGUMENTS_FOR_FUNCTION); } - auto getMsgPrefix = [this](size_t i) { + auto get_msg_prefix = [this](size_t i) { return "Argument " + toString(i + 1) + " for function " + getName(); }; for (size_t i = 1; i < arguments.size(); ++i) { - auto * array = checkAndGetDataType(arguments[i].get()); + const auto * array = checkAndGetDataType(arguments[i].get()); if (array == nullptr && i != 1) - throw Exception(getMsgPrefix(i) + " must be array of tuples.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + throw Exception(get_msg_prefix(i) + " must be array of tuples.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - auto * tuple = checkAndGetDataType(array ? array->getNestedType().get() : arguments[i].get()); + const auto * tuple = checkAndGetDataType(array ? array->getNestedType().get() : arguments[i].get()); if (tuple == nullptr) - throw Exception(getMsgPrefix(i) + " must contains tuple.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + throw Exception(get_msg_prefix(i) + " must contains tuple.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); const DataTypes & elements = tuple->getElements(); if (elements.size() != 2) - throw Exception(getMsgPrefix(i) + " must have exactly two elements.", ErrorCodes::BAD_ARGUMENTS); + throw Exception(get_msg_prefix(i) + " must have exactly two elements.", ErrorCodes::BAD_ARGUMENTS); for (auto j : ext::range(0, elements.size())) { if (!elements[j]->isNumber()) { - throw Exception(getMsgPrefix(i) + " must contains numeric tuple at position " + toString(j + 1), + throw Exception(get_msg_prefix(i) + " must contains numeric tuple at position " + toString(j + 1), ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); } } @@ -156,10 +146,10 @@ class FunctionPointInPolygon : public IFunction void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result) const override { const IColumn * point_col = block.getByPosition(arguments[0]).column.get(); - auto const_tuple_col = checkAndGetColumn(point_col); + const auto * const_tuple_col = checkAndGetColumn(point_col); if (const_tuple_col) point_col = &const_tuple_col->getDataColumn(); - auto tuple_col = checkAndGetColumn(point_col); + const auto * tuple_col = checkAndGetColumn(point_col); if (!tuple_col) { @@ -207,18 +197,18 @@ class FunctionPointInPolygon : public IFunction { Polygon polygon; - auto getMsgPrefix = [this](size_t i) { + auto get_msg_prefix = [this](size_t i) { return "Argument " + toString(i + 1) + " for function " + getName(); }; for (size_t i = 1; i < arguments.size(); ++i) { - auto const_col = checkAndGetColumn(block.getByPosition(arguments[i]).column.get()); - auto array_col = const_col ? checkAndGetColumn(&const_col->getDataColumn()) : nullptr; - auto tuple_col = array_col ? checkAndGetColumn(&array_col->getData()) : nullptr; + const auto * const_col = checkAndGetColumn(block.getByPosition(arguments[i]).column.get()); + const auto * array_col = const_col ? checkAndGetColumn(&const_col->getDataColumn()) : nullptr; + const auto * tuple_col = array_col ? checkAndGetColumn(&array_col->getData()) : nullptr; if (!tuple_col) - throw Exception(getMsgPrefix(i) + " must be constant array of tuples.", ErrorCodes::ILLEGAL_COLUMN); + throw Exception(get_msg_prefix(i) + " must be constant array of tuples.", ErrorCodes::ILLEGAL_COLUMN); const auto & tuple_columns = tuple_col->getColumns(); const auto & column_x = tuple_columns[0]; @@ -232,7 +222,7 @@ class FunctionPointInPolygon : public IFunction auto size = column_x->size(); if (size == 0) - throw Exception(getMsgPrefix(i) + " shouldn't be empty.", ErrorCodes::ILLEGAL_COLUMN); + throw Exception(get_msg_prefix(i) + " shouldn't be empty.", ErrorCodes::ILLEGAL_COLUMN); for (auto j : ext::range(0, size)) { @@ -246,11 +236,11 @@ class FunctionPointInPolygon : public IFunction container.push_back(container.front()); } - auto callImpl = use_object_pool + auto call_impl = use_object_pool ? FunctionPointInPolygonDetail::callPointInPolygonImplWithPool, PointInPolygonImpl> : FunctionPointInPolygonDetail::callPointInPolygonImpl, PointInPolygonImpl>; - return callImpl(x, y, polygon); + return call_impl(x, y, polygon); } }; diff --git a/dbms/src/Functions/Regexps.h b/dbms/src/Functions/Regexps.h index 119169be8b5..3eddd383cfb 100644 --- a/dbms/src/Functions/Regexps.h +++ b/dbms/src/Functions/Regexps.h @@ -18,13 +18,6 @@ #include #include - -namespace ProfileEvents -{ -extern const Event RegexpCreated; -} - - namespace DB { namespace Regexps @@ -54,7 +47,6 @@ inline Pool::Pointer get(const std::string & pattern, int flags) if (no_capture) flags |= OptimizedRegularExpression::RE_NO_CAPTURE; - ProfileEvents::increment(ProfileEvents::RegexpCreated); return new Regexp{createRegexp(pattern, flags)}; }); } diff --git a/dbms/src/IO/BufferWithOwnMemory.h b/dbms/src/IO/BufferWithOwnMemory.h index 272f4fc5c01..babe2541b33 100644 --- a/dbms/src/IO/BufferWithOwnMemory.h +++ b/dbms/src/IO/BufferWithOwnMemory.h @@ -21,14 +21,6 @@ #include - -namespace ProfileEvents -{ -extern const Event IOBufferAllocs; -extern const Event IOBufferAllocBytes; -} // namespace ProfileEvents - - namespace DB { /** Replacement for std::vector to use in buffers. @@ -119,9 +111,6 @@ struct Memory return; } - ProfileEvents::increment(ProfileEvents::IOBufferAllocs); - ProfileEvents::increment(ProfileEvents::IOBufferAllocBytes, m_capacity); - size_t new_capacity = align(m_capacity, alignment); m_data = static_cast(Allocator::alloc(new_capacity, alignment)); m_capacity = new_capacity; diff --git a/dbms/src/IO/ChecksumBuffer.h b/dbms/src/IO/ChecksumBuffer.h index f6d60677a12..b095545ea6e 100644 --- a/dbms/src/IO/ChecksumBuffer.h +++ b/dbms/src/IO/ChecksumBuffer.h @@ -27,7 +27,6 @@ namespace ProfileEvents { // no need to update sync, since write buffers inherit that directly from `WriteBufferFromFileDescriptor` extern const Event WriteBufferFromFileDescriptorWrite; -extern const Event WriteBufferFromFileDescriptorWriteFailed; extern const Event WriteBufferFromFileDescriptorWriteBytes; extern const Event ReadBufferFromFileDescriptorRead; extern const Event ReadBufferFromFileDescriptorReadBytes; @@ -107,7 +106,6 @@ class FramedChecksumWriteBuffer : public WriteBufferFromFileDescriptor } if (unlikely(count == -1)) { - ProfileEvents::increment(ProfileEvents::WriteBufferFromFileDescriptorWriteFailed); if (errno == EINTR) continue; else @@ -386,8 +384,6 @@ class FramedChecksumReadBuffer : public ReadBufferFromFileDescriptor off_t doSeek(off_t offset, int whence) override { - ProfileEvents::increment(ProfileEvents::Seek); - auto & frame = reinterpret_cast &>( *(this->working_buffer.begin() - sizeof(ChecksumFrame))); // align should not fail diff --git a/dbms/src/IO/CompressedReadBufferBase.cpp b/dbms/src/IO/CompressedReadBufferBase.cpp index dd54c1b47a8..58bf47a9298 100644 --- a/dbms/src/IO/CompressedReadBufferBase.cpp +++ b/dbms/src/IO/CompressedReadBufferBase.cpp @@ -28,14 +28,6 @@ #include - -namespace ProfileEvents -{ -extern const Event ReadCompressedBytes; -extern const Event CompressedReadBufferBlocks; -extern const Event CompressedReadBufferBytes; -} // namespace ProfileEvents - namespace DB { namespace ErrorCodes @@ -83,8 +75,6 @@ size_t CompressedReadBufferBase::readCompressedData(size_t & size_ if (size_compressed > DBMS_MAX_COMPRESSED_SIZE) throw Exception("Too large size_compressed. Most likely corrupted data.", ErrorCodes::TOO_LARGE_SIZE_COMPRESSED); - ProfileEvents::increment(ProfileEvents::ReadCompressedBytes, size_compressed + sizeof(checksum)); - /// Is whole compressed block located in 'compressed_in' buffer? if (compressed_in->offset() >= COMPRESSED_BLOCK_HEADER_SIZE && compressed_in->position() + size_compressed - COMPRESSED_BLOCK_HEADER_SIZE <= compressed_in->buffer().end()) @@ -115,9 +105,6 @@ size_t CompressedReadBufferBase::readCompressedData(size_t & size_ template void CompressedReadBufferBase::decompress(char * to, size_t size_decompressed, size_t size_compressed_without_checksum) { - ProfileEvents::increment(ProfileEvents::CompressedReadBufferBlocks); - ProfileEvents::increment(ProfileEvents::CompressedReadBufferBytes, size_decompressed); - UInt8 method = compressed_buffer[0]; /// See CompressedWriteBuffer.h if (method == static_cast(CompressionMethodByte::LZ4)) diff --git a/dbms/src/IO/ReadBufferFromFileDescriptor.cpp b/dbms/src/IO/ReadBufferFromFileDescriptor.cpp index 90cc6e3ca76..4b3d52f3741 100644 --- a/dbms/src/IO/ReadBufferFromFileDescriptor.cpp +++ b/dbms/src/IO/ReadBufferFromFileDescriptor.cpp @@ -77,7 +77,7 @@ bool ReadBufferFromFileDescriptor::nextImpl() if (profile_callback) { - ProfileInfo info; + ProfileInfo info; // NOLINT info.bytes_requested = internal_buffer.size(); info.bytes_read = res; info.nanoseconds = watch->elapsed(); @@ -120,8 +120,6 @@ off_t ReadBufferFromFileDescriptor::doSeek(off_t offset, int whence) } else { - ProfileEvents::increment(ProfileEvents::Seek); - pos = working_buffer.end(); off_t res = doSeekInFile(new_pos, SEEK_SET); if (-1 == res) @@ -145,7 +143,7 @@ bool ReadBufferFromFileDescriptor::poll(size_t timeout_microseconds) FD_SET(fd, &fds); timeval timeout = {time_t(timeout_microseconds / 1000000), suseconds_t(timeout_microseconds % 1000000)}; - int res = select(1, &fds, 0, 0, &timeout); + int res = select(1, &fds, nullptr, nullptr, &timeout); if (-1 == res) throwFromErrno("Cannot select", ErrorCodes::CANNOT_SELECT); diff --git a/dbms/src/IO/WriteBufferFromFileDescriptor.cpp b/dbms/src/IO/WriteBufferFromFileDescriptor.cpp index c18337497b7..49b6d871870 100644 --- a/dbms/src/IO/WriteBufferFromFileDescriptor.cpp +++ b/dbms/src/IO/WriteBufferFromFileDescriptor.cpp @@ -24,7 +24,6 @@ namespace ProfileEvents { extern const Event FileFSync; extern const Event WriteBufferFromFileDescriptorWrite; -extern const Event WriteBufferFromFileDescriptorWriteFailed; extern const Event WriteBufferFromFileDescriptorWriteBytes; } // namespace ProfileEvents @@ -57,7 +56,6 @@ void WriteBufferFromFileDescriptor::nextImpl() if ((-1 == res || 0 == res) && errno != EINTR) { - ProfileEvents::increment(ProfileEvents::WriteBufferFromFileDescriptorWriteFailed); throwFromErrno("Cannot write to file " + getFileName(), ErrorCodes::CANNOT_WRITE_TO_FILE_DESCRIPTOR); } diff --git a/dbms/src/IO/createReadBufferFromFileBase.cpp b/dbms/src/IO/createReadBufferFromFileBase.cpp index 24c9dfb204c..0d129d03a1a 100644 --- a/dbms/src/IO/createReadBufferFromFileBase.cpp +++ b/dbms/src/IO/createReadBufferFromFileBase.cpp @@ -20,13 +20,6 @@ #endif #include - -namespace ProfileEvents -{ -extern const Event CreatedReadBufferOrdinary; -extern const Event CreatedReadBufferAIO; -} // namespace ProfileEvents - namespace DB { namespace ErrorCodes @@ -46,13 +39,11 @@ createReadBufferFromFileBase( { if ((aio_threshold == 0) || (estimated_size < aio_threshold)) { - ProfileEvents::increment(ProfileEvents::CreatedReadBufferOrdinary); return std::make_unique(filename_, buffer_size_, flags_, existing_memory_, alignment); } else { #if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(_MSC_VER) - ProfileEvents::increment(ProfileEvents::CreatedReadBufferAIO); return std::make_unique(filename_, buffer_size_, flags_, existing_memory_); #else throw Exception("AIO is not implemented yet on MacOS X", ErrorCodes::NOT_IMPLEMENTED); diff --git a/dbms/src/IO/createWriteBufferFromFileBase.cpp b/dbms/src/IO/createWriteBufferFromFileBase.cpp index 96bf3e65558..0e741eb3e5d 100644 --- a/dbms/src/IO/createWriteBufferFromFileBase.cpp +++ b/dbms/src/IO/createWriteBufferFromFileBase.cpp @@ -19,13 +19,6 @@ #endif #include - -namespace ProfileEvents -{ -extern const Event CreatedWriteBufferOrdinary; -extern const Event CreatedWriteBufferAIO; -} // namespace ProfileEvents - namespace DB { namespace ErrorCodes @@ -45,13 +38,11 @@ WriteBufferFromFileBase * createWriteBufferFromFileBase( { if ((aio_threshold == 0) || (estimated_size < aio_threshold)) { - ProfileEvents::increment(ProfileEvents::CreatedWriteBufferOrdinary); return new WriteBufferFromFile(filename_, buffer_size_, flags_, mode, existing_memory_, alignment); } else { #if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(_MSC_VER) - ProfileEvents::increment(ProfileEvents::CreatedWriteBufferAIO); return new WriteBufferAIO(filename_, buffer_size_, flags_, mode, existing_memory_); #else throw Exception("AIO is not implemented yet on MacOS X", ErrorCodes::NOT_IMPLEMENTED); diff --git a/dbms/src/Interpreters/Aggregator.cpp b/dbms/src/Interpreters/Aggregator.cpp index 6e067b88d81..6a39bc333a8 100644 --- a/dbms/src/Interpreters/Aggregator.cpp +++ b/dbms/src/Interpreters/Aggregator.cpp @@ -37,19 +37,6 @@ #include #include - -namespace ProfileEvents -{ -extern const Event ExternalAggregationWritePart; -extern const Event ExternalAggregationCompressedBytes; -extern const Event ExternalAggregationUncompressedBytes; -} // namespace ProfileEvents - -namespace CurrentMetrics -{ -extern const Metric QueryThread; -} - namespace DB { namespace ErrorCodes @@ -658,7 +645,6 @@ void Aggregator::writeToTemporaryFile(AggregatedDataVariants & data_variants, co NativeBlockOutputStream block_out(compressed_buf, ClickHouseRevision::get(), getHeader(false)); LOG_FMT_DEBUG(log, "Writing part of aggregation data into temporary file {}.", path); - ProfileEvents::increment(ProfileEvents::ExternalAggregationWritePart); /// Flush only two-level data and possibly overflow data. @@ -694,9 +680,6 @@ void Aggregator::writeToTemporaryFile(AggregatedDataVariants & data_variants, co temporary_files.sum_size_compressed += compressed_bytes; } - ProfileEvents::increment(ProfileEvents::ExternalAggregationCompressedBytes, compressed_bytes); - ProfileEvents::increment(ProfileEvents::ExternalAggregationUncompressedBytes, uncompressed_bytes); - LOG_FMT_TRACE( log, "Written part in {:.3f} sec., {} rows, " @@ -1016,7 +999,7 @@ Block Aggregator::prepareBlockAndFill( aggregate_columns[i] = header.getByName(aggregate_column_name).type->createColumn(); /// The ColumnAggregateFunction column captures the shared ownership of the arena with the aggregate function states. - ColumnAggregateFunction & column_aggregate_func = assert_cast(*aggregate_columns[i]); + auto & column_aggregate_func = assert_cast(*aggregate_columns[i]); for (auto & pool : data_variants.aggregates_pools) column_aggregate_func.addArena(pool); @@ -1502,7 +1485,7 @@ class MergingAndConvertingBlockInputStream : public IProfilingBlockInputStream Block getHeader() const override { return aggregator.getHeader(final); } - ~MergingAndConvertingBlockInputStream() + ~MergingAndConvertingBlockInputStream() override { LOG_FMT_TRACE(&Poco::Logger::get(__PRETTY_FUNCTION__), "Waiting for threads to finish"); @@ -1636,8 +1619,6 @@ class MergingAndConvertingBlockInputStream : public IProfilingBlockInputStream void thread(Int32 bucket_num) { - CurrentMetrics::Increment metric_increment{CurrentMetrics::QueryThread}; - try { /// TODO: add no_more_keys support maybe diff --git a/dbms/src/Interpreters/Context.cpp b/dbms/src/Interpreters/Context.cpp index a0345daaa75..2dbd495d2c4 100644 --- a/dbms/src/Interpreters/Context.cpp +++ b/dbms/src/Interpreters/Context.cpp @@ -76,10 +76,10 @@ namespace ProfileEvents extern const Event ContextLock; } +#include + namespace CurrentMetrics { -extern const Metric ContextLockWait; -extern const Metric MemoryTrackingForMerges; extern const Metric GlobalStorageRunMode; } // namespace CurrentMetrics @@ -307,8 +307,6 @@ Context::~Context() std::unique_lock Context::getLock() const { - ProfileEvents::increment(ProfileEvents::ContextLock); - CurrentMetrics::Increment increment{CurrentMetrics::ContextLockWait}; return std::unique_lock(shared->mutex); } diff --git a/dbms/src/Interpreters/ExpressionActions.cpp b/dbms/src/Interpreters/ExpressionActions.cpp index 8e75a64427c..0ab8519e4d0 100644 --- a/dbms/src/Interpreters/ExpressionActions.cpp +++ b/dbms/src/Interpreters/ExpressionActions.cpp @@ -27,12 +27,6 @@ #include #include - -namespace ProfileEvents -{ -extern const Event FunctionExecute; -} - namespace DB { namespace ErrorCodes @@ -339,7 +333,6 @@ void ExpressionAction::execute(Block & block) const size_t num_columns_without_result = block.columns(); block.insert({nullptr, result_type, result_name}); - ProfileEvents::increment(ProfileEvents::FunctionExecute); function->execute(block, arguments, num_columns_without_result); break; diff --git a/dbms/src/Interpreters/InterpreterInsertQuery.cpp b/dbms/src/Interpreters/InterpreterInsertQuery.cpp index aa64cf8ca94..782a254925a 100644 --- a/dbms/src/Interpreters/InterpreterInsertQuery.cpp +++ b/dbms/src/Interpreters/InterpreterInsertQuery.cpp @@ -30,12 +30,6 @@ #include #include - -namespace ProfileEvents -{ -extern const Event InsertQuery; -} - namespace DB { namespace ErrorCodes @@ -54,7 +48,6 @@ InterpreterInsertQuery::InterpreterInsertQuery( , context(context_) , allow_materialized(allow_materialized_) { - ProfileEvents::increment(ProfileEvents::InsertQuery); } @@ -62,7 +55,7 @@ StoragePtr InterpreterInsertQuery::getTable(const ASTInsertQuery & query) { if (query.table_function) { - auto table_function = typeid_cast(query.table_function.get()); + const auto * table_function = typeid_cast(query.table_function.get()); const auto & factory = TableFunctionFactory::instance(); return factory.get(table_function->name, context)->execute(query.table_function, context); } @@ -71,7 +64,7 @@ StoragePtr InterpreterInsertQuery::getTable(const ASTInsertQuery & query) return context.getTable(query.database, query.table); } -Block InterpreterInsertQuery::getSampleBlock(const ASTInsertQuery & query, const StoragePtr & table) +Block InterpreterInsertQuery::getSampleBlock(const ASTInsertQuery & query, const StoragePtr & table) // NOLINT { Block table_sample_non_materialized; if (query.is_import) diff --git a/dbms/src/Interpreters/InterpreterSelectQuery.cpp b/dbms/src/Interpreters/InterpreterSelectQuery.cpp index 51b55f65bd4..01e8625f943 100644 --- a/dbms/src/Interpreters/InterpreterSelectQuery.cpp +++ b/dbms/src/Interpreters/InterpreterSelectQuery.cpp @@ -131,8 +131,6 @@ InterpreterSelectQuery::~InterpreterSelectQuery() = default; void InterpreterSelectQuery::init(const Names & required_result_column_names) { - ProfileEvents::increment(ProfileEvents::SelectQuery); - if (!context.hasQueryContext()) context.setQueryContext(context); diff --git a/dbms/src/Interpreters/ProcessList.h b/dbms/src/Interpreters/ProcessList.h index fdc009237aa..5ed586c263d 100644 --- a/dbms/src/Interpreters/ProcessList.h +++ b/dbms/src/Interpreters/ProcessList.h @@ -31,12 +31,6 @@ #include #include - -namespace CurrentMetrics -{ -extern const Metric Query; -} - namespace DB { class IStorage; @@ -90,8 +84,6 @@ class ProcessListElement QueryPriorities::Handle priority_handle; - CurrentMetrics::Increment num_queries{CurrentMetrics::Query}; - std::atomic is_killed{false}; /// Be careful using it. For example, queries field could be modified concurrently. diff --git a/dbms/src/Interpreters/QueryPriorities.h b/dbms/src/Interpreters/QueryPriorities.h index 5f34ae616c7..ca01e4f0a6c 100644 --- a/dbms/src/Interpreters/QueryPriorities.h +++ b/dbms/src/Interpreters/QueryPriorities.h @@ -23,13 +23,6 @@ #include #include - -namespace CurrentMetrics -{ -extern const Metric QueryPreempted; -} - - namespace DB { /** Implements query priorities in very primitive way. @@ -95,7 +88,6 @@ class QueryPriorities if (!found) return true; - CurrentMetrics::Increment metric_increment{CurrentMetrics::QueryPreempted}; if (std::cv_status::timeout == condvar.wait_for(lock, cur_timeout)) return false; else diff --git a/dbms/src/Server/HTTPHandler.h b/dbms/src/Server/HTTPHandler.h index bd06d56bd4e..74b5cc4b4c7 100644 --- a/dbms/src/Server/HTTPHandler.h +++ b/dbms/src/Server/HTTPHandler.h @@ -14,24 +14,20 @@ #pragma once -#include "IServer.h" - -#include - #include #include +#include +#include "IServer.h" -namespace CurrentMetrics + +namespace Poco { - extern const Metric HTTPConnection; +class Logger; } -namespace Poco { class Logger; } - namespace DB { - class WriteBufferFromHTTPServerResponse; @@ -69,11 +65,9 @@ class HTTPHandler : public Poco::Net::HTTPRequestHandler IServer & server; Poco::Logger * log; - /// It is the name of the server that will be sent in an http-header X-ClickHouse-Server-Display-Name. + /// It is the name of the server that will be sent in an http-header X-ClickHouse-Server-Display-Name. String server_display_name; - CurrentMetrics::Increment metric_increment{CurrentMetrics::HTTPConnection}; - /// Also initializes 'used_output'. void processQuery( Poco::Net::HTTPServerRequest & request, @@ -91,4 +85,4 @@ class HTTPHandler : public Poco::Net::HTTPRequestHandler void pushDelayedResults(Output & used_output); }; -} +} // namespace DB diff --git a/dbms/src/Server/Server.cpp b/dbms/src/Server/Server.cpp index 95c1d5d3f2a..186ab0889d8 100644 --- a/dbms/src/Server/Server.cpp +++ b/dbms/src/Server/Server.cpp @@ -175,11 +175,6 @@ namespace } } // namespace -namespace CurrentMetrics -{ -extern const Metric Revision; -} - namespace DB { namespace ErrorCodes @@ -1064,8 +1059,6 @@ int Server::main(const std::vector & /*args*/) LOG_FMT_INFO(log, "TiFlashRaftProxyHelper is null, failed to get server info"); } - CurrentMetrics::set(CurrentMetrics::Revision, ClickHouseRevision::get()); - // print necessary grpc log. grpc_log = &Poco::Logger::get("grpc"); gpr_set_log_verbosity(GPR_LOG_SEVERITY_DEBUG); diff --git a/dbms/src/Server/TCPHandler.h b/dbms/src/Server/TCPHandler.h index ed0af52dc98..2fde0b11d9b 100644 --- a/dbms/src/Server/TCPHandler.h +++ b/dbms/src/Server/TCPHandler.h @@ -29,11 +29,6 @@ #include "IServer.h" -namespace CurrentMetrics -{ -extern const Metric TCPConnection; -} - namespace Poco { class Logger; @@ -131,8 +126,6 @@ class TCPHandler : public Poco::Net::TCPServerConnection /// At the moment, only one ongoing query in the connection is supported at a time. QueryState state; - CurrentMetrics::Increment metric_increment{CurrentMetrics::TCPConnection}; - /// It is the name of the server that will be sent to the client. String server_display_name; diff --git a/dbms/src/Storages/BackgroundProcessingPool.cpp b/dbms/src/Storages/BackgroundProcessingPool.cpp index 9fb4271ea38..15740fa2875 100644 --- a/dbms/src/Storages/BackgroundProcessingPool.cpp +++ b/dbms/src/Storages/BackgroundProcessingPool.cpp @@ -42,7 +42,6 @@ inline static pid_t getTid() namespace CurrentMetrics { -extern const Metric BackgroundPoolTask; extern const Metric MemoryTrackingInBackgroundProcessingPool; } // namespace CurrentMetrics @@ -218,8 +217,6 @@ void BackgroundProcessingPool::threadFunction() continue; { - CurrentMetrics::Increment metric_increment{CurrentMetrics::BackgroundPoolTask}; - bool done_work = false; if (!task->multi) { diff --git a/dbms/src/Storages/MarkCache.h b/dbms/src/Storages/MarkCache.h index 5816b0c1bba..728f830e0d0 100644 --- a/dbms/src/Storages/MarkCache.h +++ b/dbms/src/Storages/MarkCache.h @@ -14,24 +14,23 @@ #pragma once -#include - #include #include #include -#include #include +#include + +#include namespace ProfileEvents { - extern const Event MarkCacheHits; - extern const Event MarkCacheMisses; -} +extern const Event MarkCacheHits; +extern const Event MarkCacheMisses; +} // namespace ProfileEvents namespace DB { - /// Estimate of number of bytes in cache for marks. struct MarksWeightFunction { @@ -53,7 +52,8 @@ class MarkCache : public LRUCache MappedPtr getOrSet(const Key & key, LoadFunc && load) @@ -70,4 +70,4 @@ class MarkCache : public LRUCache; -} +} // namespace DB diff --git a/dbms/src/Storages/StorageBuffer.cpp b/dbms/src/Storages/StorageBuffer.cpp index 0dc05674696..1d7c0ace57f 100644 --- a/dbms/src/Storages/StorageBuffer.cpp +++ b/dbms/src/Storages/StorageBuffer.cpp @@ -34,24 +34,6 @@ #include - -namespace ProfileEvents -{ -extern const Event StorageBufferFlush; -extern const Event StorageBufferErrorOnFlush; -extern const Event StorageBufferPassedAllMinThresholds; -extern const Event StorageBufferPassedTimeMaxThreshold; -extern const Event StorageBufferPassedRowsMaxThreshold; -extern const Event StorageBufferPassedBytesMaxThreshold; -} // namespace ProfileEvents - -namespace CurrentMetrics -{ -extern const Metric StorageBufferRows; -extern const Metric StorageBufferBytes; -} // namespace CurrentMetrics - - namespace DB { namespace ErrorCodes @@ -170,10 +152,6 @@ static void appendBlock(const Block & from, Block & to) to.checkNumberOfRows(); size_t rows = from.rows(); - size_t bytes = from.bytes(); - - CurrentMetrics::add(CurrentMetrics::StorageBufferRows, rows); - CurrentMetrics::add(CurrentMetrics::StorageBufferBytes, bytes); size_t old_rows = to.rows(); @@ -430,25 +408,21 @@ bool StorageBuffer::checkThresholdsImpl(size_t rows, size_t bytes, time_t time_p { if (time_passed > min_thresholds.time && rows > min_thresholds.rows && bytes > min_thresholds.bytes) { - ProfileEvents::increment(ProfileEvents::StorageBufferPassedAllMinThresholds); return true; } if (time_passed > max_thresholds.time) { - ProfileEvents::increment(ProfileEvents::StorageBufferPassedTimeMaxThreshold); return true; } if (rows > max_thresholds.rows) { - ProfileEvents::increment(ProfileEvents::StorageBufferPassedRowsMaxThreshold); return true; } if (bytes > max_thresholds.bytes) { - ProfileEvents::increment(ProfileEvents::StorageBufferPassedBytesMaxThreshold); return true; } @@ -495,11 +469,6 @@ void StorageBuffer::flushBuffer(Buffer & buffer, bool check_thresholds) buffer.data.swap(block_to_write); buffer.first_write_time = 0; - CurrentMetrics::sub(CurrentMetrics::StorageBufferRows, block_to_write.rows()); - CurrentMetrics::sub(CurrentMetrics::StorageBufferBytes, block_to_write.bytes()); - - ProfileEvents::increment(ProfileEvents::StorageBufferFlush); - LOG_FMT_TRACE(log, "Flushing buffer with {} rows, {} bytes, age {} seconds.", rows, bytes, time_passed); if (no_destination) @@ -517,13 +486,7 @@ void StorageBuffer::flushBuffer(Buffer & buffer, bool check_thresholds) } catch (...) { - ProfileEvents::increment(ProfileEvents::StorageBufferErrorOnFlush); - /// Return the block to its place in the buffer. - - CurrentMetrics::add(CurrentMetrics::StorageBufferRows, block_to_write.rows()); - CurrentMetrics::add(CurrentMetrics::StorageBufferBytes, block_to_write.bytes()); - buffer.data.swap(block_to_write); if (!buffer.first_write_time) diff --git a/dbms/src/TableFunctions/ITableFunction.cpp b/dbms/src/TableFunctions/ITableFunction.cpp index ca05075cac0..d262a5637f7 100644 --- a/dbms/src/TableFunctions/ITableFunction.cpp +++ b/dbms/src/TableFunctions/ITableFunction.cpp @@ -15,17 +15,10 @@ #include #include - -namespace ProfileEvents -{ -extern const Event TableFunctionExecute; -} - namespace DB { StoragePtr ITableFunction::execute(const ASTPtr & ast_function, const Context & context) const { - ProfileEvents::increment(ProfileEvents::TableFunctionExecute); return executeImpl(ast_function, context); } From ebb27d1eb5537728fee499b8f1c415aee1658472 Mon Sep 17 00:00:00 2001 From: hehechen Date: Mon, 20 Jun 2022 20:24:37 +0800 Subject: [PATCH 115/127] fix pageworklaod (#5165) close pingcap/tiflash#5164 --- .../src/Storages/Page/workload/CMakeLists.txt | 3 +- ...moryCostInGC.cpp => HeavyMemoryCostInGC.h} | 2 - .../workload/{HeavyRead.cpp => HeavyRead.h} | 2 - ...SkewWriteRead.cpp => HeavySkewWriteRead.h} | 2 - .../workload/{HeavyWrite.cpp => HeavyWrite.h} | 2 - ...alidBigFileGC.cpp => HighValidBigFileGC.h} | 2 - ...tsLongTime.cpp => HoldSnapshotsLongTime.h} | 2 - dbms/src/Storages/Page/workload/MainEntry.cpp | 48 +++++++------------ .../Page/workload/{Normal.cpp => Normal.h} | 2 - dbms/src/Storages/Page/workload/PSWorkload.h | 20 ++++---- ...city.cpp => PageStorageInMemoryCapacity.h} | 2 - ...usandsOfOffset.cpp => ThousandsOfOffset.h} | 2 - 12 files changed, 29 insertions(+), 60 deletions(-) rename dbms/src/Storages/Page/workload/{HeavyMemoryCostInGC.cpp => HeavyMemoryCostInGC.h} (98%) rename dbms/src/Storages/Page/workload/{HeavyRead.cpp => HeavyRead.h} (98%) rename dbms/src/Storages/Page/workload/{HeavySkewWriteRead.cpp => HeavySkewWriteRead.h} (98%) rename dbms/src/Storages/Page/workload/{HeavyWrite.cpp => HeavyWrite.h} (98%) rename dbms/src/Storages/Page/workload/{HighValidBigFileGC.cpp => HighValidBigFileGC.h} (98%) rename dbms/src/Storages/Page/workload/{HoldSnapshotsLongTime.cpp => HoldSnapshotsLongTime.h} (98%) rename dbms/src/Storages/Page/workload/{Normal.cpp => Normal.h} (98%) rename dbms/src/Storages/Page/workload/{PageStorageInMemoryCapacity.cpp => PageStorageInMemoryCapacity.h} (99%) rename dbms/src/Storages/Page/workload/{ThousandsOfOffset.cpp => ThousandsOfOffset.h} (99%) diff --git a/dbms/src/Storages/Page/workload/CMakeLists.txt b/dbms/src/Storages/Page/workload/CMakeLists.txt index 5c8ecb34d97..adf94c75f11 100644 --- a/dbms/src/Storages/Page/workload/CMakeLists.txt +++ b/dbms/src/Storages/Page/workload/CMakeLists.txt @@ -14,8 +14,7 @@ include_directories (${CMAKE_CURRENT_BINARY_DIR}) -set (page-workload-src HeavyMemoryCostInGC.cpp HeavyRead.cpp HeavySkewWriteRead.cpp HeavyWrite.cpp HighValidBigFileGC.cpp HoldSnapshotsLongTime.cpp Normal.cpp - PageStorageInMemoryCapacity.cpp ThousandsOfOffset.cpp MainEntry.cpp Normal.cpp PageStorageInMemoryCapacity.cpp PSBackground.cpp PSRunnable.cpp PSStressEnv.cpp PSWorkload.cpp) +set (page-workload-src MainEntry.cpp PSBackground.cpp PSRunnable.cpp PSStressEnv.cpp PSWorkload.cpp) add_library (page-workload-lib ${page-workload-src}) target_link_libraries (page-workload-lib dbms clickhouse_functions clickhouse-server-lib) diff --git a/dbms/src/Storages/Page/workload/HeavyMemoryCostInGC.cpp b/dbms/src/Storages/Page/workload/HeavyMemoryCostInGC.h similarity index 98% rename from dbms/src/Storages/Page/workload/HeavyMemoryCostInGC.cpp rename to dbms/src/Storages/Page/workload/HeavyMemoryCostInGC.h index 7e745e29fc2..3daaf10ffb3 100644 --- a/dbms/src/Storages/Page/workload/HeavyMemoryCostInGC.cpp +++ b/dbms/src/Storages/Page/workload/HeavyMemoryCostInGC.h @@ -81,6 +81,4 @@ class HeavyMemoryCostInGC fmt::format("Memory Peak is {} , it should not bigger than {} ", metrics_dumper->getMemoryPeak(), 5 * 1024 * 1024)); } }; - -REGISTER_WORKLOAD(HeavyMemoryCostInGC) } // namespace DB::PS::tests diff --git a/dbms/src/Storages/Page/workload/HeavyRead.cpp b/dbms/src/Storages/Page/workload/HeavyRead.h similarity index 98% rename from dbms/src/Storages/Page/workload/HeavyRead.cpp rename to dbms/src/Storages/Page/workload/HeavyRead.h index a67c435e84c..80023f95988 100644 --- a/dbms/src/Storages/Page/workload/HeavyRead.cpp +++ b/dbms/src/Storages/Page/workload/HeavyRead.h @@ -70,6 +70,4 @@ class HeavyRead : public StressWorkload } } }; - -REGISTER_WORKLOAD(HeavyRead) } // namespace DB::PS::tests \ No newline at end of file diff --git a/dbms/src/Storages/Page/workload/HeavySkewWriteRead.cpp b/dbms/src/Storages/Page/workload/HeavySkewWriteRead.h similarity index 98% rename from dbms/src/Storages/Page/workload/HeavySkewWriteRead.cpp rename to dbms/src/Storages/Page/workload/HeavySkewWriteRead.h index 805bf105358..0e75bc0d3e5 100644 --- a/dbms/src/Storages/Page/workload/HeavySkewWriteRead.cpp +++ b/dbms/src/Storages/Page/workload/HeavySkewWriteRead.h @@ -85,6 +85,4 @@ class HeavySkewWriteRead : public StressWorkload return true; } }; - -REGISTER_WORKLOAD(HeavySkewWriteRead) } // namespace DB::PS::tests \ No newline at end of file diff --git a/dbms/src/Storages/Page/workload/HeavyWrite.cpp b/dbms/src/Storages/Page/workload/HeavyWrite.h similarity index 98% rename from dbms/src/Storages/Page/workload/HeavyWrite.cpp rename to dbms/src/Storages/Page/workload/HeavyWrite.h index 8dfd7f810f7..54b7585ee20 100644 --- a/dbms/src/Storages/Page/workload/HeavyWrite.cpp +++ b/dbms/src/Storages/Page/workload/HeavyWrite.h @@ -72,6 +72,4 @@ class HeavyWrite : public StressWorkload return true; } }; - -REGISTER_WORKLOAD(HeavyWrite) } // namespace DB::PS::tests \ No newline at end of file diff --git a/dbms/src/Storages/Page/workload/HighValidBigFileGC.cpp b/dbms/src/Storages/Page/workload/HighValidBigFileGC.h similarity index 98% rename from dbms/src/Storages/Page/workload/HighValidBigFileGC.cpp rename to dbms/src/Storages/Page/workload/HighValidBigFileGC.h index a9af6aebb76..cc3b5b45135 100644 --- a/dbms/src/Storages/Page/workload/HighValidBigFileGC.cpp +++ b/dbms/src/Storages/Page/workload/HighValidBigFileGC.h @@ -129,6 +129,4 @@ class HighValidBigFileGCWorkload private: UInt64 gc_time_ms = 0; }; - -REGISTER_WORKLOAD(HighValidBigFileGCWorkload) } // namespace DB::PS::tests \ No newline at end of file diff --git a/dbms/src/Storages/Page/workload/HoldSnapshotsLongTime.cpp b/dbms/src/Storages/Page/workload/HoldSnapshotsLongTime.h similarity index 98% rename from dbms/src/Storages/Page/workload/HoldSnapshotsLongTime.cpp rename to dbms/src/Storages/Page/workload/HoldSnapshotsLongTime.h index f02fbf65bcd..071a104010c 100644 --- a/dbms/src/Storages/Page/workload/HoldSnapshotsLongTime.cpp +++ b/dbms/src/Storages/Page/workload/HoldSnapshotsLongTime.h @@ -94,6 +94,4 @@ class HoldSnapshotsLongTime : public StressWorkload return true; } }; - -REGISTER_WORKLOAD(HoldSnapshotsLongTime) } // namespace DB::PS::tests \ No newline at end of file diff --git a/dbms/src/Storages/Page/workload/MainEntry.cpp b/dbms/src/Storages/Page/workload/MainEntry.cpp index ac82e1ea4bc..18e42106c90 100644 --- a/dbms/src/Storages/Page/workload/MainEntry.cpp +++ b/dbms/src/Storages/Page/workload/MainEntry.cpp @@ -11,44 +11,32 @@ // 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. +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include using namespace DB::PS::tests; int StressWorkload::mainEntry(int argc, char ** argv) { { - // maybe due to sequence of linking, REGISTER_WORKLOAD is not visible to main function in dbms/src/Server/main.cpp - // cause that REGISTER_WORKLOAD will not be triggered before mainEntry - // we do this to trigger REGISTER_WORKLOAD explicitly. - void _work_load_register_named_HeavyMemoryCostInGC(); - void (*f)() = _work_load_register_named_HeavyMemoryCostInGC; - (void)f; - void _work_load_register_named_HeavyRead(); - f = _work_load_register_named_HeavyRead; - (void)f; - void _work_load_register_named_HeavySkewWriteRead(); - f = _work_load_register_named_HeavySkewWriteRead; - (void)f; - void _work_load_register_named_HeavyWrite(); - f = _work_load_register_named_HeavyWrite; - (void)f; - void _work_load_register_named_HighValidBigFileGCWorkload(); - f = _work_load_register_named_HighValidBigFileGCWorkload; - (void)f; - void _work_load_register_named_HoldSnapshotsLongTime(); - f = _work_load_register_named_HoldSnapshotsLongTime; - (void)f; - void _work_load_register_named_PageStorageInMemoryCapacity(); - f = _work_load_register_named_PageStorageInMemoryCapacity; - (void)f; - void _work_load_register_named_NormalWorkload(); - f = _work_load_register_named_NormalWorkload; - (void)f; - void _work_load_register_named_ThousandsOfOffset(); - f = _work_load_register_named_ThousandsOfOffset; - (void)f; + work_load_register(); + work_load_register(); + work_load_register(); + work_load_register(); + work_load_register(); + work_load_register(); + work_load_register(); + work_load_register(); + work_load_register(); } try { diff --git a/dbms/src/Storages/Page/workload/Normal.cpp b/dbms/src/Storages/Page/workload/Normal.h similarity index 98% rename from dbms/src/Storages/Page/workload/Normal.cpp rename to dbms/src/Storages/Page/workload/Normal.h index 57229395809..164f17b9d61 100644 --- a/dbms/src/Storages/Page/workload/Normal.cpp +++ b/dbms/src/Storages/Page/workload/Normal.h @@ -77,6 +77,4 @@ class NormalWorkload stop_watch.stop(); } }; - -REGISTER_WORKLOAD(NormalWorkload) } // namespace DB::PS::tests diff --git a/dbms/src/Storages/Page/workload/PSWorkload.h b/dbms/src/Storages/Page/workload/PSWorkload.h index eaaaf4eba5b..26a9c24d6da 100644 --- a/dbms/src/Storages/Page/workload/PSWorkload.h +++ b/dbms/src/Storages/Page/workload/PSWorkload.h @@ -193,15 +193,15 @@ class StressWorkloadManger StressEnv options; }; -#define REGISTER_WORKLOAD(WORKLOAD) \ - void __attribute__((constructor)) _work_load_register_named_##WORKLOAD(void) \ - { \ - StressWorkloadManger::getInstance().reg( \ - WORKLOAD::nameFunc(), \ - WORKLOAD::maskFunc(), \ - [](const StressEnv & opts) -> std::shared_ptr { \ - return std::make_shared(opts); \ - }); \ - } +template +void work_load_register() +{ + StressWorkloadManger::getInstance().reg( + Workload::nameFunc(), + Workload::maskFunc(), + [](const StressEnv & opts) -> std::shared_ptr { + return std::make_shared(opts); + }); +} } // namespace DB::PS::tests diff --git a/dbms/src/Storages/Page/workload/PageStorageInMemoryCapacity.cpp b/dbms/src/Storages/Page/workload/PageStorageInMemoryCapacity.h similarity index 99% rename from dbms/src/Storages/Page/workload/PageStorageInMemoryCapacity.cpp rename to dbms/src/Storages/Page/workload/PageStorageInMemoryCapacity.h index 6ab321d1a10..337c732e6f7 100644 --- a/dbms/src/Storages/Page/workload/PageStorageInMemoryCapacity.cpp +++ b/dbms/src/Storages/Page/workload/PageStorageInMemoryCapacity.h @@ -174,6 +174,4 @@ class PageStorageInMemoryCapacity : public StressWorkload std::round(resident_used) ? (total_mem / ((double)resident_used / page_writen)) : 0)); } }; - -REGISTER_WORKLOAD(PageStorageInMemoryCapacity) } // namespace DB::PS::tests \ No newline at end of file diff --git a/dbms/src/Storages/Page/workload/ThousandsOfOffset.cpp b/dbms/src/Storages/Page/workload/ThousandsOfOffset.h similarity index 99% rename from dbms/src/Storages/Page/workload/ThousandsOfOffset.cpp rename to dbms/src/Storages/Page/workload/ThousandsOfOffset.h index 5a02ef48d68..0232ea235f1 100644 --- a/dbms/src/Storages/Page/workload/ThousandsOfOffset.cpp +++ b/dbms/src/Storages/Page/workload/ThousandsOfOffset.h @@ -169,6 +169,4 @@ class ThousandsOfOffset : public StressWorkload return true; } }; - -REGISTER_WORKLOAD(ThousandsOfOffset) } // namespace DB::PS::tests \ No newline at end of file From f3f37ae8fe551cd00f558a7728716adad7b2f8fe Mon Sep 17 00:00:00 2001 From: hongyunyan <649330952@qq.com> Date: Wed, 22 Jun 2022 10:16:36 +0800 Subject: [PATCH 116/127] Enhancement: Add how to run integration tests and microbenchmark tests in README (#5182) close pingcap/tiflash#5172, ref pingcap/tiflash#5178 --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 02af727105b..aa64e39d5ba 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,30 @@ LSAN_OPTIONS=suppressions=$WORKSPACE/tiflash/test/sanitize/asan.suppression ## Run Integration Tests -TBD. +1. Build your own tiflash binary in $BUILD with `-DCMAKE_BUILD_TYPE=DEBUG`. +``` +cd $BUILD +cmake $WORKSPACE/tiflash -GNinja -DCMAKE_BUILD_TYPE=DEBUG +ninja tiflash +``` +2. Run tidb cluster locally using tiup playgroud or other tools. +``` +tiup playground nightly --tiflash.binpath $BUILD/dbms/src/Server/tiflash +``` +3. Check $WORKSPACE/tests/_env.sh to make the port and build dir right. +4. Run your integration tests using commands like "./run-test.sh fullstack-test2/ddl" under $WORKSPACE dir + +## Run MicroBenchmark Tests + +To run micro benchmark tests, you need to build with -DCMAKE_BUILD_TYPE=RELEASE -DENABLE_TESTS=ON: + +```shell +cd $BUILD +cmake $WORKSPACE/tiflash -GNinja -DCMAKE_BUILD_TYPE=DEBUG -DENABLE_TESTS=ON +ninja bench_dbms +``` + +And the microbenchmark-test executables are at `$BUILD/dbms/bench_dbms`, you can run it with `./bench_dbms` or `./bench_dbms --benchmark_filter=xxx` . More usage please check with `./bench_dbms --help`. ## Generate LLVM Coverage Report From 649462ab275ff4a7a36b9998f4aa909d31442b92 Mon Sep 17 00:00:00 2001 From: hongyunyan <649330952@qq.com> Date: Wed, 22 Jun 2022 10:44:37 +0800 Subject: [PATCH 117/127] Enhancement: add a integrated test on DDL module (#5130) ref pingcap/tiflash#5129 --- dbms/src/Common/FailPoint.cpp | 46 +- .../Interpreters/InterpreterSelectQuery.cpp | 16 + .../Storages/Transaction/PartitionStreams.cpp | 15 + .../ddl/multi_alter_with_write.test | 880 ++++++++++++++++++ 4 files changed, 936 insertions(+), 21 deletions(-) create mode 100644 tests/fullstack-test2/ddl/multi_alter_with_write.test diff --git a/dbms/src/Common/FailPoint.cpp b/dbms/src/Common/FailPoint.cpp index c6c3caa44ad..10d0a558a50 100644 --- a/dbms/src/Common/FailPoint.cpp +++ b/dbms/src/Common/FailPoint.cpp @@ -85,33 +85,37 @@ std::unordered_map> FailPointHelper::f M(force_remote_read_for_batch_cop) \ M(force_context_path) \ M(force_slow_page_storage_snapshot_release) \ - M(force_change_all_blobs_to_read_only) - -#define APPLY_FOR_FAILPOINTS_ONCE_WITH_CHANNEL(M) \ - M(pause_with_alter_locks_acquired) \ - M(hang_in_execution) \ - M(pause_before_dt_background_delta_merge) \ - M(pause_until_dt_background_delta_merge) \ - M(pause_before_apply_raft_cmd) \ - M(pause_before_apply_raft_snapshot) \ - M(pause_until_apply_raft_snapshot) \ + M(force_change_all_blobs_to_read_only) \ + M(unblock_query_init_after_write) + + +#define APPLY_FOR_PAUSEABLE_FAILPOINTS_ONCE(M) \ + M(pause_with_alter_locks_acquired) \ + M(hang_in_execution) \ + M(pause_before_dt_background_delta_merge) \ + M(pause_until_dt_background_delta_merge) \ + M(pause_before_apply_raft_cmd) \ + M(pause_before_apply_raft_snapshot) \ + M(pause_until_apply_raft_snapshot) \ M(pause_after_copr_streams_acquired_once) -#define APPLY_FOR_FAILPOINTS_WITH_CHANNEL(M) \ - M(pause_when_reading_from_dt_stream) \ - M(pause_when_writing_to_dt_store) \ - M(pause_when_ingesting_to_dt_store) \ - M(pause_when_altering_dt_store) \ - M(pause_after_copr_streams_acquired) \ - M(pause_before_server_merge_one_delta) +#define APPLY_FOR_PAUSEABLE_FAILPOINTS(M) \ + M(pause_when_reading_from_dt_stream) \ + M(pause_when_writing_to_dt_store) \ + M(pause_when_ingesting_to_dt_store) \ + M(pause_when_altering_dt_store) \ + M(pause_after_copr_streams_acquired) \ + M(pause_before_server_merge_one_delta) \ + M(pause_query_init) + namespace FailPoints { #define M(NAME) extern const char(NAME)[] = #NAME ""; APPLY_FOR_FAILPOINTS_ONCE(M) APPLY_FOR_FAILPOINTS(M) -APPLY_FOR_FAILPOINTS_ONCE_WITH_CHANNEL(M) -APPLY_FOR_FAILPOINTS_WITH_CHANNEL(M) +APPLY_FOR_PAUSEABLE_FAILPOINTS_ONCE(M) +APPLY_FOR_PAUSEABLE_FAILPOINTS(M) #undef M } // namespace FailPoints @@ -167,11 +171,11 @@ void FailPointHelper::enableFailPoint(const String & fail_point_name) } #define M(NAME) SUB_M(NAME, FIU_ONETIME) - APPLY_FOR_FAILPOINTS_ONCE_WITH_CHANNEL(M) + APPLY_FOR_PAUSEABLE_FAILPOINTS_ONCE(M) #undef M #define M(NAME) SUB_M(NAME, 0) - APPLY_FOR_FAILPOINTS_WITH_CHANNEL(M) + APPLY_FOR_PAUSEABLE_FAILPOINTS(M) #undef M #undef SUB_M diff --git a/dbms/src/Interpreters/InterpreterSelectQuery.cpp b/dbms/src/Interpreters/InterpreterSelectQuery.cpp index 01e8625f943..fe8f04427a0 100644 --- a/dbms/src/Interpreters/InterpreterSelectQuery.cpp +++ b/dbms/src/Interpreters/InterpreterSelectQuery.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include #include @@ -93,6 +94,12 @@ extern const int SCHEMA_VERSION_ERROR; extern const int UNKNOWN_EXCEPTION; } // namespace ErrorCodes + +namespace FailPoints +{ +extern const char pause_query_init[]; +} // namespace FailPoints + InterpreterSelectQuery::InterpreterSelectQuery( const ASTPtr & query_ptr_, const Context & context_, @@ -131,6 +138,15 @@ InterpreterSelectQuery::~InterpreterSelectQuery() = default; void InterpreterSelectQuery::init(const Names & required_result_column_names) { + /// the failpoint pause_query_init should use with the failpoint unblock_query_init_after_write, + /// to fulfill that the select query action will be blocked before init state to wait the write action finished. + /// In using, we need enable unblock_query_init_after_write in our test code, + /// and before each write statement take effect, we need enable pause_query_init. + /// When the write action finished, the pause_query_init will be disabled automatically, + /// and then the select query could be continued. + /// you can refer multi_alter_with_write.test for an example. + FAIL_POINT_PAUSE(FailPoints::pause_query_init); + if (!context.hasQueryContext()) context.setQueryContext(context); diff --git a/dbms/src/Storages/Transaction/PartitionStreams.cpp b/dbms/src/Storages/Transaction/PartitionStreams.cpp index 456f067fe5e..cf151c4270d 100644 --- a/dbms/src/Storages/Transaction/PartitionStreams.cpp +++ b/dbms/src/Storages/Transaction/PartitionStreams.cpp @@ -40,6 +40,8 @@ namespace FailPoints extern const char pause_before_apply_raft_cmd[]; extern const char pause_before_apply_raft_snapshot[]; extern const char force_set_safepoint_when_decode_block[]; +extern const char unblock_query_init_after_write[]; +extern const char pause_query_init[]; } // namespace FailPoints namespace ErrorCodes @@ -151,6 +153,7 @@ static void writeRegionDataToStorage( default: throw Exception("Unknown StorageEngine: " + toString(static_cast(storage->engineType())), ErrorCodes::LOGICAL_ERROR); } + write_part_cost = watch.elapsedMilliseconds(); GET_METRIC(tiflash_raft_write_data_to_storage_duration_seconds, type_write).Observe(write_part_cost / 1000.0); if (need_decode) @@ -165,10 +168,20 @@ static void writeRegionDataToStorage( /// decoding data. Check the test case for more details. FAIL_POINT_PAUSE(FailPoints::pause_before_apply_raft_cmd); + /// disable pause_query_init when the write action finish, to make the query action continue. + /// the usage of unblock_query_init_after_write and pause_query_init can refer to InterpreterSelectQuery::init + SCOPE_EXIT({ + fiu_do_on(FailPoints::unblock_query_init_after_write, { + FailPointHelper::disableFailPoint(FailPoints::pause_query_init); + }); + }); + /// Try read then write once. { if (atomic_read_write(false)) + { return; + } } /// If first try failed, sync schema and force read then write. @@ -177,10 +190,12 @@ static void writeRegionDataToStorage( tmt.getSchemaSyncer()->syncSchemas(context); if (!atomic_read_write(true)) + { // Failure won't be tolerated this time. // TODO: Enrich exception message. throw Exception("Write region " + std::to_string(region->id()) + " to table " + std::to_string(table_id) + " failed", ErrorCodes::LOGICAL_ERROR); + } } } diff --git a/tests/fullstack-test2/ddl/multi_alter_with_write.test b/tests/fullstack-test2/ddl/multi_alter_with_write.test new file mode 100644 index 00000000000..3284511d775 --- /dev/null +++ b/tests/fullstack-test2/ddl/multi_alter_with_write.test @@ -0,0 +1,880 @@ +# Copyright 2022 PingCAP, Ltd. +# +# 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. + +# this test focus on the case when multi DDL actions happen closely +#( and these DDL actions will be fetched in the same regular sync schema duration.) +# and there are some corresponding insert(write) actions between these DDL actions. +# Considering that these write actions and these schema change will arrive at +# tiflash in a different order, we simulate these different order situation to check +# that our schema module was working correctly. + +# TiDB Timeline : write cmd 1 | alter cmd 1 | write cmd 2 | alter cmd 2 | write cmd 3 + +# stop regular schema sync +=> DBGInvoke __enable_schema_sync_service('false') + +# Enable the failpoint and make it pause before applying the raft cmd to write a row +>> DBGInvoke __init_fail_point() +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# Enable the failpoint to make our query only start when the write action finished +>> DBGInvoke __enable_fail_point(unblock_query_init_after_write) + +# ----------------------------------------------------------------------------- +# Order 1 : write cmd 1 | alter cmd 1 | write cmd 2 | alter cmd 2 | write cmd 3 +# ----------------------------------------------------------------------------- + +mysql> drop table if exists test.t +mysql> create table test.t(a int primary key, b decimal(5,2) not NULL, c varchar(10), d int default 0); + +mysql> alter table test.t set tiflash replica 1; + +func> wait_table test t + +# write cmd 1 +mysql> insert into test.t (a, b, c) values (1, 4.5, 'abc'); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 1 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┐ +│ 1 │ 4.50 │ abc │ 0 │ +└─────┴──────┴───────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# alter cmd 1 +mysql> alter table test.t add column e decimal(6,1) NULL; + +# make alter cmd 1 take effect +>> DBGInvoke __refresh_schemas() + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 1 │ 4.50 │ abc │ 0 │ \N │ +└─────┴──────┴───────┴─────┴─────┘ + +# write cmd 2 +mysql> insert into test.t values (3, 0.2, 'ccc', 3, 0.1); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 2 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 1 │ 4.50 │ abc │ 0 │ \N │ +│ 3 │ 0.20 │ ccc │ 3 │ 0.1 │ +└─────┴──────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# alter cmd 2 +mysql> alter table test.t drop column b; + +# make alter cmd 2 take effect +>> DBGInvoke __refresh_schemas() + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +└─────┴───────┴─────┴─────┘ + +# write cmd 3 +mysql> insert into test.t values (4, 'abcd', 10, 0.2); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 3 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +│ 4 │ abcd │ 10 │ 0.2 │ +└─────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# --------------------------------------------------------------------------------------------- +# Order 2 : write cmd 1 | alter cmd 1 | write cmd 2 | write cmd 3 --> sync schema(alter cmd 2) +# --------------------------------------------------------------------------------------------- + +mysql> drop table if exists test.t +mysql> create table test.t(a int primary key, b decimal(5,2) not NULL, c varchar(10), d int default 0); + +mysql> alter table test.t set tiflash replica 1; + +func> wait_table test t + +# write cmd 1 +mysql> insert into test.t (a, b, c) values (1, 4.5, 'abc'); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 1 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┐ +│ 1 │ 4.50 │ abc │ 0 │ +└─────┴──────┴───────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# alter cmd 1 +mysql> alter table test.t add column e decimal(6,1) NULL; + +# make alter cmd 1 take effect +>> DBGInvoke __refresh_schemas() + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 1 │ 4.50 │ abc │ 0 │ \N │ +└─────┴──────┴───────┴─────┴─────┘ + +# write cmd 2 +mysql> insert into test.t values (3, 0.2, 'ccc', 3, 0.1); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 2 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 1 │ 4.50 │ abc │ 0 │ \N │ +│ 3 │ 0.20 │ ccc │ 3 │ 0.1 │ +└─────┴──────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# alter cmd 2 +mysql> alter table test.t drop column b; + +# write cmd 3 +mysql> insert into test.t values (4, 'abcd', 10, 0.2); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 3 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +# check what happen after write cmd 3 --> call sync schema and get alter cmd 2 happen +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +│ 4 │ abcd │ 10 │ 0.2 │ +└─────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# ----------------------------------------------------------------------------------------------- +# Order 3 : write cmd 1 | alter cmd 1 | alter cmd 2 | write cmd 2 -->sync schema() | write cmd 3 +# ----------------------------------------------------------------------------------------------- + +mysql> drop table if exists test.t +mysql> create table test.t(a int primary key, b decimal(5,2) not NULL, c varchar(10), d int default 0); + +mysql> alter table test.t set tiflash replica 1; + +func> wait_table test t + +# write cmd 1 +mysql> insert into test.t (a, b, c) values (1, 4.5, 'abc'); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 1 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┐ +│ 1 │ 4.50 │ abc │ 0 │ +└─────┴──────┴───────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# alter cmd 1 +mysql> alter table test.t add column e decimal(6,1) NULL; + +# make alter cmd 1 take effect +>> DBGInvoke __refresh_schemas() + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 1 │ 4.50 │ abc │ 0 │ \N │ +└─────┴──────┴───────┴─────┴─────┘ + +# write cmd 2 +mysql> insert into test.t values (3, 0.2, 'ccc', 3, 0.1); + +# alter cmd 2 +mysql> alter table test.t drop column b; + +# make alter cmd 2 take effect +>> DBGInvoke __refresh_schemas() + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 2 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +└─────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# write cmd 3 +mysql> insert into test.t values (4, 'abcd', 10, 0.2); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 3 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +│ 4 │ abcd │ 10 │ 0.2 │ +└─────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# ----------------------------------------------------------------------------------------------- +# Order 4 : write cmd 1 | write cmd 2 --> sync schema(alter cmd 1) | alter cmd 2 | write cmd 3 +# ----------------------------------------------------------------------------------------------- + +mysql> drop table if exists test.t +mysql> create table test.t(a int primary key, b decimal(5,2) not NULL, c varchar(10), d int default 0); + +mysql> alter table test.t set tiflash replica 1; + +func> wait_table test t + +# write cmd 1 +mysql> insert into test.t (a, b, c) values (1, 4.5, 'abc'); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 1 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┐ +│ 1 │ 4.50 │ abc │ 0 │ +└─────┴──────┴───────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# alter cmd 1 +mysql> alter table test.t add column e decimal(6,1) NULL; + +# check no schema change before write cmd 2 take effect +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┐ +│ 1 │ 4.50 │ abc │ 0 │ +└─────┴──────┴───────┴─────┘ + +# write cmd 2 +mysql> insert into test.t values (3, 0.2, 'ccc', 3, 0.1); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 2 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +# check what happen after write cmd 2 --> should call sync schema, get the alter cmd 1 happened. +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 1 │ 4.50 │ abc │ 0 │ \N │ +│ 3 │ 0.20 │ ccc │ 3 │ 0.1 │ +└─────┴──────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# alter cmd 2 +mysql> alter table test.t drop column b; + +# make alter cmd 2 take effect +>> DBGInvoke __refresh_schemas() + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +└─────┴───────┴─────┴─────┘ + +# write cmd 3 +mysql> insert into test.t values (4, 'abcd', 10, 0.2); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 3 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +│ 4 │ abcd │ 10 │ 0.2 │ +└─────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# --------------------------------------------------------------------------------------------------------------------- +# Order 5 : write cmd 1 | write cmd 2 --> sync schema(alter cmd 1) | write cmd 3 --> sync schema(alter cmd 2) +# ---------------------------------------------------------------------------------------------------------------------- + +mysql> drop table if exists test.t +mysql> create table test.t(a int primary key, b decimal(5,2) not NULL, c varchar(10), d int default 0); + +mysql> alter table test.t set tiflash replica 1; + +func> wait_table test t + +# write cmd 1 +mysql> insert into test.t (a, b, c) values (1, 4.5, 'abc'); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 1 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┐ +│ 1 │ 4.50 │ abc │ 0 │ +└─────┴──────┴───────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# alter cmd 1 +mysql> alter table test.t add column e decimal(6,1) NULL; + +# write cmd 2 +mysql> insert into test.t values (3, 0.2, 'ccc', 3, 0.1); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 2 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +# check what happen after write cmd 2 --> should call sync schema, get the alter cmd 1 happened. +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 1 │ 4.50 │ abc │ 0 │ \N │ +│ 3 │ 0.20 │ ccc │ 3 │ 0.1 │ +└─────┴──────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# alter cmd 2 +mysql> alter table test.t drop column b; + +# write cmd 3 +mysql> insert into test.t values (4, 'abcd', 10, 0.2); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 3 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +# check what happen after write cmd 3 --> should call sync schema, get the alter cmd 2 happened. +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +│ 4 │ abcd │ 10 │ 0.2 │ +└─────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# ----------------------------------------------------------------------------------------------- +# Order 6 : write cmd 1 | write cmd 2 --> sync schema(alter cmd 1 alter cmd 2) | write cmd 3 +# ----------------------------------------------------------------------------------------------- + +mysql> drop table if exists test.t +mysql> create table test.t(a int primary key, b decimal(5,2) not NULL, c varchar(10), d int default 0); + +mysql> alter table test.t set tiflash replica 1; + +func> wait_table test t + +# write cmd 1 +mysql> insert into test.t (a, b, c) values (1, 4.5, 'abc'); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 1 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┐ +│ 1 │ 4.50 │ abc │ 0 │ +└─────┴──────┴───────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# alter cmd 1 +mysql> alter table test.t add column e decimal(6,1) NULL; + +# write cmd 2 +mysql> insert into test.t values (3, 0.2, 'ccc', 3, 0.1); + +# alter cmd 2 +mysql> alter table test.t drop column b; + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 2 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +# check what happen after write cmd 2 --> should call sync schema, get the alter cmd 1 && alter cmd 2 happened. +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +└─────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# write cmd 3 +mysql> insert into test.t values (4, 'abcd', 10, 0.2); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 3 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +│ 4 │ abcd │ 10 │ 0.2 │ +└─────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# ------------------------------------------------------------------------------- +# Order 7 : alter cmd 1 | write cmd 1 | write cmd 2 | alter cmd 2 | write cmd 3 +# ------------------------------------------------------------------------------- + +mysql> drop table if exists test.t +mysql> create table test.t(a int primary key, b decimal(5,2) not NULL, c varchar(10), d int default 0); + +mysql> alter table test.t set tiflash replica 1; + +func> wait_table test t + +# add a new pre write to make check the alter cmd 1 more convenient. +mysql> insert into test.t (a, b, c) values (0, 0, ' '); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┐ +│ 0 │ 0.00 │ │ 0 │ +└─────┴──────┴───────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + + +# alter cmd 1 +mysql> alter table test.t add column e decimal(6,1) NULL; + +# make alter cmd 1 take effect +>> DBGInvoke __refresh_schemas() + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 0 │ 0.00 │ │ 0 │ \N │ +└─────┴──────┴───────┴─────┴─────┘ + +# write cmd 1 +mysql> insert into test.t (a, b, c) values (1, 4.5, 'abc'); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 1 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 0 │ 0.00 │ │ 0 │ \N │ +│ 1 │ 4.50 │ abc │ 0 │ \N │ +└─────┴──────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# write cmd 2 +mysql> insert into test.t values (3, 0.2, 'ccc', 3, 0.1); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 2 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 0 │ 0.00 │ │ 0 │ \N │ +│ 1 │ 4.50 │ abc │ 0 │ \N │ +│ 3 │ 0.20 │ ccc │ 3 │ 0.1 │ +└─────┴──────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# alter cmd 2 +mysql> alter table test.t drop column b; + +# make alter cmd 2 take effect +>> DBGInvoke __refresh_schemas() + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 0 │ │ 0 │ \N │ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +└─────┴───────┴─────┴─────┘ + +# write cmd 3 +mysql> insert into test.t values (4, 'abcd', 10, 0.2); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 3 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 0 │ │ 0 │ \N │ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +│ 4 │ abcd │ 10 │ 0.2 │ +└─────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# -------------------------------------------------------------------------------------------------- +# Order 8 : alter cmd 1 | write cmd 1 | write cmd 2 | write cmd 3 --> sync schema(alter cmd 2) +# -------------------------------------------------------------------------------------------------- + +mysql> drop table if exists test.t +mysql> create table test.t(a int primary key, b decimal(5,2) not NULL, c varchar(10), d int default 0); + +mysql> alter table test.t set tiflash replica 1; + +func> wait_table test t + +# add a new pre write to make check the alter cmd 1 more convenient. +mysql> insert into test.t (a, b, c) values (0, 0, ' '); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┐ +│ 0 │ 0.00 │ │ 0 │ +└─────┴──────┴───────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# alter cmd 1 +mysql> alter table test.t add column e decimal(6,1) NULL; + +# make alter cmd 1 take effect +>> DBGInvoke __refresh_schemas() + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 0 │ 0.00 │ │ 0 │ \N │ +└─────┴──────┴───────┴─────┴─────┘ + +# write cmd 1 +mysql> insert into test.t (a, b, c) values (1, 4.5, 'abc'); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 1 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 0 │ 0.00 │ │ 0 │ \N │ +│ 1 │ 4.50 │ abc │ 0 │ \N │ +└─────┴──────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# write cmd 2 +mysql> insert into test.t values (3, 0.2, 'ccc', 3, 0.1); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 2 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 0 │ 0.00 │ │ 0 │ \N │ +│ 1 │ 4.50 │ abc │ 0 │ \N │ +│ 3 │ 0.20 │ ccc │ 3 │ 0.1 │ +└─────┴──────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# alter cmd 2 +mysql> alter table test.t drop column b; + +# write cmd 3 +mysql> insert into test.t values (4, 'abcd', 10, 0.2); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 3 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +# check what happen after write cmd 3 --> should call sync schema, get the alter cmd 2 happened. +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 0 │ │ 0 │ \N │ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +│ 4 │ abcd │ 10 │ 0.2 │ +└─────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# -------------------------------------------------------------------------------------------------- +# Order 9 : alter cmd 1 | write cmd 1 | alter cmd 2 | write cmd 2 -->sync schema() | write cmd 3 +# -------------------------------------------------------------------------------------------------- + +mysql> drop table if exists test.t +mysql> create table test.t(a int primary key, b decimal(5,2) not NULL, c varchar(10), d int default 0); + +mysql> alter table test.t set tiflash replica 1; + +func> wait_table test t + +# add a new pre write to make check the alter cmd 1 more convenient. +mysql> insert into test.t (a, b, c) values (0, 0, ' '); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┐ +│ 0 │ 0.00 │ │ 0 │ +└─────┴──────┴───────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# alter cmd 1 +mysql> alter table test.t add column e decimal(6,1) NULL; + +# make alter cmd 1 take effect +>> DBGInvoke __refresh_schemas() + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 0 │ 0.00 │ │ 0 │ \N │ +└─────┴──────┴───────┴─────┴─────┘ + +# write cmd 1 +mysql> insert into test.t (a, b, c) values (1, 4.5, 'abc'); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 1 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┬─e───┐ +│ 0 │ 0.00 │ │ 0 │ \N │ +│ 1 │ 4.50 │ abc │ 0 │ \N │ +└─────┴──────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# write cmd 2 +mysql> insert into test.t values (3, 0.2, 'ccc', 3, 0.1); + +# alter cmd 2 +mysql> alter table test.t drop column b; + +# make alter cmd 2 take effect +>> DBGInvoke __refresh_schemas() + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 2 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 0 │ │ 0 │ \N │ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +└─────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# write cmd 3 +mysql> insert into test.t values (4, 'abcd', 10, 0.2); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 3 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 0 │ │ 0 │ \N │ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +│ 4 │ abcd │ 10 │ 0.2 │ +└─────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# ------------------------------------------------------------------------------------------------------------------ +# Order 10 : alter cmd 1 | alter cmd 2 | write cmd 1 -->sync schema() | write cmd 2 -->sync schema() | write cmd 3 +# ------------------------------------------------------------------------------------------------------------------ + +mysql> drop table if exists test.t +mysql> create table test.t(a int primary key, b decimal(5,2) not NULL, c varchar(10), d int default 0); + +mysql> alter table test.t set tiflash replica 1; + +func> wait_table test t + +# add a new pre write to make check the alter cmd 1 more convenient. +mysql> insert into test.t (a, b, c) values (0, 0, ' '); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─b────┬─c─────┬─d───┐ +│ 0 │ 0.00 │ │ 0 │ +└─────┴──────┴───────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# write cmd 1 +mysql> insert into test.t (a, b, c) values (1, 4.5, 'abc'); + +# alter cmd 1 +mysql> alter table test.t add column e decimal(6,1) NULL; + +# make alter cmd 1 take effect +>> DBGInvoke __refresh_schemas() + +# write cmd 2 +mysql> insert into test.t values (3, 0.2, 'ccc', 3, 0.1); + +# alter cmd 2 +mysql> alter table test.t drop column b; + +# make alter cmd 2 take effect +>> DBGInvoke __refresh_schemas() + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 1 and write cmd 2 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 0 │ │ 0 │ \N │ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +└─────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + +# write cmd 3 +mysql> insert into test.t values (4, 'abcd', 10, 0.2); + +# enable pause_query_init make query start until write cmd finish +>> DBGInvoke __enable_fail_point(pause_query_init) + +# make write cmd 3 take effect +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) + +=> DBGInvoke query_mapped('select * from \$d.\$t', test, t) +┌─a───┬─c─────┬─d───┬─e───┐ +│ 0 │ │ 0 │ \N │ +│ 1 │ abc │ 0 │ \N │ +│ 3 │ ccc │ 3 │ 0.1 │ +│ 4 │ abcd │ 10 │ 0.2 │ +└─────┴───────┴─────┴─────┘ + +>> DBGInvoke __enable_fail_point(pause_before_apply_raft_cmd) + + +## + +=> DBGInvoke __enable_schema_sync_service('true') +>> DBGInvoke __disable_fail_point(pause_before_apply_raft_cmd) +>> DBGInvoke __disable_fail_point(unblock_query_init_after_write) +>> DBGInvoke __disable_fail_point(pause_query_init) \ No newline at end of file From 45bc5a45f680dd9d1846616c2dd9ecf3be89dcd6 Mon Sep 17 00:00:00 2001 From: Lloyd-Pottiger <60744015+Lloyd-Pottiger@users.noreply.github.com> Date: Wed, 22 Jun 2022 11:22:36 +0800 Subject: [PATCH 118/127] Revert "Revise default background threads size" (#5176) close pingcap/tiflash#5177 --- dbms/src/Core/Defines.h | 1 + dbms/src/Interpreters/Context.cpp | 24 ++++---------- dbms/src/Interpreters/Context.h | 4 +-- dbms/src/Interpreters/Settings.h | 6 ++-- dbms/src/Server/Server.cpp | 33 ++++++++----------- .../src/Storages/BackgroundProcessingPool.cpp | 3 -- dbms/src/Storages/BackgroundProcessingPool.h | 4 +-- dbms/src/TestUtils/TiFlashTestEnv.cpp | 6 ---- 8 files changed, 27 insertions(+), 54 deletions(-) diff --git a/dbms/src/Core/Defines.h b/dbms/src/Core/Defines.h index 75f6f16bb25..33d116dae33 100644 --- a/dbms/src/Core/Defines.h +++ b/dbms/src/Core/Defines.h @@ -78,6 +78,7 @@ /// too short a period can cause errors to disappear immediately after creation. #define DBMS_CONNECTION_POOL_WITH_FAILOVER_DEFAULT_DECREASE_ERROR_PERIOD (2 * DBMS_DEFAULT_SEND_TIMEOUT_SEC) #define DEFAULT_QUERIES_QUEUE_WAIT_TIME_MS 5000 /// Maximum waiting time in the request queue. +#define DBMS_DEFAULT_BACKGROUND_POOL_SIZE 16 #define DBMS_MIN_REVISION_WITH_CLIENT_INFO 54032 #define DBMS_MIN_REVISION_WITH_SERVER_TIMEZONE 54058 diff --git a/dbms/src/Interpreters/Context.cpp b/dbms/src/Interpreters/Context.cpp index 2dbd495d2c4..44699a324f4 100644 --- a/dbms/src/Interpreters/Context.cpp +++ b/dbms/src/Interpreters/Context.cpp @@ -68,8 +68,10 @@ #include #include +#include #include -#include +#include + namespace ProfileEvents { @@ -1439,33 +1441,19 @@ void Context::dropCaches() const } BackgroundProcessingPool & Context::getBackgroundPool() -{ - // Note: shared->background_pool should be initialized first. - auto lock = getLock(); - return *shared->background_pool; -} - -BackgroundProcessingPool & Context::initializeBackgroundPool(UInt16 pool_size) { auto lock = getLock(); if (!shared->background_pool) - shared->background_pool = std::make_shared(pool_size); + shared->background_pool = std::make_shared(settings.background_pool_size); return *shared->background_pool; } BackgroundProcessingPool & Context::getBlockableBackgroundPool() { - // TODO: maybe a better name for the pool - // Note: shared->blockable_background_pool should be initialized first. - auto lock = getLock(); - return *shared->blockable_background_pool; -} - -BackgroundProcessingPool & Context::initializeBlockableBackgroundPool(UInt16 pool_size) -{ + // TODO: choose a better thread pool size and maybe a better name for the pool auto lock = getLock(); if (!shared->blockable_background_pool) - shared->blockable_background_pool = std::make_shared(pool_size); + shared->blockable_background_pool = std::make_shared(settings.background_pool_size); return *shared->blockable_background_pool; } diff --git a/dbms/src/Interpreters/Context.h b/dbms/src/Interpreters/Context.h index 434179e1ab8..b6e759e364b 100644 --- a/dbms/src/Interpreters/Context.h +++ b/dbms/src/Interpreters/Context.h @@ -380,9 +380,7 @@ class Context bool useL0Opt() const; BackgroundProcessingPool & getBackgroundPool(); - BackgroundProcessingPool & initializeBackgroundPool(UInt16 pool_size); BackgroundProcessingPool & getBlockableBackgroundPool(); - BackgroundProcessingPool & initializeBlockableBackgroundPool(UInt16 pool_size); void createTMTContext(const TiFlashRaftConfig & raft_config, pingcap::ClusterConfig && cluster_config); @@ -507,7 +505,7 @@ class DDLGuard class SessionCleaner { public: - explicit SessionCleaner(Context & context_) + SessionCleaner(Context & context_) : context{context_} {} ~SessionCleaner(); diff --git a/dbms/src/Interpreters/Settings.h b/dbms/src/Interpreters/Settings.h index f2b3bbbd7fe..9361e0525d2 100644 --- a/dbms/src/Interpreters/Settings.h +++ b/dbms/src/Interpreters/Settings.h @@ -80,8 +80,8 @@ struct Settings M(SettingBool, extremes, false, "Calculate minimums and maximums of the result columns. They can be output in JSON-formats.") \ M(SettingBool, use_uncompressed_cache, true, "Whether to use the cache of uncompressed blocks.") \ M(SettingBool, replace_running_query, false, "Whether the running request should be canceled with the same id as the new one.") \ - M(SettingUInt64, background_pool_size, 0, "Number of threads performing background work for tables (for example, merging in merge tree). Only effective at server startup. " \ - "0 means a quarter of the number of logical CPU cores of the machine.") \ + M(SettingUInt64, background_pool_size, DBMS_DEFAULT_BACKGROUND_POOL_SIZE, "Number of threads performing background work for tables (for example, merging in merge tree). Only has meaning at server " \ + "startup.") \ \ M(SettingBool, optimize_move_to_prewhere, true, "Allows disabling WHERE to PREWHERE optimization in SELECT queries from MergeTree.") \ \ @@ -356,7 +356,7 @@ struct Settings M(SettingUInt64, elastic_threadpool_shrink_period_ms, 300000, "The shrink period(ms) of elastic thread pool.") \ M(SettingBool, enable_local_tunnel, true, "Enable local data transfer between local MPP tasks.") \ M(SettingBool, enable_async_grpc_client, true, "Enable async grpc in MPP.") \ - M(SettingUInt64, grpc_completion_queue_pool_size, 0, "The size of gRPC completion queue pool. 0 means the number of logical CPU cores. Only effective at server startup")\ + M(SettingUInt64, grpc_completion_queue_pool_size, 0, "The size of gRPC completion queue pool. 0 means using hardware_concurrency.")\ M(SettingBool, enable_async_server, true, "Enable async rpc server.") \ M(SettingUInt64, async_pollers_per_cq, 200, "grpc async pollers per cqs") \ M(SettingUInt64, async_cqs, 1, "grpc async cqs") \ diff --git a/dbms/src/Server/Server.cpp b/dbms/src/Server/Server.cpp index 186ab0889d8..1bb35e51866 100644 --- a/dbms/src/Server/Server.cpp +++ b/dbms/src/Server/Server.cpp @@ -53,15 +53,10 @@ #include #include #include -#include -#include -#include #include #include #include -#include #include -#include #include #include #include @@ -86,6 +81,12 @@ #include #include +#include "HTTPHandlerFactory.h" +#include "MetricsPrometheus.h" +#include "MetricsTransmitter.h" +#include "StatusFile.h" +#include "TCPHandlerFactory.h" + #if Poco_NetSSL_FOUND #include #include @@ -1127,19 +1128,6 @@ int Server::main(const std::vector & /*args*/) global_context->getPathCapacity(), global_context->getFileProvider()); - /// if default value of background_pool_size is 0 - /// set it to the a quarter of the number of logical CPU cores of machine. - Settings & settings = global_context->getSettingsRef(); - if (settings.background_pool_size == 0) - { - global_context->setSetting("background_pool_size", std::to_string(server_info.cpu_info.logical_cores / 4)); - } - LOG_FMT_INFO(log, "Background & Blockable Background pool size: {}", settings.background_pool_size); - - /// Initialize the background & blockable background thread pool. - auto & bg_pool = global_context->initializeBackgroundPool(settings.background_pool_size); - auto & blockable_bg_pool = global_context->initializeBlockableBackgroundPool(settings.background_pool_size); - global_context->initializePageStorageMode(global_context->getPathPool(), STORAGE_FORMAT_CURRENT.page); global_context->initializeGlobalStoragePoolIfNeed(global_context->getPathPool()); LOG_FMT_INFO(log, "Global PageStorage run mode is {}", static_cast(global_context->getPageStorageRunMode())); @@ -1256,6 +1244,13 @@ int Server::main(const std::vector & /*args*/) /// Load global settings from default_profile and system_profile. /// It internally depends on UserConfig::parseSettings. global_context->setDefaultProfiles(config()); + Settings & settings = global_context->getSettingsRef(); + + /// Initialize the background thread pool. + /// It internally depends on settings.background_pool_size, + /// so must be called after settings has been load. + auto & bg_pool = global_context->getBackgroundPool(); + auto & blockable_bg_pool = global_context->getBlockableBackgroundPool(); /// Initialize RateLimiter. global_context->initializeRateLimiter(config(), bg_pool, blockable_bg_pool); @@ -1407,7 +1402,7 @@ int Server::main(const std::vector & /*args*/) { auto size = settings.grpc_completion_queue_pool_size; if (size == 0) - size = server_info.cpu_info.logical_cores; + size = std::thread::hardware_concurrency(); GRPCCompletionQueuePool::global_instance = std::make_unique(size); } diff --git a/dbms/src/Storages/BackgroundProcessingPool.cpp b/dbms/src/Storages/BackgroundProcessingPool.cpp index 15740fa2875..45ba032bf53 100644 --- a/dbms/src/Storages/BackgroundProcessingPool.cpp +++ b/dbms/src/Storages/BackgroundProcessingPool.cpp @@ -86,9 +86,6 @@ BackgroundProcessingPool::BackgroundProcessingPool(int size_) : size(size_) , thread_ids_counter(size_) { - if (size <= 0) - throw Exception("BackgroundProcessingPool size must be greater than 0", ErrorCodes::LOGICAL_ERROR); - LOG_FMT_INFO(&Poco::Logger::get("BackgroundProcessingPool"), "Create BackgroundProcessingPool with {} threads", size); threads.resize(size); diff --git a/dbms/src/Storages/BackgroundProcessingPool.h b/dbms/src/Storages/BackgroundProcessingPool.h index 49a01b3a397..1ba6c4efcf8 100644 --- a/dbms/src/Storages/BackgroundProcessingPool.h +++ b/dbms/src/Storages/BackgroundProcessingPool.h @@ -81,7 +81,7 @@ class BackgroundProcessingPool using TaskHandle = std::shared_ptr; - explicit BackgroundProcessingPool(int size_); + BackgroundProcessingPool(int size_); size_t getNumberOfThreads() const { return size; } @@ -96,7 +96,7 @@ class BackgroundProcessingPool /// 2. thread B also get the same task /// 3. thread A finish the execution of the task quickly, release the task and try to update the next schedule time of the task /// 4. thread B find the task is not occupied and execute the task again almost immediately - TaskHandle addTask(const Task & task, bool multi = true, size_t interval_ms = 0); + TaskHandle addTask(const Task & task, const bool multi = true, const size_t interval_ms = 0); void removeTask(const TaskHandle & task); ~BackgroundProcessingPool(); diff --git a/dbms/src/TestUtils/TiFlashTestEnv.cpp b/dbms/src/TestUtils/TiFlashTestEnv.cpp index a7bcfe43d7a..cbd42b57550 100644 --- a/dbms/src/TestUtils/TiFlashTestEnv.cpp +++ b/dbms/src/TestUtils/TiFlashTestEnv.cpp @@ -24,8 +24,6 @@ #include #include -#include - namespace DB::tests { std::unique_ptr TiFlashTestEnv::global_context = nullptr; @@ -41,10 +39,6 @@ void TiFlashTestEnv::initializeGlobalContext(Strings testdata_path, PageStorageR KeyManagerPtr key_manager = std::make_shared(false); global_context->initializeFileProvider(key_manager, false); - // initialize background & blockable background thread pool - global_context->initializeBackgroundPool(std::thread::hardware_concurrency() / 4); - global_context->initializeBlockableBackgroundPool(std::thread::hardware_concurrency() / 4); - // Theses global variables should be initialized by the following order // 1. capacity // 2. path pool From bfceb28168082e4107ccf3828ee720ca3139e22d Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Wed, 22 Jun 2022 14:42:37 +0800 Subject: [PATCH 119/127] chore: remove extra dyn cast (#5186) close pingcap/tiflash#5185 --- dbms/src/Flash/Coprocessor/ArrowColCodec.cpp | 75 ++++++++++---------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/dbms/src/Flash/Coprocessor/ArrowColCodec.cpp b/dbms/src/Flash/Coprocessor/ArrowColCodec.cpp index a1c6061948a..1609c83b029 100644 --- a/dbms/src/Flash/Coprocessor/ArrowColCodec.cpp +++ b/dbms/src/Flash/Coprocessor/ArrowColCodec.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -41,7 +40,7 @@ extern const int NOT_IMPLEMENTED; const IColumn * getNestedCol(const IColumn * flash_col) { if (flash_col->isColumnNullable()) - return dynamic_cast(flash_col)->getNestedColumnPtr().get(); + return static_cast(flash_col)->getNestedColumnPtr().get(); else return flash_col; } @@ -75,8 +74,8 @@ bool flashDecimalColToArrowColInternal( const IColumn * nested_col = getNestedCol(flash_col_untyped); if (checkColumn>(nested_col) && checkDataType>(data_type)) { - const ColumnDecimal * flash_col = checkAndGetColumn>(nested_col); - const DataTypeDecimal * type = checkAndGetDataType>(data_type); + const auto * flash_col = checkAndGetColumn>(nested_col); + const auto * type = checkAndGetDataType>(data_type); UInt32 scale = type->getScale(); for (size_t i = start_index; i < end_index; i++) { @@ -92,8 +91,8 @@ bool flashDecimalColToArrowColInternal( std::vector digits; digits.reserve(type->getPrec()); decimalToVector(dec.value, digits, scale); - TiDBDecimal tiDecimal(scale, digits, dec.value < 0); - dag_column.append(tiDecimal); + TiDBDecimal ti_decimal(scale, digits, dec.value < 0); + dag_column.append(ti_decimal); } return true; } @@ -121,7 +120,7 @@ template bool flashIntegerColToArrowColInternal(TiDBColumn & dag_column, const IColumn * flash_col_untyped, size_t start_index, size_t end_index) { const IColumn * nested_col = getNestedCol(flash_col_untyped); - if (const ColumnVector * flash_col = checkAndGetColumn>(nested_col)) + if (const auto * flash_col = checkAndGetColumn>(nested_col)) { constexpr bool is_unsigned = std::is_unsigned_v; for (size_t i = start_index; i < end_index; i++) @@ -135,9 +134,9 @@ bool flashIntegerColToArrowColInternal(TiDBColumn & dag_column, const IColumn * } } if constexpr (is_unsigned) - dag_column.append((UInt64)flash_col->getElement(i)); + dag_column.append(static_cast(flash_col->getElement(i))); else - dag_column.append((Int64)flash_col->getElement(i)); + dag_column.append(static_cast(flash_col->getElement(i))); } return true; } @@ -148,7 +147,7 @@ template void flashDoubleColToArrowCol(TiDBColumn & dag_column, const IColumn * flash_col_untyped, size_t start_index, size_t end_index) { const IColumn * nested_col = getNestedCol(flash_col_untyped); - if (const ColumnVector * flash_col = checkAndGetColumn>(nested_col)) + if (const auto * flash_col = checkAndGetColumn>(nested_col)) { for (size_t i = start_index; i < end_index; i++) { @@ -160,7 +159,7 @@ void flashDoubleColToArrowCol(TiDBColumn & dag_column, const IColumn * flash_col continue; } } - dag_column.append((T)flash_col->getElement(i)); + dag_column.append(static_cast(flash_col->getElement(i))); } return; } @@ -196,7 +195,7 @@ void flashDateOrDateTimeColToArrowCol( { const IColumn * nested_col = getNestedCol(flash_col_untyped); using DateFieldType = DataTypeMyTimeBase::FieldType; - auto * flash_col = checkAndGetColumn>(nested_col); + const auto * flash_col = checkAndGetColumn>(nested_col); for (size_t i = start_index; i < end_index; i++) { if constexpr (is_nullable) @@ -217,7 +216,7 @@ void flashStringColToArrowCol(TiDBColumn & dag_column, const IColumn * flash_col { const IColumn * nested_col = getNestedCol(flash_col_untyped); // columnFixedString is not used so do not check it - auto * flash_col = checkAndGetColumn(nested_col); + const auto * flash_col = checkAndGetColumn(nested_col); for (size_t i = start_index; i < end_index; i++) { // todo check if we can convert flash_col to DAG col directly since the internal representation is almost the same @@ -242,7 +241,7 @@ void flashBitColToArrowCol( const tipb::FieldType & field_type) { const IColumn * nested_col = getNestedCol(flash_col_untyped); - auto * flash_col = checkAndGetColumn>(nested_col); + const auto * flash_col = checkAndGetColumn>(nested_col); for (size_t i = start_index; i < end_index; i++) { if constexpr (is_nullable) @@ -267,7 +266,7 @@ void flashEnumColToArrowCol( const IDataType * data_type) { const IColumn * nested_col = getNestedCol(flash_col_untyped); - auto * flash_col = checkAndGetColumn>(nested_col); + const auto * flash_col = checkAndGetColumn>(nested_col); const auto * enum_type = checkAndGetDataType(data_type); size_t enum_value_size = enum_type->getValues().size(); for (size_t i = start_index; i < end_index; i++) @@ -280,10 +279,10 @@ void flashEnumColToArrowCol( continue; } } - auto enum_value = (UInt64)flash_col->getElement(i); + auto enum_value = static_cast(flash_col->getElement(i)); if (enum_value == 0 || enum_value > enum_value_size) throw TiFlashException("number of enum overflow enum boundary", Errors::Coprocessor::Internal); - TiDBEnum ti_enum(enum_value, enum_type->getNameForValue((const DataTypeEnum16::FieldType)enum_value)); + TiDBEnum ti_enum(enum_value, enum_type->getNameForValue(static_cast(enum_value))); dag_column.append(ti_enum); } } @@ -300,7 +299,7 @@ void flashColToArrowCol(TiDBColumn & dag_column, const ColumnWithTypeAndName & f throw TiFlashException("Flash column and TiDB column has different not null flag", Errors::Coprocessor::Internal); } if (type->isNullable()) - type = dynamic_cast(type)->getNestedType().get(); + type = static_cast(type)->getNestedType().get(); switch (tidb_column_info.tp) { @@ -457,7 +456,7 @@ const char * arrowEnumColToFlashCol( { if (checkNull(i, null_count, null_bitmap, col)) continue; - const auto enum_value = (Int64)toLittleEndian(*(reinterpret_cast(pos + offsets[i]))); + const auto enum_value = static_cast(toLittleEndian(*(reinterpret_cast(pos + offsets[i])))); col.column->assumeMutable()->insert(Field(enum_value)); } return pos + offsets[length]; @@ -479,11 +478,11 @@ const char * arrowBitColToFlashCol( continue; const String value = String(pos + offsets[i], pos + offsets[i + 1]); if (value.length() == 0) - col.column->assumeMutable()->insert(Field(UInt64(0))); + col.column->assumeMutable()->insert(Field(static_cast(0))); UInt64 result = 0; - for (auto & c : value) + for (const auto & c : value) { - result = (result << 8u) | (UInt8)c; + result = (result << 8u) | static_cast(c); } col.column->assumeMutable()->insert(Field(result)); } @@ -500,7 +499,7 @@ T toCHDecimal(UInt8 digits_int, UInt8 digits_frac, bool negative, const Int32 * UInt8 tailing_digit = digits_frac % DIGITS_PER_WORD; typename T::NativeType value = 0; - const int word_max = int(1e9); + const int word_max = static_cast(1e9); for (int i = 0; i < word_int; i++) { value = value * word_max + word_buf[i]; @@ -552,28 +551,28 @@ const char * arrowDecimalColToFlashCol( pos += 1; Int32 word_buf[MAX_WORD_BUF_LEN]; const DataTypePtr decimal_type - = col.type->isNullable() ? dynamic_cast(col.type.get())->getNestedType() : col.type; - for (int j = 0; j < MAX_WORD_BUF_LEN; j++) + = col.type->isNullable() ? static_cast(col.type.get())->getNestedType() : col.type; + for (int & j : word_buf) { - word_buf[j] = toLittleEndian(*(reinterpret_cast(pos))); + j = toLittleEndian(*(reinterpret_cast(pos))); pos += 4; } - if (auto * type32 = checkDecimal(*decimal_type)) + if (const auto * type32 = checkDecimal(*decimal_type)) { auto res = toCHDecimal(digits_int, digits_frac, negative, word_buf); col.column->assumeMutable()->insert(DecimalField(res, type32->getScale())); } - else if (auto * type64 = checkDecimal(*decimal_type)) + else if (const auto * type64 = checkDecimal(*decimal_type)) { auto res = toCHDecimal(digits_int, digits_frac, negative, word_buf); col.column->assumeMutable()->insert(DecimalField(res, type64->getScale())); } - else if (auto * type128 = checkDecimal(*decimal_type)) + else if (const auto * type128 = checkDecimal(*decimal_type)) { auto res = toCHDecimal(digits_int, digits_frac, negative, word_buf); col.column->assumeMutable()->insert(DecimalField(res, type128->getScale())); } - else if (auto * type256 = checkDecimal(*decimal_type)) + else if (const auto * type256 = checkDecimal(*decimal_type)) { auto res = toCHDecimal(digits_int, digits_frac, negative, word_buf); col.column->assumeMutable()->insert(DecimalField(res, type256->getScale())); @@ -600,13 +599,13 @@ const char * arrowDateColToFlashCol( continue; } UInt64 chunk_time = toLittleEndian(*(reinterpret_cast(pos))); - UInt16 year = (UInt16)((chunk_time & MyTimeBase::YEAR_BIT_FIELD_MASK) >> MyTimeBase::YEAR_BIT_FIELD_OFFSET); - UInt8 month = (UInt8)((chunk_time & MyTimeBase::MONTH_BIT_FIELD_MASK) >> MyTimeBase::MONTH_BIT_FIELD_OFFSET); - UInt8 day = (UInt8)((chunk_time & MyTimeBase::DAY_BIT_FIELD_MASK) >> MyTimeBase::DAY_BIT_FIELD_OFFSET); - UInt16 hour = (UInt16)((chunk_time & MyTimeBase::HOUR_BIT_FIELD_MASK) >> MyTimeBase::HOUR_BIT_FIELD_OFFSET); - UInt8 minute = (UInt8)((chunk_time & MyTimeBase::MINUTE_BIT_FIELD_MASK) >> MyTimeBase::MINUTE_BIT_FIELD_OFFSET); - UInt8 second = (UInt8)((chunk_time & MyTimeBase::SECOND_BIT_FIELD_MASK) >> MyTimeBase::SECOND_BIT_FIELD_OFFSET); - UInt32 micro_second = (UInt32)((chunk_time & MyTimeBase::MICROSECOND_BIT_FIELD_MASK) >> MyTimeBase::MICROSECOND_BIT_FIELD_OFFSET); + auto year = static_cast((chunk_time & MyTimeBase::YEAR_BIT_FIELD_MASK) >> MyTimeBase::YEAR_BIT_FIELD_OFFSET); + auto month = static_cast((chunk_time & MyTimeBase::MONTH_BIT_FIELD_MASK) >> MyTimeBase::MONTH_BIT_FIELD_OFFSET); + auto day = static_cast((chunk_time & MyTimeBase::DAY_BIT_FIELD_MASK) >> MyTimeBase::DAY_BIT_FIELD_OFFSET); + auto hour = static_cast((chunk_time & MyTimeBase::HOUR_BIT_FIELD_MASK) >> MyTimeBase::HOUR_BIT_FIELD_OFFSET); + auto minute = static_cast((chunk_time & MyTimeBase::MINUTE_BIT_FIELD_MASK) >> MyTimeBase::MINUTE_BIT_FIELD_OFFSET); + auto second = static_cast((chunk_time & MyTimeBase::SECOND_BIT_FIELD_MASK) >> MyTimeBase::SECOND_BIT_FIELD_OFFSET); + auto micro_second = static_cast((chunk_time & MyTimeBase::MICROSECOND_BIT_FIELD_MASK) >> MyTimeBase::MICROSECOND_BIT_FIELD_OFFSET); MyDateTime mt(year, month, day, hour, minute, second, micro_second); pos += field_length; col.column->assumeMutable()->insert(Field(mt.toPackedUInt())); @@ -659,7 +658,7 @@ const char * arrowNumColToFlashCol( case TiDB::TypeFloat: u32 = toLittleEndian(*(reinterpret_cast(pos))); std::memcpy(&f32, &u32, sizeof(Float32)); - col.column->assumeMutable()->insert(Field((Float64)f32)); + col.column->assumeMutable()->insert(Field(static_cast(f32))); break; case TiDB::TypeDouble: u64 = toLittleEndian(*(reinterpret_cast(pos))); From e14c677700f66ae5df2e43eabd43e00ef95625f0 Mon Sep 17 00:00:00 2001 From: xufei Date: Wed, 22 Jun 2022 15:46:37 +0800 Subject: [PATCH 120/127] Add MPPReceiverSet, which includes ExchangeReceiver and CoprocessorReader (#5175) ref pingcap/tiflash#5095 --- dbms/src/Flash/Coprocessor/DAGContext.cpp | 14 ++++-- dbms/src/Flash/Coprocessor/DAGContext.h | 20 +++++--- .../Coprocessor/DAGQueryBlockInterpreter.cpp | 6 +-- .../Coprocessor/DAGStorageInterpreter.cpp | 1 + dbms/src/Flash/Mpp/MPPReceiverSet.cpp | 48 +++++++++++++++++++ dbms/src/Flash/Mpp/MPPReceiverSet.h | 44 +++++++++++++++++ dbms/src/Flash/Mpp/MPPTask.cpp | 17 +++---- dbms/src/Flash/Mpp/MPPTask.h | 7 +-- 8 files changed, 131 insertions(+), 26 deletions(-) create mode 100644 dbms/src/Flash/Mpp/MPPReceiverSet.cpp create mode 100644 dbms/src/Flash/Mpp/MPPReceiverSet.h diff --git a/dbms/src/Flash/Coprocessor/DAGContext.cpp b/dbms/src/Flash/Coprocessor/DAGContext.cpp index 1ef7338a589..ec0544c6ee4 100644 --- a/dbms/src/Flash/Coprocessor/DAGContext.cpp +++ b/dbms/src/Flash/Coprocessor/DAGContext.cpp @@ -206,12 +206,20 @@ void DAGContext::attachBlockIO(const BlockIO & io_) io = io_; } -const std::unordered_map> & DAGContext::getMPPExchangeReceiverMap() const +ExchangeReceiverPtr DAGContext::getMPPExchangeReceiver(const String & executor_id) const { if (!isMPPTask()) throw TiFlashException("mpp_exchange_receiver_map is used in mpp only", Errors::Coprocessor::Internal); - RUNTIME_ASSERT(mpp_exchange_receiver_map != nullptr, log, "MPPTask without exchange receiver map"); - return *mpp_exchange_receiver_map; + RUNTIME_ASSERT(mpp_receiver_set != nullptr, log, "MPPTask without receiver set"); + return mpp_receiver_set->getExchangeReceiver(executor_id); +} + +void DAGContext::addCoprocessorReader(const CoprocessorReaderPtr & coprocessor_reader) +{ + if (!isMPPTask()) + return; + RUNTIME_ASSERT(mpp_receiver_set != nullptr, log, "MPPTask without receiver set"); + return mpp_receiver_set->addCoprocessorReader(coprocessor_reader); } bool DAGContext::containsRegionsInfoForTable(Int64 table_id) const diff --git a/dbms/src/Flash/Coprocessor/DAGContext.h b/dbms/src/Flash/Coprocessor/DAGContext.h index 07b65b2d8fe..8b94d4637a8 100644 --- a/dbms/src/Flash/Coprocessor/DAGContext.h +++ b/dbms/src/Flash/Coprocessor/DAGContext.h @@ -37,8 +37,13 @@ namespace DB class Context; class MPPTunnelSet; class ExchangeReceiver; -using ExchangeReceiverMap = std::unordered_map>; -using ExchangeReceiverMapPtr = std::shared_ptr>>; +using ExchangeReceiverPtr = std::shared_ptr; +/// key: executor_id of ExchangeReceiver nodes in dag. +using ExchangeReceiverMap = std::unordered_map; +class MPPReceiverSet; +using MPPReceiverSetPtr = std::shared_ptr; +class CoprocessorReader; +using CoprocessorReaderPtr = std::shared_ptr; class Join; using JoinPtr = std::shared_ptr; @@ -304,11 +309,12 @@ class DAGContext bool columnsForTestEmpty() { return columns_for_test_map.empty(); } - const std::unordered_map> & getMPPExchangeReceiverMap() const; - void setMPPExchangeReceiverMap(ExchangeReceiverMapPtr & exchange_receiver_map) + ExchangeReceiverPtr getMPPExchangeReceiver(const String & executor_id) const; + void setMPPReceiverSet(const MPPReceiverSetPtr & receiver_set) { - mpp_exchange_receiver_map = exchange_receiver_map; + mpp_receiver_set = receiver_set; } + void addCoprocessorReader(const CoprocessorReaderPtr & coprocessor_reader); void addSubquery(const String & subquery_id, SubqueryForSet && subquery); bool hasSubquery() const { return !subqueries.empty(); } @@ -369,8 +375,8 @@ class DAGContext ConcurrentBoundedQueue warnings; /// warning_count is the actual warning count during the entire execution std::atomic warning_count; - /// key: executor_id of ExchangeReceiver nodes in dag. - ExchangeReceiverMapPtr mpp_exchange_receiver_map; + + MPPReceiverSetPtr mpp_receiver_set; /// vector of SubqueriesForSets(such as join build subquery). /// The order of the vector is also the order of the subquery. std::vector subqueries; diff --git a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp index 86d6428c92a..e322a830744 100644 --- a/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp +++ b/dbms/src/Flash/Coprocessor/DAGQueryBlockInterpreter.cpp @@ -481,14 +481,14 @@ void DAGQueryBlockInterpreter::recordProfileStreams(DAGPipeline & pipeline, cons void DAGQueryBlockInterpreter::handleExchangeReceiver(DAGPipeline & pipeline) { - auto it = dagContext().getMPPExchangeReceiverMap().find(query_block.source_name); - if (unlikely(it == dagContext().getMPPExchangeReceiverMap().end())) + auto exchange_receiver = dagContext().getMPPExchangeReceiver(query_block.source_name); + if (unlikely(exchange_receiver == nullptr)) throw Exception("Can not find exchange receiver for " + query_block.source_name, ErrorCodes::LOGICAL_ERROR); // todo choose a more reasonable stream number auto & exchange_receiver_io_input_streams = dagContext().getInBoundIOInputStreamsMap()[query_block.source_name]; for (size_t i = 0; i < max_streams; ++i) { - BlockInputStreamPtr stream = std::make_shared(it->second, log->identifier(), query_block.source_name); + BlockInputStreamPtr stream = std::make_shared(exchange_receiver, log->identifier(), query_block.source_name); exchange_receiver_io_input_streams.push_back(stream); stream = std::make_shared(stream, 8192, 0, log->identifier()); stream->setExtraInfo("squashing after exchange receiver"); diff --git a/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp b/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp index 14cddd94730..ad2de7217e0 100644 --- a/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp +++ b/dbms/src/Flash/Coprocessor/DAGStorageInterpreter.cpp @@ -486,6 +486,7 @@ void DAGStorageInterpreter::buildRemoteStreams(std::vector && rem std::vector tasks(all_tasks.begin() + task_start, all_tasks.begin() + task_end); auto coprocessor_reader = std::make_shared(schema, cluster, tasks, has_enforce_encode_type, 1); + context.getDAGContext()->addCoprocessorReader(coprocessor_reader); BlockInputStreamPtr input = std::make_shared(coprocessor_reader, log->identifier(), table_scan.getTableScanExecutorID()); pipeline.streams.push_back(input); task_start = task_end; diff --git a/dbms/src/Flash/Mpp/MPPReceiverSet.cpp b/dbms/src/Flash/Mpp/MPPReceiverSet.cpp new file mode 100644 index 00000000000..60cca308c18 --- /dev/null +++ b/dbms/src/Flash/Mpp/MPPReceiverSet.cpp @@ -0,0 +1,48 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include + +namespace DB +{ +void MPPReceiverSet::addExchangeReceiver(const String & executor_id, const ExchangeReceiverPtr & exchange_receiver) +{ + RUNTIME_ASSERT(exchange_receiver_map.find(executor_id) == exchange_receiver_map.end(), log, "Duplicate executor_id: {} in DAGRequest", executor_id); + exchange_receiver_map[executor_id] = exchange_receiver; +} + +void MPPReceiverSet::addCoprocessorReader(const CoprocessorReaderPtr & coprocessor_reader) +{ + coprocessor_readers.push_back(coprocessor_reader); +} + +ExchangeReceiverPtr MPPReceiverSet::getExchangeReceiver(const String & executor_id) const +{ + auto it = exchange_receiver_map.find(executor_id); + if (unlikely(it == exchange_receiver_map.end())) + return nullptr; + return it->second; +} + +void MPPReceiverSet::cancel() +{ + for (auto & it : exchange_receiver_map) + { + it.second->cancel(); + } + for (auto & cop_reader : coprocessor_readers) + cop_reader->cancel(); +} +} // namespace DB diff --git a/dbms/src/Flash/Mpp/MPPReceiverSet.h b/dbms/src/Flash/Mpp/MPPReceiverSet.h new file mode 100644 index 00000000000..44274cb3ce8 --- /dev/null +++ b/dbms/src/Flash/Mpp/MPPReceiverSet.h @@ -0,0 +1,44 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#pragma once + +#include +#include + +namespace DB +{ +class MPPReceiverSet +{ +public: + explicit MPPReceiverSet(const String & req_id) + : log(Logger::get("MPPReceiverSet", req_id)) + {} + void addExchangeReceiver(const String & executor_id, const ExchangeReceiverPtr & exchange_receiver); + void addCoprocessorReader(const CoprocessorReaderPtr & coprocessor_reader); + ExchangeReceiverPtr getExchangeReceiver(const String & executor_id) const; + void cancel(); + +private: + /// two kinds of receiver in MPP + /// ExchangeReceiver: receiver data from other MPPTask + /// CoprocessorReader: used in remote read + ExchangeReceiverMap exchange_receiver_map; + std::vector coprocessor_readers; + const LoggerPtr log; +}; + +using MPPReceiverSetPtr = std::shared_ptr; + +} // namespace DB diff --git a/dbms/src/Flash/Mpp/MPPTask.cpp b/dbms/src/Flash/Mpp/MPPTask.cpp index 40f03ff79ba..0381bbdfa04 100644 --- a/dbms/src/Flash/Mpp/MPPTask.cpp +++ b/dbms/src/Flash/Mpp/MPPTask.cpp @@ -125,7 +125,7 @@ void MPPTask::registerTunnels(const mpp::DispatchTaskRequest & task_request) void MPPTask::initExchangeReceivers() { - mpp_exchange_receiver_map = std::make_shared(); + receiver_set = std::make_shared(log->identifier()); traverseExecutors(&dag_req, [&](const tipb::Executor & executor) { if (executor.tp() == tipb::ExecType::TypeExchangeReceiver) { @@ -147,22 +147,19 @@ void MPPTask::initExchangeReceivers() if (status != RUNNING) throw Exception("exchange receiver map can not be initialized, because the task is not in running state"); - (*mpp_exchange_receiver_map)[executor_id] = exchange_receiver; + receiver_set->addExchangeReceiver(executor_id, exchange_receiver); new_thread_count_of_exchange_receiver += exchange_receiver->computeNewThreadCount(); } return true; }); - dag_context->setMPPExchangeReceiverMap(mpp_exchange_receiver_map); + dag_context->setMPPReceiverSet(receiver_set); } -void MPPTask::cancelAllExchangeReceivers() +void MPPTask::cancelAllReceivers() { - if (likely(mpp_exchange_receiver_map != nullptr)) + if (likely(receiver_set != nullptr)) { - for (auto & it : *mpp_exchange_receiver_map) - { - it.second->cancel(); - } + receiver_set->cancel(); } } @@ -393,7 +390,7 @@ void MPPTask::runImpl() else { context->getProcessList().sendCancelToQuery(context->getCurrentQueryId(), context->getClientInfo().current_user, true); - cancelAllExchangeReceivers(); + cancelAllReceivers(); writeErrToAllTunnels(err_msg); } LOG_FMT_INFO(log, "task ends, time cost is {} ms.", stopwatch.elapsedMilliseconds()); diff --git a/dbms/src/Flash/Mpp/MPPTask.h b/dbms/src/Flash/Mpp/MPPTask.h index c8423ac484c..d7e5ed169de 100644 --- a/dbms/src/Flash/Mpp/MPPTask.h +++ b/dbms/src/Flash/Mpp/MPPTask.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -109,7 +110,7 @@ class MPPTask : public std::enable_shared_from_this void initExchangeReceivers(); - void cancelAllExchangeReceivers(); + void cancelAllReceivers(); tipb::DAGRequest dag_req; @@ -126,8 +127,8 @@ class MPPTask : public std::enable_shared_from_this MPPTaskId id; MPPTunnelSetPtr tunnel_set; - /// key: executor_id of ExchangeReceiver nodes in dag. - ExchangeReceiverMapPtr mpp_exchange_receiver_map; + + MPPReceiverSetPtr receiver_set; int new_thread_count_of_exchange_receiver = 0; From 18325f9eb4dec584f3551e0ef7fd4593590a2a77 Mon Sep 17 00:00:00 2001 From: hongyunyan <649330952@qq.com> Date: Wed, 22 Jun 2022 17:14:37 +0800 Subject: [PATCH 121/127] DDL: Use Column Name Instead of Offset to Find the common handle cluster index (#5166) close pingcap/tiflash#5154 --- dbms/src/Core/Block.cpp | 14 +- dbms/src/Debug/MockTiDB.cpp | 5 +- dbms/src/Debug/dbgFuncMockRaftCommand.cpp | 42 +++-- dbms/src/Debug/dbgFuncMockRaftSnapshot.cpp | 24 ++- dbms/src/Debug/dbgFuncRegion.cpp | 8 +- dbms/src/Debug/dbgTools.cpp | 15 +- .../DecodingStorageSchemaSnapshot.h | 14 +- .../Transaction/RegionBlockReader.cpp | 2 + dbms/src/Storages/Transaction/TiDB.cpp | 4 +- dbms/src/Storages/Transaction/TiDB.h | 21 ++- .../Storages/Transaction/TiKVRecordFormat.h | 9 +- .../Transaction/tests/RowCodecTestUtils.h | 8 +- .../tests/bench_region_block_reader.cpp | 171 ++++++++++++++++++ ...gtest_decoding_storage_schema_snapshot.cpp | 65 +++++++ .../tests/gtest_region_block_reader.cpp | 68 +++---- .../clustered_index/ddl.test | 86 +++++++++ 16 files changed, 477 insertions(+), 79 deletions(-) create mode 100644 dbms/src/Storages/Transaction/tests/bench_region_block_reader.cpp create mode 100644 dbms/src/Storages/Transaction/tests/gtest_decoding_storage_schema_snapshot.cpp diff --git a/dbms/src/Core/Block.cpp b/dbms/src/Core/Block.cpp index 28db7af82e1..971e8f36e2a 100644 --- a/dbms/src/Core/Block.cpp +++ b/dbms/src/Core/Block.cpp @@ -238,10 +238,18 @@ void Block::checkNumberOfRows() const if (rows == -1) rows = size; else if (rows != size) - throw Exception("Sizes of columns doesn't match: " - + data.front().name + ": " + toString(rows) - + ", " + elem.name + ": " + toString(size), + { + auto first_col = data.front(); + throw Exception(fmt::format( + "Sizes of columns doesn't match: {}(id={}): {}, {}(id={}): {}", + first_col.name, + first_col.column_id, + rows, + elem.name, + elem.column_id, + size), ErrorCodes::SIZES_OF_COLUMNS_DOESNT_MATCH); + } } } diff --git a/dbms/src/Debug/MockTiDB.cpp b/dbms/src/Debug/MockTiDB.cpp index 42ab56a97c1..7b3bdb0948f 100644 --- a/dbms/src/Debug/MockTiDB.cpp +++ b/dbms/src/Debug/MockTiDB.cpp @@ -221,7 +221,6 @@ TiDB::TableInfoPtr MockTiDB::parseColumns( { String & name = string_tokens[index]; index_info.idx_cols[index].name = name; - index_info.idx_cols[index].offset = pk_column_pos_map[name]; index_info.idx_cols[index].length = -1; } } @@ -302,7 +301,7 @@ int MockTiDB::newTables( tables_by_id.emplace(table->table_info.id, table); tables_by_name.emplace(qualified_name, table); - AffectedOption opt; + AffectedOption opt{}; opt.schema_id = table->database_id; opt.table_id = table->id(); opt.old_schema_id = table->database_id; @@ -571,7 +570,7 @@ void MockTiDB::renameTables(const std::vectordatabase_id; opt.table_id = new_table->id(); opt.old_schema_id = table->database_id; diff --git a/dbms/src/Debug/dbgFuncMockRaftCommand.cpp b/dbms/src/Debug/dbgFuncMockRaftCommand.cpp index df93ee1c78d..3626041f428 100644 --- a/dbms/src/Debug/dbgFuncMockRaftCommand.cpp +++ b/dbms/src/Debug/dbgFuncMockRaftCommand.cpp @@ -40,7 +40,7 @@ void MockRaftCommand::dbgFuncRegionBatchSplit(Context & context, const ASTs & ar auto & tmt = context.getTMTContext(); auto & kvstore = tmt.getKVStore(); - RegionID region_id = (RegionID)safeGet(typeid_cast(*args[0]).value); + auto region_id = static_cast(safeGet(typeid_cast(*args[0]).value)); const String & database_name = typeid_cast(*args[1]).name; const String & table_name = typeid_cast(*args[2]).name; auto table = MockTiDB::instance().getTableByName(database_name, table_name); @@ -49,7 +49,7 @@ void MockRaftCommand::dbgFuncRegionBatchSplit(Context & context, const ASTs & ar if (4 + handle_column_size * 4 != args.size()) throw Exception("Args not matched, should be: region-id1, database-name, table-name, start1, end1, start2, end2, region-id2", ErrorCodes::BAD_ARGUMENTS); - RegionID region_id2 = (RegionID)safeGet(typeid_cast(*args[args.size() - 1]).value); + auto region_id2 = static_cast(safeGet(typeid_cast(*args[args.size() - 1]).value)); auto table_id = table->id(); TiKVKey start_key1, start_key2, end_key1, end_key2; @@ -59,9 +59,17 @@ void MockRaftCommand::dbgFuncRegionBatchSplit(Context & context, const ASTs & ar std::vector start_keys2; std::vector end_keys1; std::vector end_keys2; + + std::unordered_map column_name_columns_index_map; + for (size_t i = 0; i < table_info.columns.size(); i++) + { + column_name_columns_index_map.emplace(table_info.columns[i].name, i); + } + for (size_t i = 0; i < handle_column_size; i++) { - auto & column_info = table_info.columns[table_info.getPrimaryIndexInfo().idx_cols[i].offset]; + auto idx = column_name_columns_index_map[table_info.getPrimaryIndexInfo().idx_cols[i].name]; + auto & column_info = table_info.columns[idx]; auto start_field1 = RegionBench::convertField(column_info, typeid_cast(*args[3 + i]).value); TiDB::DatumBumpy start_datum1 = TiDB::DatumBumpy(start_field1, column_info.tp); @@ -88,10 +96,10 @@ void MockRaftCommand::dbgFuncRegionBatchSplit(Context & context, const ASTs & ar } else { - HandleID start1 = (HandleID)safeGet(typeid_cast(*args[3]).value); - HandleID end1 = (HandleID)safeGet(typeid_cast(*args[4]).value); - HandleID start2 = (HandleID)safeGet(typeid_cast(*args[5]).value); - HandleID end2 = (HandleID)safeGet(typeid_cast(*args[6]).value); + auto start1 = static_cast(safeGet(typeid_cast(*args[3]).value)); + auto end1 = static_cast(safeGet(typeid_cast(*args[4]).value)); + auto start2 = static_cast(safeGet(typeid_cast(*args[5]).value)); + auto end2 = static_cast(safeGet(typeid_cast(*args[6]).value)); start_key1 = RecordKVFormat::genKey(table_id, start1); start_key2 = RecordKVFormat::genKey(table_id, start2); end_key1 = RecordKVFormat::genKey(table_id, end1); @@ -110,7 +118,7 @@ void MockRaftCommand::dbgFuncRegionBatchSplit(Context & context, const ASTs & ar request.set_cmd_type(raft_cmdpb::AdminCmdType::BatchSplit); raft_cmdpb::BatchSplitResponse * splits = response.mutable_splits(); { - auto region = splits->add_regions(); + auto * region = splits->add_regions(); region->set_id(region_id); region->set_start_key(start_key1); region->set_end_key(end_key1); @@ -118,7 +126,7 @@ void MockRaftCommand::dbgFuncRegionBatchSplit(Context & context, const ASTs & ar *region->mutable_region_epoch() = new_epoch; } { - auto region = splits->add_regions(); + auto * region = splits->add_regions(); region->set_id(region_id2); region->set_start_key(start_key2); region->set_end_key(end_key2); @@ -144,8 +152,8 @@ void MockRaftCommand::dbgFuncPrepareMerge(Context & context, const ASTs & args, throw Exception("Args not matched, should be: source-id1, target-id2", ErrorCodes::BAD_ARGUMENTS); } - RegionID region_id = (RegionID)safeGet(typeid_cast(*args[0]).value); - RegionID target_id = (RegionID)safeGet(typeid_cast(*args[1]).value); + auto region_id = static_cast(safeGet(typeid_cast(*args[0]).value)); + auto target_id = static_cast(safeGet(typeid_cast(*args[1]).value)); auto & tmt = context.getTMTContext(); auto & kvstore = tmt.getKVStore(); @@ -157,7 +165,7 @@ void MockRaftCommand::dbgFuncPrepareMerge(Context & context, const ASTs & args, { request.set_cmd_type(raft_cmdpb::AdminCmdType::PrepareMerge); - auto prepare_merge = request.mutable_prepare_merge(); + auto * prepare_merge = request.mutable_prepare_merge(); { auto min_index = region->appliedIndex(); prepare_merge->set_min_index(min_index); @@ -184,8 +192,8 @@ void MockRaftCommand::dbgFuncCommitMerge(Context & context, const ASTs & args, D throw Exception("Args not matched, should be: source-id1, current-id2", ErrorCodes::BAD_ARGUMENTS); } - RegionID source_id = (RegionID)safeGet(typeid_cast(*args[0]).value); - RegionID current_id = (RegionID)safeGet(typeid_cast(*args[1]).value); + auto source_id = static_cast(safeGet(typeid_cast(*args[0]).value)); + auto current_id = static_cast(safeGet(typeid_cast(*args[1]).value)); auto & tmt = context.getTMTContext(); auto & kvstore = tmt.getKVStore(); @@ -196,7 +204,7 @@ void MockRaftCommand::dbgFuncCommitMerge(Context & context, const ASTs & args, D { request.set_cmd_type(raft_cmdpb::AdminCmdType::CommitMerge); - auto commit_merge = request.mutable_commit_merge(); + auto * commit_merge = request.mutable_commit_merge(); { commit_merge->set_commit(source_region->appliedIndex()); *commit_merge->mutable_source() = source_region->getMetaRegion(); @@ -220,7 +228,7 @@ void MockRaftCommand::dbgFuncRollbackMerge(Context & context, const ASTs & args, throw Exception("Args not matched, should be: region-id", ErrorCodes::BAD_ARGUMENTS); } - RegionID region_id = (RegionID)safeGet(typeid_cast(*args[0]).value); + auto region_id = static_cast(safeGet(typeid_cast(*args[0]).value)); auto & tmt = context.getTMTContext(); auto & kvstore = tmt.getKVStore(); @@ -231,7 +239,7 @@ void MockRaftCommand::dbgFuncRollbackMerge(Context & context, const ASTs & args, { request.set_cmd_type(raft_cmdpb::AdminCmdType::RollbackMerge); - auto rollback_merge = request.mutable_rollback_merge(); + auto * rollback_merge = request.mutable_rollback_merge(); { auto merge_state = region->getMergeState(); rollback_merge->set_commit(merge_state.commit()); diff --git a/dbms/src/Debug/dbgFuncMockRaftSnapshot.cpp b/dbms/src/Debug/dbgFuncMockRaftSnapshot.cpp index 9d5b848ddea..b5d3f252d0a 100644 --- a/dbms/src/Debug/dbgFuncMockRaftSnapshot.cpp +++ b/dbms/src/Debug/dbgFuncMockRaftSnapshot.cpp @@ -68,6 +68,12 @@ RegionPtr GenDbgRegionSnapshotWithData(Context & context, const ASTs & args) size_t handle_column_size = is_common_handle ? table_info.getPrimaryIndexInfo().idx_cols.size() : 1; RegionPtr region; + std::unordered_map column_name_columns_index_map; + for (size_t i = 0; i < table_info.columns.size(); i++) + { + column_name_columns_index_map.emplace(table_info.columns[i].name, i); + } + if (!is_common_handle) { auto start = static_cast(safeGet(typeid_cast(*args[3]).value)); @@ -81,7 +87,8 @@ RegionPtr GenDbgRegionSnapshotWithData(Context & context, const ASTs & args) std::vector end_keys; for (size_t i = 0; i < handle_column_size; i++) { - auto & column_info = table_info.columns[table_info.getPrimaryIndexInfo().idx_cols[i].offset]; + auto idx = column_name_columns_index_map[table_info.getPrimaryIndexInfo().idx_cols[i].name]; + auto & column_info = table_info.columns[idx]; auto start_field = RegionBench::convertField(column_info, typeid_cast(*args[3 + i]).value); TiDB::DatumBumpy start_datum = TiDB::DatumBumpy(start_field, column_info.tp); start_keys.emplace_back(start_datum.field()); @@ -122,9 +129,9 @@ RegionPtr GenDbgRegionSnapshotWithData(Context & context, const ASTs & args) std::vector keys; // handle key for (size_t i = 0; i < table_info.getPrimaryIndexInfo().idx_cols.size(); i++) { - auto & idx_col = table_info.getPrimaryIndexInfo().idx_cols[i]; - auto & column_info = table_info.columns[idx_col.offset]; - auto start_field = RegionBench::convertField(column_info, fields[idx_col.offset]); + auto idx = column_name_columns_index_map[table_info.getPrimaryIndexInfo().idx_cols[i].name]; + auto & column_info = table_info.columns[idx]; + auto start_field = RegionBench::convertField(column_info, fields[idx]); TiDB::DatumBumpy start_datum = TiDB::DatumBumpy(start_field, column_info.tp); keys.emplace_back(start_datum.field()); } @@ -198,9 +205,16 @@ void MockRaftCommand::dbgFuncRegionSnapshot(Context & context, const ASTs & args // Get start key and end key form multiple column if it is clustered_index. std::vector start_keys; std::vector end_keys; + + std::unordered_map column_name_columns_index_map; + for (size_t i = 0; i < table_info.columns.size(); i++) + { + column_name_columns_index_map.emplace(table_info.columns[i].name, i); + } for (size_t i = 0; i < handle_column_size; i++) { - const auto & column_info = table_info.columns[table_info.getPrimaryIndexInfo().idx_cols[i].offset]; + auto idx = column_name_columns_index_map[table_info.getPrimaryIndexInfo().idx_cols[i].name]; + const auto & column_info = table_info.columns[idx]; auto start_field = RegionBench::convertField(column_info, typeid_cast(*args[1 + i]).value); TiDB::DatumBumpy start_datum = TiDB::DatumBumpy(start_field, column_info.tp); start_keys.emplace_back(start_datum.field()); diff --git a/dbms/src/Debug/dbgFuncRegion.cpp b/dbms/src/Debug/dbgFuncRegion.cpp index b2024eac1d8..f65a18b8fd0 100644 --- a/dbms/src/Debug/dbgFuncRegion.cpp +++ b/dbms/src/Debug/dbgFuncRegion.cpp @@ -61,9 +61,15 @@ void dbgFuncPutRegion(Context & context, const ASTs & args, DBGInvoker::Printer { std::vector start_keys; std::vector end_keys; + std::unordered_map column_name_columns_index_map; + for (size_t i = 0; i < table_info.columns.size(); i++) + { + column_name_columns_index_map.emplace(table_info.columns[i].name, i); + } for (size_t i = 0; i < handle_column_size; i++) { - const auto & column_info = table_info.columns[table_info.getPrimaryIndexInfo().idx_cols[i].offset]; + auto idx = column_name_columns_index_map[table_info.getPrimaryIndexInfo().idx_cols[i].name]; + const auto & column_info = table_info.columns[idx]; auto start_field = RegionBench::convertField(column_info, typeid_cast(*args[1 + i]).value); TiDB::DatumBumpy start_datum = TiDB::DatumBumpy(start_field, column_info.tp); start_keys.emplace_back(start_datum.field()); diff --git a/dbms/src/Debug/dbgTools.cpp b/dbms/src/Debug/dbgTools.cpp index 685b2563a3b..854d8a18bd5 100644 --- a/dbms/src/Debug/dbgTools.cpp +++ b/dbms/src/Debug/dbgTools.cpp @@ -310,7 +310,7 @@ void insert( // // Parse the fields in the inserted row std::vector fields; { - for (ASTs::const_iterator it = values_begin; it != values_end; ++it) + for (auto it = values_begin; it != values_end; ++it) { auto field = typeid_cast((*it).get())->value; fields.emplace_back(field); @@ -330,11 +330,18 @@ void insert( // if (table_info.is_common_handle) { std::vector keys; + + std::unordered_map column_name_columns_index_map; + for (size_t i = 0; i < table_info.columns.size(); i++) + { + column_name_columns_index_map.emplace(table_info.columns[i].name, i); + } + for (size_t i = 0; i < table_info.getPrimaryIndexInfo().idx_cols.size(); i++) { - const auto & idx_col = table_info.getPrimaryIndexInfo().idx_cols[i]; - const auto & column_info = table_info.columns[idx_col.offset]; - auto start_field = RegionBench::convertField(column_info, fields[idx_col.offset]); + const auto & col_idx = column_name_columns_index_map[table_info.getPrimaryIndexInfo().idx_cols[i].name]; + const auto & column_info = table_info.columns[col_idx]; + auto start_field = RegionBench::convertField(column_info, fields[col_idx]); TiDB::DatumBumpy start_datum = TiDB::DatumBumpy(start_field, column_info.tp); keys.emplace_back(start_datum.field()); } diff --git a/dbms/src/Storages/Transaction/DecodingStorageSchemaSnapshot.h b/dbms/src/Storages/Transaction/DecodingStorageSchemaSnapshot.h index e8e0610326c..b0cacefe6f4 100644 --- a/dbms/src/Storages/Transaction/DecodingStorageSchemaSnapshot.h +++ b/dbms/src/Storages/Transaction/DecodingStorageSchemaSnapshot.h @@ -77,10 +77,12 @@ struct DecodingStorageSchemaSnapshot , decoding_schema_version{decoding_schema_version_} { std::unordered_map column_lut; + std::unordered_map column_name_id_map; for (size_t i = 0; i < table_info_.columns.size(); i++) { const auto & ci = table_info_.columns[i]; column_lut.emplace(ci.id, i); + column_name_id_map.emplace(ci.name, ci.id); } for (size_t i = 0; i < column_defines->size(); i++) { @@ -88,7 +90,7 @@ struct DecodingStorageSchemaSnapshot sorted_column_id_with_pos.insert({cd.id, i}); if (cd.id != TiDBPkColumnID && cd.id != VersionColumnID && cd.id != DelMarkColumnID) { - auto & columns = table_info_.columns; + const auto & columns = table_info_.columns; column_infos.push_back(columns[column_lut.at(cd.id)]); } else @@ -100,10 +102,14 @@ struct DecodingStorageSchemaSnapshot // create pk related metadata if needed if (is_common_handle) { - const auto & primary_index_info = table_info_.getPrimaryIndexInfo(); - for (size_t i = 0; i < primary_index_info.idx_cols.size(); i++) + /// we will not update the IndexInfo except Rename DDL. + /// When the add column / drop column action happenes, the offset of each column may change + /// Thus, we should not use offset to get the column we want, + /// but use to compare the column name to get the column id. + const auto & primary_index_cols = table_info_.getPrimaryIndexInfo().idx_cols; + for (const auto & col : primary_index_cols) { - auto pk_column_id = table_info_.columns[primary_index_info.idx_cols[i].offset].id; + auto pk_column_id = column_name_id_map[col.name]; pk_column_ids.emplace_back(pk_column_id); pk_pos_map.emplace(pk_column_id, reinterpret_cast(std::numeric_limits::max())); } diff --git a/dbms/src/Storages/Transaction/RegionBlockReader.cpp b/dbms/src/Storages/Transaction/RegionBlockReader.cpp index a9384e4a14d..2ec690c467b 100644 --- a/dbms/src/Storages/Transaction/RegionBlockReader.cpp +++ b/dbms/src/Storages/Transaction/RegionBlockReader.cpp @@ -208,6 +208,8 @@ bool RegionBlockReader::readImpl(Block & block, const RegionDataReadInfoList & d } index++; } + block.checkNumberOfRows(); + return true; } diff --git a/dbms/src/Storages/Transaction/TiDB.cpp b/dbms/src/Storages/Transaction/TiDB.cpp index 15bf2a3fb58..dc7f1f3e348 100644 --- a/dbms/src/Storages/Transaction/TiDB.cpp +++ b/dbms/src/Storages/Transaction/TiDB.cpp @@ -631,8 +631,8 @@ catch (const Poco::Exception & e) /////////////////////// IndexColumnInfo::IndexColumnInfo(Poco::JSON::Object::Ptr json) - : offset(0) - , length(0) + : length(0) + , offset(0) { deserialize(json); } diff --git a/dbms/src/Storages/Transaction/TiDB.h b/dbms/src/Storages/Transaction/TiDB.h index f67bfb332c7..4c28a614857 100644 --- a/dbms/src/Storages/Transaction/TiDB.h +++ b/dbms/src/Storages/Transaction/TiDB.h @@ -179,7 +179,6 @@ struct ColumnInfo ColumnID id = -1; String name; - Int32 offset = -1; Poco::Dynamic::Var origin_default_value; Poco::Dynamic::Var default_value; Poco::Dynamic::Var default_bit_value; @@ -212,6 +211,12 @@ struct ColumnInfo static Int64 getTimeValue(const String &); static Int64 getYearValue(const String &); static UInt64 getBitValue(const String &); + +private: + /// please be very careful when you have to use offset, + /// because we never update offset when DDL action changes. + /// Thus, our offset will not exactly correspond the order of columns. + Int32 offset = -1; }; enum PartitionType @@ -298,8 +303,13 @@ struct IndexColumnInfo void deserialize(Poco::JSON::Object::Ptr json); String name; - Int32 offset; Int32 length; + +private: + /// please be very careful when you have to use offset, + /// because we never update offset when DDL action changes. + /// Thus, our offset will not exactly correspond the order of columns. + Int32 offset; }; struct IndexInfo { @@ -385,7 +395,12 @@ struct TableInfo bool isLogicalPartitionTable() const { return is_partition_table && belonging_table_id == DB::InvalidTableID && partition.enable; } - /// should not be called if is_common_handle = false + /// should not be called if is_common_handle = false. + /// when use IndexInfo, please avoid to use the offset info + /// the offset value may be wrong in some cases, + /// due to we will not update IndexInfo except RENAME DDL action, + /// but DDL like add column / drop column may change the offset of columns + /// Thus, please be very careful when you must have to use offset information !!!!! const IndexInfo & getPrimaryIndexInfo() const { return index_infos[0]; } IndexInfo & getPrimaryIndexInfo() { return index_infos[0]; } diff --git a/dbms/src/Storages/Transaction/TiKVRecordFormat.h b/dbms/src/Storages/Transaction/TiKVRecordFormat.h index c507616f6e9..10a7f7220e9 100644 --- a/dbms/src/Storages/Transaction/TiKVRecordFormat.h +++ b/dbms/src/Storages/Transaction/TiKVRecordFormat.h @@ -154,9 +154,16 @@ inline TiKVKey genKey(const TiDB::TableInfo & table_info, std::vector key memcpy(key.data() + 1, reinterpret_cast(&big_endian_table_id), 8); memcpy(key.data() + 1 + 8, RecordKVFormat::RECORD_PREFIX_SEP, 2); WriteBufferFromOwnString ss; + + std::unordered_map column_name_columns_index_map; + for (size_t i = 0; i < table_info.columns.size(); i++) + { + column_name_columns_index_map.emplace(table_info.columns[i].name, i); + } for (size_t i = 0; i < keys.size(); i++) { - DB::EncodeDatum(keys[i], table_info.columns[table_info.getPrimaryIndexInfo().idx_cols[i].offset].getCodecFlag(), ss); + auto idx = column_name_columns_index_map[table_info.getPrimaryIndexInfo().idx_cols[i].name]; + DB::EncodeDatum(keys[i], table_info.columns[idx].getCodecFlag(), ss); } return encodeAsTiKVKey(key + ss.releaseStr()); } diff --git a/dbms/src/Storages/Transaction/tests/RowCodecTestUtils.h b/dbms/src/Storages/Transaction/tests/RowCodecTestUtils.h index 20b395a9952..34e0d3d4104 100644 --- a/dbms/src/Storages/Transaction/tests/RowCodecTestUtils.h +++ b/dbms/src/Storages/Transaction/tests/RowCodecTestUtils.h @@ -237,14 +237,14 @@ std::pair> getTableInfoAndFields(ColumnIDs handle_ { table_info.is_common_handle = true; TiDB::IndexInfo index_info; - for (size_t i = 0; i < handle_ids.size(); i++) + for (auto handle_id : handle_ids) { TiDB::IndexColumnInfo index_column_info; - for (size_t pos = 0; pos < table_info.columns.size(); pos++) + for (auto & column : table_info.columns) { - if (table_info.columns[pos].id == handle_ids[i]) + if (column.id == handle_id) { - index_column_info.offset = pos; + index_column_info.name = column.name; break; } } diff --git a/dbms/src/Storages/Transaction/tests/bench_region_block_reader.cpp b/dbms/src/Storages/Transaction/tests/bench_region_block_reader.cpp new file mode 100644 index 00000000000..05ab637de7f --- /dev/null +++ b/dbms/src/Storages/Transaction/tests/bench_region_block_reader.cpp @@ -0,0 +1,171 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include +#include +#include + +#include "RowCodecTestUtils.h" + +using TableInfo = TiDB::TableInfo; +namespace DB::tests +{ +using ColumnIDs = std::vector; +class RegionBlockReaderBenchTest : public benchmark::Fixture +{ +protected: + Int64 handle_value = 100; + UInt8 del_mark_value = 0; + UInt64 version_value = 100; + + RegionDataReadInfoList data_list_read; + std::unordered_map fields_map; + + enum RowEncodeVersion + { + RowV1, + RowV2 + }; + +protected: + void SetUp(const benchmark::State & /*state*/) override + { + data_list_read.clear(); + fields_map.clear(); + } + + void encodeColumns(TableInfo & table_info, std::vector & fields, RowEncodeVersion row_version, size_t num_rows) + { + // for later check + std::unordered_map column_name_columns_index_map; + for (size_t i = 0; i < table_info.columns.size(); i++) + { + fields_map.emplace(table_info.columns[i].id, fields[i]); + column_name_columns_index_map.emplace(table_info.columns[i].name, i); + } + + std::vector value_fields; + std::vector pk_fields; + for (size_t i = 0; i < table_info.columns.size(); i++) + { + if (!table_info.columns[i].hasPriKeyFlag()) + value_fields.emplace_back(fields[i]); + else + pk_fields.emplace_back(fields[i]); + } + + // create PK + WriteBufferFromOwnString pk_buf; + if (table_info.is_common_handle) + { + auto & primary_index_info = table_info.getPrimaryIndexInfo(); + for (size_t i = 0; i < primary_index_info.idx_cols.size(); i++) + { + auto idx = column_name_columns_index_map[primary_index_info.idx_cols[i].name]; + EncodeDatum(pk_fields[i], table_info.columns[idx].getCodecFlag(), pk_buf); + } + } + else + { + DB::EncodeInt64(handle_value, pk_buf); + } + RawTiDBPK pk{std::make_shared(pk_buf.releaseStr())}; + // create value + WriteBufferFromOwnString value_buf; + if (row_version == RowEncodeVersion::RowV1) + { + encodeRowV1(table_info, value_fields, value_buf); + } + else if (row_version == RowEncodeVersion::RowV2) + { + encodeRowV2(table_info, value_fields, value_buf); + } + else + { + throw Exception("Unknown row format " + std::to_string(row_version), ErrorCodes::LOGICAL_ERROR); + } + auto row_value = std::make_shared(std::move(value_buf.str())); + for (size_t i = 0; i < num_rows; i++) + data_list_read.emplace_back(pk, del_mark_value, version_value, row_value); + } + + bool decodeColumns(DecodingStorageSchemaSnapshotConstPtr decoding_schema, bool force_decode) const + { + RegionBlockReader reader{decoding_schema}; + Block block = createBlockSortByColumnID(decoding_schema); + return reader.read(block, data_list_read, force_decode); + } + + std::pair> getNormalTableInfoFields(const ColumnIDs & handle_ids, bool is_common_handle) const + { + return getTableInfoAndFields( + handle_ids, + is_common_handle, + ColumnIDValue(2, handle_value), + ColumnIDValue(3, std::numeric_limits::max()), + ColumnIDValue(4, std::numeric_limits::min()), + ColumnIDValue(9, String("aaa")), + ColumnIDValue(10, DecimalField(ToDecimal(12345678910ULL, 4), 4)), + ColumnIDValueNull(11)); + } +}; + +BENCHMARK_DEFINE_F(RegionBlockReaderBenchTest, CommonHandle) +(benchmark::State & state) +{ + size_t num_rows = state.range(0); + auto [table_info, fields] = getNormalTableInfoFields({2, 3, 4}, true); + encodeColumns(table_info, fields, RowEncodeVersion::RowV2, num_rows); + auto decoding_schema = getDecodingStorageSchemaSnapshot(table_info); + for (auto _ : state) + { + decodeColumns(decoding_schema, true); + } +} + + +BENCHMARK_DEFINE_F(RegionBlockReaderBenchTest, PKIsNotHandle) +(benchmark::State & state) +{ + size_t num_rows = state.range(0); + auto [table_info, fields] = getNormalTableInfoFields({EXTRA_HANDLE_COLUMN_ID}, false); + encodeColumns(table_info, fields, RowEncodeVersion::RowV2, num_rows); + auto decoding_schema = getDecodingStorageSchemaSnapshot(table_info); + for (auto _ : state) + { + decodeColumns(decoding_schema, true); + } +} + +BENCHMARK_DEFINE_F(RegionBlockReaderBenchTest, PKIsHandle) +(benchmark::State & state) +{ + size_t num_rows = state.range(0); + auto [table_info, fields] = getNormalTableInfoFields({2}, false); + encodeColumns(table_info, fields, RowEncodeVersion::RowV2, num_rows); + auto decoding_schema = getDecodingStorageSchemaSnapshot(table_info); + for (auto _ : state) + { + decodeColumns(decoding_schema, true); + } +} + +constexpr size_t num_iterations_test = 1000; + +BENCHMARK_REGISTER_F(RegionBlockReaderBenchTest, PKIsHandle)->Iterations(num_iterations_test)->Arg(1)->Arg(10)->Arg(100); +BENCHMARK_REGISTER_F(RegionBlockReaderBenchTest, CommonHandle)->Iterations(num_iterations_test)->Arg(1)->Arg(10)->Arg(100); +BENCHMARK_REGISTER_F(RegionBlockReaderBenchTest, PKIsNotHandle)->Iterations(num_iterations_test)->Arg(1)->Arg(10)->Arg(100); + +} // namespace DB::tests diff --git a/dbms/src/Storages/Transaction/tests/gtest_decoding_storage_schema_snapshot.cpp b/dbms/src/Storages/Transaction/tests/gtest_decoding_storage_schema_snapshot.cpp new file mode 100644 index 00000000000..1de9809ecad --- /dev/null +++ b/dbms/src/Storages/Transaction/tests/gtest_decoding_storage_schema_snapshot.cpp @@ -0,0 +1,65 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. + +#include +#include +#include + +#include "RowCodecTestUtils.h" + +namespace DB::tests +{ +static TableInfo getTableInfoByJson(const String & json_table_info) +{ + return TableInfo(json_table_info); +} +TEST(DecodingStorageSchemaSnapshotTest, CheckPKInfosUnderClusteredIndex) +{ + // table with column [A,B,C,D], primary keys [A,C] + const String json_table_info = R"json({"id":75,"name":{"O":"test","L":"test"},"charset":"utf8mb4","collate":"utf8mb4_bin","cols":[{"id":1,"name":{"O":"A","L":"a"},"offset":0,"origin_default":null,"origin_default_bit":null,"default":null,"default_bit":null,"default_is_expr":false,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":3,"Flag":4099,"Flen":11,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":"","hidden":false,"change_state_info":null,"version":2},{"id":2,"name":{"O":"B","L":"b"},"offset":1,"origin_default":null,"origin_default_bit":null,"default":null,"default_bit":null,"default_is_expr":false,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":15,"Flag":0,"Flen":20,"Decimal":0,"Charset":"utf8mb4","Collate":"utf8mb4_bin","Elems":null},"state":5,"comment":"","hidden":false,"change_state_info":null,"version":2},{"id":3,"name":{"O":"C","L":"c"},"offset":2,"origin_default":null,"origin_default_bit":null,"default":null,"default_bit":null,"default_is_expr":false,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":3,"Flag":4099,"Flen":11,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":"","hidden":false,"change_state_info":null,"version":2},{"id":4,"name":{"O":"D","L":"d"},"offset":3,"origin_default":null,"origin_default_bit":null,"default":null,"default_bit":null,"default_is_expr":false,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":3,"Flag":0,"Flen":11,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":"","hidden":false,"change_state_info":null,"version":2}],"index_info":[{"id":1,"idx_name":{"O":"PRIMARY","L":"primary"},"tbl_name":{"O":"","L":""},"idx_cols":[{"name":{"O":"A","L":"a"},"offset":0,"length":-1},{"name":{"O":"C","L":"c"},"offset":2,"length":-1}],"state":5,"comment":"","index_type":1,"is_unique":true,"is_primary":true,"is_invisible":false,"is_global":false}],"constraint_info":null,"fk_info":null,"state":5,"pk_is_handle":false,"is_common_handle":true,"common_handle_version":1,"comment":"","auto_inc_id":0,"auto_id_cache":0,"auto_rand_id":0,"max_col_id":4,"max_idx_id":1,"max_cst_id":0,"update_timestamp":434039123413303302,"ShardRowIDBits":0,"max_shard_row_id_bits":0,"auto_random_bits":0,"pre_split_regions":0,"partition":null,"compression":"","view":null,"sequence":null,"Lock":null,"version":4,"tiflash_replica":{"Count":1,"LocationLabels":[],"Available":false,"AvailablePartitionIDs":null},"is_columnar":false,"temp_table_type":0,"cache_table_status":0,"policy_ref_info":null,"stats_options":null})json"; + auto table_info = getTableInfoByJson(json_table_info); + auto decoding_schema = getDecodingStorageSchemaSnapshot(table_info); + + //check decoding_schema->pk_column_ids infos + ASSERT_EQ(decoding_schema->pk_column_ids.size(), 2); + ASSERT_EQ(decoding_schema->pk_column_ids[0], 1); + ASSERT_EQ(decoding_schema->pk_column_ids[1], 3); + + //check decoding_schema->pk_pos_map infos + ASSERT_EQ(decoding_schema->pk_column_ids.size(), decoding_schema->pk_pos_map.size()); + // there are three hidden column in the decoded block, so the position of A,C is 3,5 + ASSERT_EQ(decoding_schema->pk_pos_map.at(decoding_schema->pk_column_ids[0]), 3); + ASSERT_EQ(decoding_schema->pk_pos_map.at(decoding_schema->pk_column_ids[1]), 5); +} + +TEST(DecodingStorageSchemaSnapshotTest, CheckPKInfosUnderClusteredIndexAfterDropColumn) +{ + // drop column B for [A,B,C,D]; table with column [A,C,D], primary keys [A,C] + const String json_table_info = R"json({"id":75,"name":{"O":"test","L":"test"},"charset":"utf8mb4","collate":"utf8mb4_bin","cols":[{"id":1,"name":{"O":"A","L":"a"},"offset":0,"origin_default":null,"origin_default_bit":null,"default":null,"default_bit":null,"default_is_expr":false,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":3,"Flag":4099,"Flen":11,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":"","hidden":false,"change_state_info":null,"version":2},{"id":3,"name":{"O":"C","L":"c"},"offset":2,"origin_default":null,"origin_default_bit":null,"default":null,"default_bit":null,"default_is_expr":false,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":3,"Flag":4099,"Flen":11,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":"","hidden":false,"change_state_info":null,"version":2},{"id":4,"name":{"O":"D","L":"d"},"offset":3,"origin_default":null,"origin_default_bit":null,"default":null,"default_bit":null,"default_is_expr":false,"generated_expr_string":"","generated_stored":false,"dependences":null,"type":{"Tp":3,"Flag":0,"Flen":11,"Decimal":0,"Charset":"binary","Collate":"binary","Elems":null},"state":5,"comment":"","hidden":false,"change_state_info":null,"version":2}],"index_info":[{"id":1,"idx_name":{"O":"PRIMARY","L":"primary"},"tbl_name":{"O":"","L":""},"idx_cols":[{"name":{"O":"A","L":"a"},"offset":0,"length":-1},{"name":{"O":"C","L":"c"},"offset":2,"length":-1}],"state":5,"comment":"","index_type":1,"is_unique":true,"is_primary":true,"is_invisible":false,"is_global":false}],"constraint_info":null,"fk_info":null,"state":5,"pk_is_handle":false,"is_common_handle":true,"common_handle_version":1,"comment":"","auto_inc_id":0,"auto_id_cache":0,"auto_rand_id":0,"max_col_id":4,"max_idx_id":1,"max_cst_id":0,"update_timestamp":434039123413303302,"ShardRowIDBits":0,"max_shard_row_id_bits":0,"auto_random_bits":0,"pre_split_regions":0,"partition":null,"compression":"","view":null,"sequence":null,"Lock":null,"version":4,"tiflash_replica":{"Count":1,"LocationLabels":[],"Available":false,"AvailablePartitionIDs":null},"is_columnar":false,"temp_table_type":0,"cache_table_status":0,"policy_ref_info":null,"stats_options":null})json"; + auto table_info = getTableInfoByJson(json_table_info); + auto decoding_schema = getDecodingStorageSchemaSnapshot(table_info); + + //check decoding_schema->pk_column_ids infos + ASSERT_EQ(decoding_schema->pk_column_ids.size(), 2); + ASSERT_EQ(decoding_schema->pk_column_ids[0], 1); + ASSERT_EQ(decoding_schema->pk_column_ids[1], 3); + + //check decoding_schema->pk_pos_map infos + ASSERT_EQ(decoding_schema->pk_column_ids.size(), decoding_schema->pk_pos_map.size()); + // there are three hidden column in the decoded block, so the position of A,C is 3,4 + ASSERT_EQ(decoding_schema->pk_pos_map.at(decoding_schema->pk_column_ids[0]), 3); + ASSERT_EQ(decoding_schema->pk_pos_map.at(decoding_schema->pk_column_ids[1]), 4); +} + +} // namespace DB::tests diff --git a/dbms/src/Storages/Transaction/tests/gtest_region_block_reader.cpp b/dbms/src/Storages/Transaction/tests/gtest_region_block_reader.cpp index 6a883230854..d08b4dd3738 100644 --- a/dbms/src/Storages/Transaction/tests/gtest_region_block_reader.cpp +++ b/dbms/src/Storages/Transaction/tests/gtest_region_block_reader.cpp @@ -26,13 +26,13 @@ using ColumnIDs = std::vector; class RegionBlockReaderTestFixture : public ::testing::Test { protected: - Int64 handle_value_ = 100; - UInt8 del_mark_value_ = 0; - UInt64 version_value_ = 100; - size_t rows_ = 3; + Int64 handle_value = 100; + UInt8 del_mark_value = 0; + UInt64 version_value = 100; + size_t rows = 3; - RegionDataReadInfoList data_list_read_; - std::unordered_map fields_map_; + RegionDataReadInfoList data_list_read; + std::unordered_map fields_map; enum RowEncodeVersion { @@ -43,8 +43,8 @@ class RegionBlockReaderTestFixture : public ::testing::Test protected: void SetUp() override { - data_list_read_.clear(); - fields_map_.clear(); + data_list_read.clear(); + fields_map.clear(); } void TearDown() override {} @@ -52,8 +52,12 @@ class RegionBlockReaderTestFixture : public ::testing::Test void encodeColumns(TableInfo & table_info, std::vector & fields, RowEncodeVersion row_version) { // for later check + std::unordered_map column_name_columns_index_map; for (size_t i = 0; i < table_info.columns.size(); i++) - fields_map_.emplace(table_info.columns[i].id, fields[i]); + { + fields_map.emplace(table_info.columns[i].id, fields[i]); + column_name_columns_index_map.emplace(table_info.columns[i].name, i); + } std::vector value_fields; std::vector pk_fields; @@ -72,13 +76,13 @@ class RegionBlockReaderTestFixture : public ::testing::Test auto & primary_index_info = table_info.getPrimaryIndexInfo(); for (size_t i = 0; i < primary_index_info.idx_cols.size(); i++) { - size_t pk_offset = primary_index_info.idx_cols[i].offset; - EncodeDatum(pk_fields[i], table_info.columns[pk_offset].getCodecFlag(), pk_buf); + auto idx = column_name_columns_index_map[primary_index_info.idx_cols[i].name]; + EncodeDatum(pk_fields[i], table_info.columns[idx].getCodecFlag(), pk_buf); } } else { - DB::EncodeInt64(handle_value_, pk_buf); + DB::EncodeInt64(handle_value, pk_buf); } RawTiDBPK pk{std::make_shared(pk_buf.releaseStr())}; // create value @@ -96,44 +100,44 @@ class RegionBlockReaderTestFixture : public ::testing::Test throw Exception("Unknown row format " + std::to_string(row_version), ErrorCodes::LOGICAL_ERROR); } auto row_value = std::make_shared(std::move(value_buf.str())); - for (size_t i = 0; i < rows_; i++) - data_list_read_.emplace_back(pk, del_mark_value_, version_value_, row_value); + for (size_t i = 0; i < rows; i++) + data_list_read.emplace_back(pk, del_mark_value, version_value, row_value); } void checkBlock(DecodingStorageSchemaSnapshotConstPtr decoding_schema, const Block & block) const { ASSERT_EQ(block.columns(), decoding_schema->column_defines->size()); - for (size_t row = 0; row < rows_; row++) + for (size_t row = 0; row < rows; row++) { for (size_t pos = 0; pos < block.columns(); pos++) { - auto & column_element = block.getByPosition(pos); + const auto & column_element = block.getByPosition(pos); if (row == 0) { - ASSERT_EQ(column_element.column->size(), rows_); + ASSERT_EQ(column_element.column->size(), rows); } if (column_element.name == EXTRA_HANDLE_COLUMN_NAME) { if (decoding_schema->is_common_handle) { - ASSERT_EQ((*column_element.column)[row], Field(*std::get<0>(data_list_read_[row]))); + ASSERT_EQ((*column_element.column)[row], Field(*std::get<0>(data_list_read[row]))); } else { - ASSERT_EQ((*column_element.column)[row], Field(handle_value_)); + ASSERT_EQ((*column_element.column)[row], Field(handle_value)); } } else if (column_element.name == VERSION_COLUMN_NAME) { - ASSERT_EQ((*column_element.column)[row], Field(version_value_)); + ASSERT_EQ((*column_element.column)[row], Field(version_value)); } else if (column_element.name == TAG_COLUMN_NAME) { - ASSERT_EQ((*column_element.column)[row], Field(NearestFieldType::Type(del_mark_value_))); + ASSERT_EQ((*column_element.column)[row], Field(NearestFieldType::Type(del_mark_value))); } else { - ASSERT_EQ((*column_element.column)[row], fields_map_.at(column_element.column_id)); + ASSERT_EQ((*column_element.column)[row], fields_map.at(column_element.column_id)); } } } @@ -143,7 +147,7 @@ class RegionBlockReaderTestFixture : public ::testing::Test { RegionBlockReader reader{decoding_schema}; Block block = createBlockSortByColumnID(decoding_schema); - if (!reader.read(block, data_list_read_, force_decode)) + if (!reader.read(block, data_list_read, force_decode)) return false; checkBlock(decoding_schema, block); @@ -155,7 +159,7 @@ class RegionBlockReaderTestFixture : public ::testing::Test return getTableInfoAndFields( handle_ids, is_common_handle, - ColumnIDValue(2, handle_value_), + ColumnIDValue(2, handle_value), ColumnIDValue(3, std::numeric_limits::max()), ColumnIDValue(4, std::numeric_limits::min()), ColumnIDValue(9, String("aaa")), @@ -170,7 +174,7 @@ class RegionBlockReaderTestFixture : public ::testing::Test handle_ids, is_common_handle, ColumnIDValue(1, String("")), - ColumnIDValue(2, handle_value_), + ColumnIDValue(2, handle_value), ColumnIDValue(3, std::numeric_limits::max()), ColumnIDValue(4, std::numeric_limits::min()), ColumnIDValue(8, String("")), @@ -182,12 +186,12 @@ class RegionBlockReaderTestFixture : public ::testing::Test // add default value for missing column std::vector missing_column_ids{1, 8, 13}; String missing_column_default_value = String("default"); - for (size_t i = 0; i < table_info.columns.size(); i++) + for (auto & column : table_info.columns) { - if (std::find(missing_column_ids.begin(), missing_column_ids.end(), table_info.columns[i].id) != missing_column_ids.end()) + if (std::find(missing_column_ids.begin(), missing_column_ids.end(), column.id) != missing_column_ids.end()) { - table_info.columns[i].origin_default_value = missing_column_default_value; - fields_map_.emplace(table_info.columns[i].id, Field(missing_column_default_value)); + column.origin_default_value = missing_column_default_value; + fields_map.emplace(column.id, Field(missing_column_default_value)); } } return table_info; @@ -199,7 +203,7 @@ class RegionBlockReaderTestFixture : public ::testing::Test std::tie(table_info, std::ignore) = getTableInfoAndFields( handle_ids, is_common_handle, - ColumnIDValue(2, handle_value_), + ColumnIDValue(2, handle_value), ColumnIDValue(4, std::numeric_limits::min()), ColumnIDValue(9, String("aaa")), ColumnIDValue(10, DecimalField(ToDecimal(12345678910ULL, 4), 4))); @@ -212,7 +216,7 @@ class RegionBlockReaderTestFixture : public ::testing::Test std::tie(table_info, std::ignore) = getTableInfoAndFields( handle_ids, is_common_handle, - ColumnIDValue(2, handle_value_), + ColumnIDValue(2, handle_value), ColumnIDValue(3, std::numeric_limits::max()), ColumnIDValue(4, std::numeric_limits::min()), ColumnIDValue(9, String("aaa")), @@ -227,7 +231,7 @@ class RegionBlockReaderTestFixture : public ::testing::Test std::tie(table_info, std::ignore) = getTableInfoAndFields( handle_ids, is_common_handle, - ColumnIDValue(2, handle_value_), + ColumnIDValue(2, handle_value), ColumnIDValue(3, std::numeric_limits::max()), ColumnIDValue(4, std::numeric_limits::min()), ColumnIDValue(9, String("aaa")), diff --git a/tests/fullstack-test-dt/clustered_index/ddl.test b/tests/fullstack-test-dt/clustered_index/ddl.test index 8abe450c11a..6c4925c9619 100644 --- a/tests/fullstack-test-dt/clustered_index/ddl.test +++ b/tests/fullstack-test-dt/clustered_index/ddl.test @@ -66,3 +66,89 @@ mysql> set session tidb_isolation_read_engines='tiflash'; select * from test.t_2 mysql> drop table test.t_1; mysql> drop table test.t_2; + +### about issue 5154 to check whether add column/drop column will effect the cluster index decode +### drop the column between two columns that are cluster index columns + +mysql> drop table if exists test.t_3; +mysql> create table test.t_3 (A int, B varchar(20), C int, D int, PRIMARY KEY(A,C) CLUSTERED); +mysql> insert into test.t_3 values (1,'1',1,1),(2,'2',2,2); + +mysql> alter table test.t_3 set tiflash replica 1; + +func> wait_table test t_3 + +mysql> set session tidb_isolation_read_engines='tiflash';select * from test.t_3; ++---+---+---+---+ +| A | B | C | D | ++---+---+---+---+ +| 1 | 1 | 1 | 1 | +| 2 | 2 | 2 | 2 | ++---+---+---+---+ + +mysql> alter table test.t_3 drop column B; + +mysql> set session tidb_isolation_read_engines='tiflash';select * from test.t_3; ++---+---+---+ +| A | C | D | ++---+---+---+ +| 1 | 1 | 1 | +| 2 | 2 | 2 | ++---+---+---+ + +# insert some rows +mysql> insert into test.t_3 values (3,3,3),(4,4,4); + +mysql> set session tidb_isolation_read_engines='tiflash';select * from test.t_3; ++---+---+---+ +| A | C | D | ++---+---+---+ +| 1 | 1 | 1 | +| 2 | 2 | 2 | +| 3 | 3 | 3 | +| 4 | 4 | 4 | ++---+---+---+ + +mysql> drop table test.t_3; + +### add the column between two columns that are cluster index columns +mysql> drop table if exists test.t_4 +mysql> create table test.t_4 (A int, B varchar(20), C int, D int, PRIMARY KEY(A,C) CLUSTERED); + +mysql> insert into test.t_4 values (1,'1',1,1),(2,'2',2,2); + +mysql> alter table test.t_4 set tiflash replica 1; + +func> wait_table test t_4 + +mysql> set session tidb_isolation_read_engines='tiflash';select * from test.t_4; ++---+---+---+---+ +| A | B | C | D | ++---+---+---+---+ +| 1 | 1 | 1 | 1 | +| 2 | 2 | 2 | 2 | ++---+---+---+---+ + +mysql> alter table test.t_4 Add column E int after B; + +mysql> set session tidb_isolation_read_engines='tiflash';select * from test.t_4; ++---+---+------+---+---+ +| A | B | E | C | D | ++---+---+------+---+---+ +| 1 | 1 | NULL | 1 | 1 | +| 2 | 2 | NULL | 2 | 2 | ++---+---+------+---+---+ + +mysql> insert into test.t_4 values (3,'3',3,3,3),(4,'4',4,4,4); + +mysql> set session tidb_isolation_read_engines='tiflash';select * from test.t_4; ++---+---+------+------+------+ +| A | B | E | C | D | ++---+---+------+------+------+ +| 1 | 1 | NULL | 1 | 1 | +| 2 | 2 | NULL | 2 | 2 | +| 3 | 3 | 3 | 3 | 3 | +| 4 | 4 | 4 | 4 | 4 | ++---+---+------+------+------+ + +mysql> drop table test.t_4; \ No newline at end of file From 8a5dc2963af649fc02d15a4034106a50aa9c093e Mon Sep 17 00:00:00 2001 From: yibin Date: Wed, 22 Jun 2022 17:50:37 +0800 Subject: [PATCH 122/127] Add random failpoint in critical paths (#4876) close pingcap/tiflash#4807 --- dbms/src/Common/FailPoint.cpp | 65 ++++++++++++++++++- dbms/src/Common/FailPoint.h | 20 +++++- dbms/src/Common/wrapInvocable.h | 1 - .../DataStreams/SharedQueryBlockInputStream.h | 7 ++ dbms/src/DataStreams/SizeLimits.cpp | 23 +++++-- dbms/src/Flash/EstablishCall.cpp | 7 ++ dbms/src/Flash/Mpp/ExchangeReceiver.cpp | 15 ++++- dbms/src/Flash/Mpp/MPPTask.cpp | 11 +++- dbms/src/Flash/Mpp/MPPTaskManager.cpp | 7 ++ dbms/src/Flash/Mpp/MPPTunnel.cpp | 2 + dbms/src/Flash/Mpp/MinTSOScheduler.cpp | 10 ++- dbms/src/Interpreters/Aggregator.cpp | 9 +++ dbms/src/Interpreters/Join.cpp | 12 +++- dbms/src/Interpreters/executeQuery.cpp | 7 +- dbms/src/Server/Server.cpp | 2 + 15 files changed, 181 insertions(+), 17 deletions(-) diff --git a/dbms/src/Common/FailPoint.cpp b/dbms/src/Common/FailPoint.cpp index 10d0a558a50..1dff46c273b 100644 --- a/dbms/src/Common/FailPoint.cpp +++ b/dbms/src/Common/FailPoint.cpp @@ -12,7 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include +#include +#include +#include +#include +#include #include #include @@ -21,7 +27,6 @@ namespace DB { std::unordered_map> FailPointHelper::fail_point_wait_channels; - #define APPLY_FOR_FAILPOINTS_ONCE(M) \ M(exception_between_drop_meta_and_data) \ M(exception_between_alter_data_and_meta) \ @@ -109,6 +114,22 @@ std::unordered_map> FailPointHelper::f M(pause_query_init) +#define APPLY_FOR_RANDOM_FAILPOINTS(M) \ + M(random_tunnel_wait_timeout_failpoint) \ + M(random_tunnel_init_rpc_failure_failpoint) \ + M(random_receiver_sync_msg_push_failure_failpoint) \ + M(random_receiver_async_msg_push_failure_failpoint) \ + M(random_limit_check_failpoint) \ + M(random_join_build_failpoint) \ + M(random_join_prob_failpoint) \ + M(random_aggregate_create_state_failpoint) \ + M(random_aggregate_merge_failpoint) \ + M(random_sharedquery_failpoint) \ + M(random_interpreter_failpoint) \ + M(random_task_lifecycle_failpoint) \ + M(random_task_manager_find_task_failure_failpoint) \ + M(random_min_tso_scheduler_failpoint) + namespace FailPoints { #define M(NAME) extern const char(NAME)[] = #NAME ""; @@ -116,6 +137,7 @@ APPLY_FOR_FAILPOINTS_ONCE(M) APPLY_FOR_FAILPOINTS(M) APPLY_FOR_PAUSEABLE_FAILPOINTS_ONCE(M) APPLY_FOR_PAUSEABLE_FAILPOINTS(M) +APPLY_FOR_RANDOM_FAILPOINTS(M) #undef M } // namespace FailPoints @@ -179,7 +201,7 @@ void FailPointHelper::enableFailPoint(const String & fail_point_name) #undef M #undef SUB_M - throw Exception("Cannot find fail point " + fail_point_name, ErrorCodes::FAIL_POINT_ERROR); + throw Exception(fmt::format("Cannot find fail point {}", fail_point_name), ErrorCodes::FAIL_POINT_ERROR); } void FailPointHelper::disableFailPoint(const String & fail_point_name) @@ -204,6 +226,41 @@ void FailPointHelper::wait(const String & fail_point_name) ptr->wait(); } } + +void FailPointHelper::initRandomFailPoints(Poco::Util::LayeredConfiguration & config, Poco::Logger * log) +{ + String random_fail_point_cfg = config.getString("flash.random_fail_points", ""); + if (random_fail_point_cfg.empty()) + return; + + Poco::StringTokenizer string_tokens(random_fail_point_cfg, ","); + for (const auto & string_token : string_tokens) + { + Poco::StringTokenizer pair_tokens(string_token, "-"); + RUNTIME_ASSERT((pair_tokens.count() == 2), log, "RandomFailPoints config should be FailPointA-RatioA,FailPointB-RatioB,... format"); + double rate = atof(pair_tokens[1].c_str()); //NOLINT(cert-err34-c): check conversion error manually + RUNTIME_ASSERT((0 <= rate && rate <= 1.0), log, "RandomFailPoint trigger rate should in [0,1], while {}", rate); + enableRandomFailPoint(pair_tokens[0], rate); + } + LOG_FMT_INFO(log, "Enable RandomFailPoints: {}", random_fail_point_cfg); +} + +void FailPointHelper::enableRandomFailPoint(const String & fail_point_name, double rate) +{ +#define SUB_M(NAME) \ + if (fail_point_name == FailPoints::NAME) \ + { \ + fiu_enable_random(FailPoints::NAME, 1, nullptr, 0, rate); \ + return; \ + } + +#define M(NAME) SUB_M(NAME) + APPLY_FOR_RANDOM_FAILPOINTS(M) +#undef M +#undef SUB_M + + throw Exception(fmt::format("Cannot find fail point {}", fail_point_name), ErrorCodes::FAIL_POINT_ERROR); +} #else class FailPointChannel { @@ -214,6 +271,10 @@ void FailPointHelper::enableFailPoint(const String &) {} void FailPointHelper::disableFailPoint(const String &) {} void FailPointHelper::wait(const String &) {} + +void FailPointHelper::initRandomFailPoints(Poco::Util::LayeredConfiguration & config, Poco::Logger * log) {} + +void FailPointHelper::enableRandomFailPoint(const String & fail_point_name, double rate) {} #endif } // namespace DB diff --git a/dbms/src/Common/FailPoint.h b/dbms/src/Common/FailPoint.h index 2cf40ad55e4..31df2dbdcd2 100644 --- a/dbms/src/Common/FailPoint.h +++ b/dbms/src/Common/FailPoint.h @@ -21,6 +21,15 @@ #include +namespace Poco +{ +class Logger; +namespace Util +{ +class LayeredConfiguration; +} +} // namespace Poco + namespace DB { namespace ErrorCodes @@ -35,7 +44,6 @@ extern const int FAIL_POINT_ERROR; // When `fail_point` is enabled, wait till it is disabled #define FAIL_POINT_PAUSE(fail_point) fiu_do_on(fail_point, FailPointHelper::wait(fail_point);) - class FailPointChannel; class FailPointHelper { @@ -46,6 +54,16 @@ class FailPointHelper static void wait(const String & fail_point_name); + /* + * For Server RandomFailPoint test usage. When FIU_ENABLE is defined, this function does the following work: + * 1. Return if TiFlash config has empty flash.random_fail_points cfg + * 2. Parse flash.random_fail_points, which expect to has "FailPointA-RatioA,FailPointB-RatioB,..." format + * 3. Call enableRandomFailPoint method with parsed FailPointName and Rate + */ + static void initRandomFailPoints(Poco::Util::LayeredConfiguration & config, Poco::Logger * log); + + static void enableRandomFailPoint(const String & fail_point_name, double rate); + private: static std::unordered_map> fail_point_wait_channels; }; diff --git a/dbms/src/Common/wrapInvocable.h b/dbms/src/Common/wrapInvocable.h index d6cee519835..1c93bb3e782 100644 --- a/dbms/src/Common/wrapInvocable.h +++ b/dbms/src/Common/wrapInvocable.h @@ -35,7 +35,6 @@ inline auto wrapInvocable(bool propagate_memory_tracker, Func && func, Args &&.. // run the task with the parameters provided return std::apply(std::move(func), std::move(args)); }; - return capture; } } // namespace DB diff --git a/dbms/src/DataStreams/SharedQueryBlockInputStream.h b/dbms/src/DataStreams/SharedQueryBlockInputStream.h index e7cece67f0b..d7c0707b5aa 100644 --- a/dbms/src/DataStreams/SharedQueryBlockInputStream.h +++ b/dbms/src/DataStreams/SharedQueryBlockInputStream.h @@ -14,6 +14,7 @@ #pragma once +#include #include #include #include @@ -24,6 +25,11 @@ namespace DB { +namespace FailPoints +{ +extern const char random_sharedquery_failpoint[]; +} // namespace FailPoints + /** This block input stream is used by SharedQuery. * It enable multiple threads read from one stream. */ @@ -136,6 +142,7 @@ class SharedQueryBlockInputStream : public IProfilingBlockInputStream in->readPrefix(); while (true) { + FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::random_sharedquery_failpoint); Block block = in->read(); // in is finished or queue is canceled if (!block || !queue.push(block)) diff --git a/dbms/src/DataStreams/SizeLimits.cpp b/dbms/src/DataStreams/SizeLimits.cpp index 7dd5e1524ba..4d1bfaae997 100644 --- a/dbms/src/DataStreams/SizeLimits.cpp +++ b/dbms/src/DataStreams/SizeLimits.cpp @@ -12,22 +12,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include #include -#include +#include +#include +#include +#include namespace DB { +namespace FailPoints +{ +extern const char random_limit_check_failpoint[]; +} // namespace FailPoints bool SizeLimits::check(UInt64 rows, UInt64 bytes, const char * what, int exception_code) const { - if (max_rows && rows > max_rows) + bool rows_exceed_limit = max_rows && rows > max_rows; + fiu_do_on(FailPoints::random_limit_check_failpoint, rows_exceed_limit = true;); + if (rows_exceed_limit) { if (overflow_mode == OverflowMode::THROW) throw Exception("Limit for " + std::string(what) + " exceeded, max rows: " + formatReadableQuantity(max_rows) - + ", current rows: " + formatReadableQuantity(rows), exception_code); + + ", current rows: " + formatReadableQuantity(rows), + exception_code); else return false; } @@ -36,7 +44,8 @@ bool SizeLimits::check(UInt64 rows, UInt64 bytes, const char * what, int excepti { if (overflow_mode == OverflowMode::THROW) throw Exception("Limit for " + std::string(what) + " exceeded, max bytes: " + formatReadableSizeWithBinarySuffix(max_bytes) - + ", current bytes: " + formatReadableSizeWithBinarySuffix(bytes), exception_code); + + ", current bytes: " + formatReadableSizeWithBinarySuffix(bytes), + exception_code); else return false; } @@ -44,4 +53,4 @@ bool SizeLimits::check(UInt64 rows, UInt64 bytes, const char * what, int excepti return true; } -} +} // namespace DB diff --git a/dbms/src/Flash/EstablishCall.cpp b/dbms/src/Flash/EstablishCall.cpp index 8af81e30962..89857a2407e 100644 --- a/dbms/src/Flash/EstablishCall.cpp +++ b/dbms/src/Flash/EstablishCall.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include #include @@ -19,6 +20,11 @@ namespace DB { +namespace FailPoints +{ +extern const char random_tunnel_init_rpc_failure_failpoint[]; +} // namespace FailPoints + EstablishCallData::EstablishCallData(AsyncFlashService * service, grpc::ServerCompletionQueue * cq, grpc::ServerCompletionQueue * notify_cq, const std::shared_ptr> & is_shutdown) : service(service) , cq(cq) @@ -71,6 +77,7 @@ void EstablishCallData::initRpc() std::exception_ptr eptr = nullptr; try { + FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::random_tunnel_init_rpc_failure_failpoint); service->establishMPPConnectionSyncOrAsync(&ctx, &request, nullptr, this); } catch (...) diff --git a/dbms/src/Flash/Mpp/ExchangeReceiver.cpp b/dbms/src/Flash/Mpp/ExchangeReceiver.cpp index f194afee31f..ec8bde51469 100644 --- a/dbms/src/Flash/Mpp/ExchangeReceiver.cpp +++ b/dbms/src/Flash/Mpp/ExchangeReceiver.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include #include @@ -22,6 +23,12 @@ namespace DB { +namespace FailPoints +{ +extern const char random_receiver_sync_msg_push_failure_failpoint[]; +extern const char random_receiver_async_msg_push_failure_failpoint[]; +} // namespace FailPoints + namespace { String getReceiverStateStr(const ExchangeReceiverState & s) @@ -257,7 +264,9 @@ class AsyncRequestHandler : public UnaryCallback recv_msg->packet = std::move(packet); recv_msg->source_index = request->source_index; recv_msg->req_info = req_info; - if (!msg_channel->push(std::move(recv_msg))) + bool push_success = msg_channel->push(std::move(recv_msg)); + fiu_do_on(FailPoints::random_receiver_async_msg_push_failure_failpoint, push_success = false;); + if (!push_success) return false; // can't reuse packet since it is sent to readers. packet = std::make_shared(); @@ -483,7 +492,9 @@ void ExchangeReceiverBase::readLoop(const Request & req) if (recv_msg->packet->has_error()) throw Exception("Exchange receiver meet error : " + recv_msg->packet->error().msg()); - if (!msg_channel.push(std::move(recv_msg))) + bool push_success = msg_channel.push(std::move(recv_msg)); + fiu_do_on(FailPoints::random_receiver_sync_msg_push_failure_failpoint, push_success = false;); + if (!push_success) { meet_error = true; auto local_state = getState(); diff --git a/dbms/src/Flash/Mpp/MPPTask.cpp b/dbms/src/Flash/Mpp/MPPTask.cpp index 0381bbdfa04..ac084ba4550 100644 --- a/dbms/src/Flash/Mpp/MPPTask.cpp +++ b/dbms/src/Flash/Mpp/MPPTask.cpp @@ -51,6 +51,7 @@ extern const char exception_before_mpp_register_tunnel_for_root_mpp_task[]; extern const char exception_during_mpp_register_tunnel_for_non_root_mpp_task[]; extern const char exception_during_mpp_write_err_to_tunnel[]; extern const char force_no_local_region_for_mpp_task[]; +extern const char random_task_lifecycle_failpoint[]; } // namespace FailPoints MPPTask::MPPTask(const mpp::TaskMeta & meta_, const ContextPtr & context_) @@ -394,7 +395,15 @@ void MPPTask::runImpl() writeErrToAllTunnels(err_msg); } LOG_FMT_INFO(log, "task ends, time cost is {} ms.", stopwatch.elapsedMilliseconds()); - unregisterTask(); + // unregister flag is only for FailPoint usage, to produce the situation that MPPTask is destructed + // by grpc CancelMPPTask thread; + bool unregister = true; + fiu_do_on(FailPoints::random_task_lifecycle_failpoint, { + if (!err_msg.empty()) + unregister = false; + }); + if (unregister) + unregisterTask(); if (switchStatus(RUNNING, FINISHED)) LOG_INFO(log, "finish task"); diff --git a/dbms/src/Flash/Mpp/MPPTaskManager.cpp b/dbms/src/Flash/Mpp/MPPTaskManager.cpp index 531f8f7a10d..3df4af5de5f 100644 --- a/dbms/src/Flash/Mpp/MPPTaskManager.cpp +++ b/dbms/src/Flash/Mpp/MPPTaskManager.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include #include @@ -22,6 +23,11 @@ namespace DB { +namespace FailPoints +{ +extern const char random_task_manager_find_task_failure_failpoint[]; +} // namespace FailPoints + MPPTaskManager::MPPTaskManager(MPPTaskSchedulerPtr scheduler_) : scheduler(std::move(scheduler_)) , log(&Poco::Logger::get("TaskManager")) @@ -50,6 +56,7 @@ MPPTaskPtr MPPTaskManager::findTaskWithTimeout(const mpp::TaskMeta & meta, std:: it = query_it->second->task_map.find(id); return it != query_it->second->task_map.end(); }); + fiu_do_on(FailPoints::random_task_manager_find_task_failure_failpoint, ret = false;); if (cancelled) { errMsg = fmt::format("Task [{},{}] has been cancelled.", meta.start_ts(), meta.task_id()); diff --git a/dbms/src/Flash/Mpp/MPPTunnel.cpp b/dbms/src/Flash/Mpp/MPPTunnel.cpp index 826e7fea88a..13a7eaad95e 100644 --- a/dbms/src/Flash/Mpp/MPPTunnel.cpp +++ b/dbms/src/Flash/Mpp/MPPTunnel.cpp @@ -25,6 +25,7 @@ namespace DB namespace FailPoints { extern const char exception_during_mpp_close_tunnel[]; +extern const char random_tunnel_wait_timeout_failpoint[]; } // namespace FailPoints template @@ -322,6 +323,7 @@ void MPPTunnelBase::waitUntilConnectedOrFinished(std::unique_lock #include #include #include namespace DB { +namespace FailPoints +{ +extern const char random_min_tso_scheduler_failpoint[]; +} // namespace FailPoints + constexpr UInt64 MAX_UINT64 = std::numeric_limits::max(); constexpr UInt64 OS_THREAD_SOFT_LIMIT = 100000; @@ -193,7 +199,9 @@ bool MinTSOScheduler::scheduleImp(const UInt64 tso, const MPPQueryTaskSetPtr & q } else { - if (tso <= min_tso) /// the min_tso query should fully run, otherwise throw errors here. + bool is_tso_min = tso <= min_tso; + fiu_do_on(FailPoints::random_min_tso_scheduler_failpoint, is_tso_min = true;); + if (is_tso_min) /// the min_tso query should fully run, otherwise throw errors here. { has_error = true; auto msg = fmt::format("threads are unavailable for the query {} ({} min_tso {}) {}, need {}, but used {} of the thread hard limit {}, {} active and {} waiting queries.", tso, tso == min_tso ? "is" : "is newer than", min_tso, isWaiting ? "from the waiting set" : "when directly schedule it", needed_threads, estimated_thread_usage, thread_hard_limit, active_set.size(), waiting_set.size()); diff --git a/dbms/src/Interpreters/Aggregator.cpp b/dbms/src/Interpreters/Aggregator.cpp index 6a39bc333a8..6cb947a1bfa 100644 --- a/dbms/src/Interpreters/Aggregator.cpp +++ b/dbms/src/Interpreters/Aggregator.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -48,6 +49,11 @@ extern const int CANNOT_MERGE_DIFFERENT_AGGREGATED_DATA_VARIANTS; extern const int LOGICAL_ERROR; } // namespace ErrorCodes +namespace FailPoints +{ +extern const char random_aggregate_create_state_failpoint[]; +extern const char random_aggregate_merge_failpoint[]; +} // namespace FailPoints AggregatedDataVariants::~AggregatedDataVariants() { @@ -317,6 +323,7 @@ void Aggregator::createAggregateStates(AggregateDataPtr & aggregate_data) const * In order that then everything is properly destroyed, we "roll back" some of the created states. * The code is not very convenient. */ + FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::random_aggregate_create_state_failpoint); aggregate_functions[j]->create(aggregate_data + offsets_of_aggregate_states[j]); } catch (...) @@ -1504,6 +1511,8 @@ class MergingAndConvertingBlockInputStream : public IProfilingBlockInputStream if (current_bucket_num >= NUM_BUCKETS) return {}; + FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::random_aggregate_merge_failpoint); + AggregatedDataVariantsPtr & first = data[0]; if (current_bucket_num == -1) diff --git a/dbms/src/Interpreters/Join.cpp b/dbms/src/Interpreters/Join.cpp index 820618a6e8b..181ebcaaa64 100644 --- a/dbms/src/Interpreters/Join.cpp +++ b/dbms/src/Interpreters/Join.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -26,9 +27,17 @@ #include #include #include +#include + namespace DB { +namespace FailPoints +{ +extern const char random_join_build_failpoint[]; +extern const char random_join_prob_failpoint[]; +} // namespace FailPoints + namespace ErrorCodes { extern const int UNKNOWN_SET_DATA_VARIANT; @@ -621,6 +630,7 @@ void NO_INLINE insertFromBlockImplTypeCaseWithLock( } for (size_t insert_index = 0; insert_index < segment_index_info.size(); insert_index++) { + FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::random_join_build_failpoint); size_t segment_index = (insert_index + stream_index) % segment_index_info.size(); if (segment_index == segment_size) { @@ -1513,7 +1523,7 @@ void Join::joinBlockImpl(Block & block, const Maps & maps) const default: throw Exception("Unknown JOIN keys variant.", ErrorCodes::UNKNOWN_SET_DATA_VARIANT); } - + FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::random_join_prob_failpoint); for (size_t i = 0; i < num_columns_to_add; ++i) { const ColumnWithTypeAndName & sample_col = sample_block_with_columns_to_add.getByPosition(i); diff --git a/dbms/src/Interpreters/executeQuery.cpp b/dbms/src/Interpreters/executeQuery.cpp index 96cfc0a58ae..78ad4b41ce6 100644 --- a/dbms/src/Interpreters/executeQuery.cpp +++ b/dbms/src/Interpreters/executeQuery.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include #include @@ -53,7 +54,10 @@ extern const int LOGICAL_ERROR; extern const int QUERY_IS_TOO_LARGE; extern const int INTO_OUTFILE_NOT_ALLOWED; } // namespace ErrorCodes - +namespace FailPoints +{ +extern const char random_interpreter_failpoint[]; +} // namespace FailPoints namespace { void checkASTSizeLimits(const IAST & ast, const Settings & settings) @@ -226,6 +230,7 @@ std::tuple executeQueryImpl( context.setProcessListElement(&process_list_entry->get()); } + FAIL_POINT_TRIGGER_EXCEPTION(FailPoints::random_interpreter_failpoint); auto interpreter = query_src.interpreter(context, stage); res = interpreter->execute(); diff --git a/dbms/src/Server/Server.cpp b/dbms/src/Server/Server.cpp index 1bb35e51866..571ba8fe3a5 100644 --- a/dbms/src/Server/Server.cpp +++ b/dbms/src/Server/Server.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -977,6 +978,7 @@ int Server::main(const std::vector & /*args*/) Poco::Logger * log = &logger(); #ifdef FIU_ENABLE fiu_init(0); // init failpoint + FailPointHelper::initRandomFailPoints(config(), log); #endif UpdateMallocConfig(log); From 7c19a375044169dcc48bb365d78b32480975ec7c Mon Sep 17 00:00:00 2001 From: hehechen Date: Wed, 22 Jun 2022 18:28:37 +0800 Subject: [PATCH 123/127] Segment test framework (#5150) close pingcap/tiflash#5151 --- .../src/Storages/DeltaMerge/tests/DMTestEnv.h | 5 +- .../DeltaMerge/tests/gtest_segment.cpp | 86 ++++ .../tests/gtest_segment_test_basic.cpp | 430 ++++++++++++++++++ .../tests/gtest_segment_test_basic.h | 123 +++++ 4 files changed, 642 insertions(+), 2 deletions(-) create mode 100644 dbms/src/Storages/DeltaMerge/tests/gtest_segment.cpp create mode 100644 dbms/src/Storages/DeltaMerge/tests/gtest_segment_test_basic.cpp create mode 100644 dbms/src/Storages/DeltaMerge/tests/gtest_segment_test_basic.h diff --git a/dbms/src/Storages/DeltaMerge/tests/DMTestEnv.h b/dbms/src/Storages/DeltaMerge/tests/DMTestEnv.h index b35dae0cbe2..84fafbc46ef 100644 --- a/dbms/src/Storages/DeltaMerge/tests/DMTestEnv.h +++ b/dbms/src/Storages/DeltaMerge/tests/DMTestEnv.h @@ -273,7 +273,8 @@ class DMTestEnv DataTypePtr pk_type = EXTRA_HANDLE_COLUMN_INT_TYPE, bool is_common_handle = false, size_t rowkey_column_size = 1, - bool with_internal_columns = true) + bool with_internal_columns = true, + bool is_deleted = false) { Block block; const size_t num_rows = (end - beg); @@ -324,7 +325,7 @@ class DMTestEnv VERSION_COLUMN_ID)); // tag_col block.insert(DB::tests::createColumn( - std::vector(num_rows, 0), + std::vector(num_rows, is_deleted), TAG_COLUMN_NAME, TAG_COLUMN_ID)); } diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_segment.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_segment.cpp new file mode 100644 index 00000000000..1c68ba3bb2a --- /dev/null +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_segment.cpp @@ -0,0 +1,86 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. +#include +#include +#include +#include +#include + + +namespace DB +{ +namespace DM +{ +namespace tests +{ +class SegmentOperationTest : public SegmentTestBasic +{ +protected: + static void SetUpTestCase() {} +}; + +TEST_F(SegmentOperationTest, Issue4956) +try +{ + SegmentTestOptions options; + reloadWithOptions(options); + + // flush data, make the segment can be split. + writeSegment(DELTA_MERGE_FIRST_SEGMENT_ID); + flushSegmentCache(DELTA_MERGE_FIRST_SEGMENT_ID); + // write data to cache, reproduce the https://github.com/pingcap/tiflash/issues/4956 + writeSegment(DELTA_MERGE_FIRST_SEGMENT_ID); + deleteRangeSegment(DELTA_MERGE_FIRST_SEGMENT_ID); + auto segment_id = splitSegment(DELTA_MERGE_FIRST_SEGMENT_ID); + ASSERT_TRUE(segment_id.has_value()); + + mergeSegment(DELTA_MERGE_FIRST_SEGMENT_ID, *segment_id); +} +CATCH + +TEST_F(SegmentOperationTest, TestSegment) +try +{ + SegmentTestOptions options; + reloadWithOptions(options); + writeSegment(DELTA_MERGE_FIRST_SEGMENT_ID); + flushSegmentCache(DELTA_MERGE_FIRST_SEGMENT_ID); + mergeSegmentDelta(DELTA_MERGE_FIRST_SEGMENT_ID); + auto segment_id = splitSegment(DELTA_MERGE_FIRST_SEGMENT_ID); + ASSERT_TRUE(segment_id.has_value()); + + size_t origin_rows = getSegmentRowNum(DELTA_MERGE_FIRST_SEGMENT_ID); + + writeSegment(*segment_id); + flushSegmentCache(*segment_id); + deleteRangeSegment(*segment_id); + writeSegmentWithDeletedPack(*segment_id); + mergeSegment(DELTA_MERGE_FIRST_SEGMENT_ID, *segment_id); + + EXPECT_EQ(getSegmentRowNum(DELTA_MERGE_FIRST_SEGMENT_ID), origin_rows); +} +CATCH + +TEST_F(SegmentOperationTest, TestSegmentRandom) +try +{ + SegmentTestOptions options; + options.is_common_handle = true; + reloadWithOptions(options); + randomSegmentTest(100); +} +CATCH +} // namespace tests +} // namespace DM +} // namespace DB diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_segment_test_basic.cpp b/dbms/src/Storages/DeltaMerge/tests/gtest_segment_test_basic.cpp new file mode 100644 index 00000000000..c676f2e08d5 --- /dev/null +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_segment_test_basic.cpp @@ -0,0 +1,430 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DB +{ +namespace DM +{ +namespace tests +{ +void SegmentTestBasic::reloadWithOptions(SegmentTestOptions config) +{ + TiFlashStorageTestBasic::SetUp(); + options = config; + table_columns = std::make_shared(); + + root_segment = reload(config.is_common_handle); + ASSERT_EQ(root_segment->segmentId(), DELTA_MERGE_FIRST_SEGMENT_ID); + segments.clear(); + segments[DELTA_MERGE_FIRST_SEGMENT_ID] = root_segment; +} + +PageId SegmentTestBasic::createNewSegmentWithSomeData() +{ + SegmentPtr new_segment; + std::tie(root_segment, new_segment) = root_segment->split(dmContext(), tableColumns()); + + const size_t num_rows_write_per_batch = 100; + { + // write to segment and flush + Block block = DMTestEnv::prepareSimpleWriteBlock(0, num_rows_write_per_batch, false); + new_segment->write(dmContext(), std::move(block), true); + } + { + // write to segment and don't flush + Block block = DMTestEnv::prepareSimpleWriteBlock(num_rows_write_per_batch, 2 * num_rows_write_per_batch, false); + new_segment->write(dmContext(), std::move(block), false); + } + return new_segment->segmentId(); +} + +size_t SegmentTestBasic::getSegmentRowNumWithoutMVCC(PageId segment_id) +{ + auto segment = segments[segment_id]; + auto in = segment->getInputStreamRaw(dmContext(), *tableColumns()); + + size_t num_rows_read = 0; + in->readPrefix(); + while (Block block = in->read()) + { + num_rows_read += block.rows(); + } + in->readSuffix(); + return num_rows_read; +} + +size_t SegmentTestBasic::getSegmentRowNum(PageId segment_id) +{ + auto segment = segments[segment_id]; + auto in = segment->getInputStream(dmContext(), *tableColumns(), {segment->getRowKeyRange()}); + + size_t num_rows_read = 0; + in->readPrefix(); + while (Block block = in->read()) + { + num_rows_read += block.rows(); + } + in->readSuffix(); + return num_rows_read; +} + +void SegmentTestBasic::checkSegmentRow(PageId segment_id, size_t expected_row_num) +{ + auto segment = segments[segment_id]; + // read written data + auto in = segment->getInputStream(dmContext(), *tableColumns(), {segment->getRowKeyRange()}); + + size_t num_rows_read = 0; + in->readPrefix(); + while (Block block = in->read()) + { + num_rows_read += block.rows(); + } + in->readSuffix(); + ASSERT_EQ(num_rows_read, expected_row_num); +} + +std::optional SegmentTestBasic::splitSegment(PageId segment_id) +{ + auto origin_segment = segments[segment_id]; + size_t origin_segment_row_num = getSegmentRowNum(segment_id); + SegmentPtr segment, new_segment; + std::tie(segment, new_segment) = origin_segment->split(dmContext(), tableColumns()); + if (new_segment) + { + segments[new_segment->segmentId()] = new_segment; + segments[segment_id] = segment; + + EXPECT_EQ(origin_segment_row_num, getSegmentRowNum(segment_id) + getSegmentRowNum(new_segment->segmentId())); + return new_segment->segmentId(); + } + return std::nullopt; +} + +void SegmentTestBasic::mergeSegment(PageId left_segment_id, PageId right_segment_id) +{ + auto left_segment = segments[left_segment_id]; + auto right_segment = segments[right_segment_id]; + + size_t left_segment_row_num = getSegmentRowNum(left_segment_id); + size_t right_segment_row_num = getSegmentRowNum(right_segment_id); + LOG_FMT_TRACE(&Poco::Logger::root(), "merge in segment:{}:{} and {}:{}", left_segment->segmentId(), left_segment_row_num, right_segment->segmentId(), right_segment_row_num); + + SegmentPtr merged_segment = Segment::merge(dmContext(), tableColumns(), left_segment, right_segment); + segments[merged_segment->segmentId()] = merged_segment; + auto it = segments.find(right_segment->segmentId()); + if (it != segments.end()) + { + segments.erase(it); + } + EXPECT_EQ(getSegmentRowNum(merged_segment->segmentId()), left_segment_row_num + right_segment_row_num); +} + +void SegmentTestBasic::mergeSegmentDelta(PageId segment_id) +{ + auto segment = segments[segment_id]; + size_t segment_row_num = getSegmentRowNum(segment_id); + SegmentPtr merged_segment = segment->mergeDelta(dmContext(), tableColumns()); + segments[merged_segment->segmentId()] = merged_segment; + EXPECT_EQ(getSegmentRowNum(merged_segment->segmentId()), segment_row_num); +} + +void SegmentTestBasic::flushSegmentCache(PageId segment_id) +{ + auto segment = segments[segment_id]; + size_t segment_row_num = getSegmentRowNum(segment_id); + segment->flushCache(dmContext()); + EXPECT_EQ(getSegmentRowNum(segment_id), segment_row_num); +} + +std::pair SegmentTestBasic::getSegmentKeyRange(SegmentPtr segment) +{ + Int64 start_key, end_key; + if (!options.is_common_handle) + { + start_key = segment->getRowKeyRange().getStart().int_value; + end_key = segment->getRowKeyRange().getEnd().int_value; + return {start_key, end_key}; + } + EXPECT_EQ(segment->getRowKeyRange().getStart().data[0], TiDB::CodecFlagInt); + EXPECT_EQ(segment->getRowKeyRange().getEnd().data[0], TiDB::CodecFlagInt); + { + size_t cursor = 1; + start_key = DecodeInt64(cursor, String(segment->getRowKeyRange().getStart().data, segment->getRowKeyRange().getStart().size)); + } + { + size_t cursor = 1; + end_key = DecodeInt64(cursor, String(segment->getRowKeyRange().getEnd().data, segment->getRowKeyRange().getEnd().size)); + } + return {start_key, end_key}; +} + +void SegmentTestBasic::writeSegment(PageId segment_id, UInt64 write_rows) +{ + if (write_rows == 0) + { + return; + } + auto segment = segments[segment_id]; + size_t segment_row_num = getSegmentRowNumWithoutMVCC(segment_id); + std::pair keys = getSegmentKeyRange(segment); + Int64 start_key = keys.first; + Int64 end_key = keys.second; + UInt64 remain_row_num = 0; + if (static_cast(end_key - start_key) > write_rows) + { + end_key = start_key + write_rows; + } + else + { + remain_row_num = write_rows - static_cast(end_key - start_key); + } + { + // write to segment and not flush + Block block = DMTestEnv::prepareSimpleWriteBlock(start_key, end_key, false, version, DMTestEnv::pk_name, EXTRA_HANDLE_COLUMN_ID, options.is_common_handle ? EXTRA_HANDLE_COLUMN_STRING_TYPE : EXTRA_HANDLE_COLUMN_INT_TYPE, options.is_common_handle); + segment->write(dmContext(), std::move(block), false); + LOG_FMT_TRACE(&Poco::Logger::root(), "write key range [{}, {})", start_key, end_key); + version++; + } + while (remain_row_num > 0) + { + UInt64 write_num = std::min(remain_row_num, static_cast(end_key - start_key)); + Block block = DMTestEnv::prepareSimpleWriteBlock(start_key, write_num + start_key, false, version, DMTestEnv::pk_name, EXTRA_HANDLE_COLUMN_ID, options.is_common_handle ? EXTRA_HANDLE_COLUMN_STRING_TYPE : EXTRA_HANDLE_COLUMN_INT_TYPE, options.is_common_handle); + segment->write(dmContext(), std::move(block), false); + remain_row_num -= write_num; + LOG_FMT_TRACE(&Poco::Logger::root(), "write key range [{}, {})", start_key, write_num + start_key); + version++; + } + EXPECT_EQ(getSegmentRowNumWithoutMVCC(segment_id), segment_row_num + write_rows); +} + +void SegmentTestBasic::writeSegmentWithDeletedPack(PageId segment_id) +{ + UInt64 write_rows = DEFAULT_MERGE_BLOCK_SIZE; + auto segment = segments[segment_id]; + size_t segment_row_num = getSegmentRowNumWithoutMVCC(segment_id); + std::pair keys = getSegmentKeyRange(segment); + Int64 start_key = keys.first; + Int64 end_key = keys.second; + UInt64 remain_row_num = 0; + if (static_cast(end_key - start_key) > write_rows) + { + end_key = start_key + write_rows; + } + else + { + remain_row_num = write_rows - static_cast(end_key - start_key); + } + { + // write to segment and not flush + Block block = DMTestEnv::prepareSimpleWriteBlock(start_key, end_key, false, version, DMTestEnv::pk_name, EXTRA_HANDLE_COLUMN_ID, options.is_common_handle ? EXTRA_HANDLE_COLUMN_STRING_TYPE : EXTRA_HANDLE_COLUMN_INT_TYPE, options.is_common_handle, 1, true, true); + segment->write(dmContext(), std::move(block), true); + LOG_FMT_TRACE(&Poco::Logger::root(), "write key range [{}, {})", start_key, end_key); + version++; + } + while (remain_row_num > 0) + { + UInt64 write_num = std::min(remain_row_num, static_cast(end_key - start_key)); + Block block = DMTestEnv::prepareSimpleWriteBlock(start_key, write_num + start_key, false, version, DMTestEnv::pk_name, EXTRA_HANDLE_COLUMN_ID, options.is_common_handle ? EXTRA_HANDLE_COLUMN_STRING_TYPE : EXTRA_HANDLE_COLUMN_INT_TYPE, options.is_common_handle, 1, true, true); + segment->write(dmContext(), std::move(block), true); + remain_row_num -= write_num; + LOG_FMT_TRACE(&Poco::Logger::root(), "write key range [{}, {})", start_key, write_num + start_key); + version++; + } + EXPECT_EQ(getSegmentRowNumWithoutMVCC(segment_id), segment_row_num + write_rows); +} + +void SegmentTestBasic::deleteRangeSegment(PageId segment_id) +{ + auto segment = segments[segment_id]; + segment->write(dmContext(), /*delete_range*/ segment->getRowKeyRange()); + EXPECT_EQ(getSegmentRowNum(segment_id), 0); +} + +void SegmentTestBasic::writeRandomSegment() +{ + if (segments.empty()) + { + return; + } + PageId random_segment_id = getRandomSegmentId(); + LOG_FMT_TRACE(&Poco::Logger::root(), "start write segment:{}", random_segment_id); + writeSegment(random_segment_id); +} +void SegmentTestBasic::writeRandomSegmentWithDeletedPack() +{ + if (segments.empty()) + { + return; + } + PageId random_segment_id = getRandomSegmentId(); + LOG_FMT_TRACE(&Poco::Logger::root(), "start write segment with deleted pack:{}", random_segment_id); + writeSegmentWithDeletedPack(random_segment_id); +} + +void SegmentTestBasic::deleteRangeRandomSegment() +{ + if (segments.empty()) + { + return; + } + PageId random_segment_id = getRandomSegmentId(); + LOG_FMT_TRACE(&Poco::Logger::root(), "start delete range segment:{}", random_segment_id); + deleteRangeSegment(random_segment_id); +} + +void SegmentTestBasic::splitRandomSegment() +{ + if (segments.empty()) + { + return; + } + PageId random_segment_id = getRandomSegmentId(); + LOG_FMT_TRACE(&Poco::Logger::root(), "start split segment:{}", random_segment_id); + splitSegment(random_segment_id); +} + +void SegmentTestBasic::mergeRandomSegment() +{ + if (segments.empty() || segments.size() == 1) + { + return; + } + std::pair segment_pair; + segment_pair = getRandomMergeablePair(); + LOG_FMT_TRACE(&Poco::Logger::root(), "start merge segment:{} and {}", segment_pair.first, segment_pair.second); + mergeSegment(segment_pair.first, segment_pair.second); +} + +void SegmentTestBasic::mergeDeltaRandomSegment() +{ + if (segments.empty()) + { + return; + } + PageId random_segment_id = getRandomSegmentId(); + LOG_FMT_TRACE(&Poco::Logger::root(), "start merge delta in segment:{}", random_segment_id); + mergeSegmentDelta(random_segment_id); +} + +void SegmentTestBasic::flushCacheRandomSegment() +{ + if (segments.empty()) + { + return; + } + PageId random_segment_id = getRandomSegmentId(); + LOG_FMT_TRACE(&Poco::Logger::root(), "start flush cache in segment:{}", random_segment_id); + flushSegmentCache(random_segment_id); +} + +void SegmentTestBasic::randomSegmentTest(size_t operator_count) +{ + for (size_t i = 0; i < operator_count; i++) + { + auto op = static_cast(random() % SegmentOperaterMax); + segment_operator_entries[op](); + } +} + +PageId SegmentTestBasic::getRandomSegmentId() +{ + auto max_segment_id = segments.rbegin()->first; + PageId random_segment_id = random() % (max_segment_id + 1); + auto it = segments.find(random_segment_id); + while (it == segments.end()) + { + random_segment_id = random() % (max_segment_id + 1); + it = segments.find(random_segment_id); + } + return random_segment_id; +} + +std::pair SegmentTestBasic::getRandomMergeablePair() +{ + while (true) + { + PageId random_left_segment_id = getRandomSegmentId(); + PageId random_right_segment_id = random_left_segment_id; + while (random_right_segment_id == random_left_segment_id) + { + random_right_segment_id = getRandomSegmentId(); + } + auto left_segment = segments[random_left_segment_id]; + auto right_segment = segments[random_right_segment_id]; + if (compare(left_segment->getRowKeyRange().getEnd(), right_segment->getRowKeyRange().getStart()) != 0 || left_segment->nextSegmentId() != right_segment->segmentId()) + { + continue; + } + return {random_left_segment_id, random_right_segment_id}; + } +} + +RowKeyRange SegmentTestBasic::commanHandleKeyRange() +{ + String start_key, end_key; + { + WriteBufferFromOwnString ss; + ::DB::EncodeUInt(static_cast(TiDB::CodecFlagInt), ss); + ::DB::EncodeInt64(std::numeric_limits::min(), ss); + start_key = ss.releaseStr(); + } + { + WriteBufferFromOwnString ss; + ::DB::EncodeUInt(static_cast(TiDB::CodecFlagInt), ss); + ::DB::EncodeInt64(std::numeric_limits::max(), ss); + end_key = ss.releaseStr(); + } + return RowKeyRange(RowKeyValue(true, std::make_shared(start_key), 0), RowKeyValue(true, std::make_shared(end_key), 0), true, 1); +} + +SegmentPtr SegmentTestBasic::reload(bool is_common_handle, const ColumnDefinesPtr & pre_define_columns, DB::Settings && db_settings) +{ + TiFlashStorageTestBasic::reload(std::move(db_settings)); + storage_path_pool = std::make_unique(db_context->getPathPool().withTable("test", "t1", false)); + storage_pool = std::make_unique(*db_context, /*ns_id*/ 100, *storage_path_pool, "test.t1"); + storage_pool->restore(); + ColumnDefinesPtr cols = (!pre_define_columns) ? DMTestEnv::getDefaultColumns(is_common_handle ? DMTestEnv::PkType::CommonHandle : DMTestEnv::PkType::HiddenTiDBRowID) : pre_define_columns; + setColumns(cols); + + return Segment::newSegment(*dm_context, table_columns, is_common_handle ? commanHandleKeyRange() : RowKeyRange::newAll(is_common_handle, 1), storage_pool->newMetaPageId(), 0); +} + +void SegmentTestBasic::setColumns(const ColumnDefinesPtr & columns) +{ + *table_columns = *columns; + + dm_context = std::make_unique(*db_context, + *storage_path_pool, + *storage_pool, + 0, + /*min_version_*/ 0, + settings.not_compress_columns, + options.is_common_handle, + 1, + db_context->getSettingsRef()); +} +} // namespace tests +} // namespace DM +} // namespace DB diff --git a/dbms/src/Storages/DeltaMerge/tests/gtest_segment_test_basic.h b/dbms/src/Storages/DeltaMerge/tests/gtest_segment_test_basic.h new file mode 100644 index 00000000000..ab0c7d6d0be --- /dev/null +++ b/dbms/src/Storages/DeltaMerge/tests/gtest_segment_test_basic.h @@ -0,0 +1,123 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace DB +{ +namespace DM +{ +namespace tests +{ +class SegmentTestBasic : public DB::base::TiFlashStorageTestBasic +{ +public: + struct SegmentTestOptions + { + bool is_common_handle = false; + }; + +public: + void reloadWithOptions(SegmentTestOptions config); + + std::optional splitSegment(PageId segment_id); + void mergeSegment(PageId left_segment_id, PageId right_segment_id); + void mergeSegmentDelta(PageId segment_id); + void flushSegmentCache(PageId segment_id); + void writeSegment(PageId segment_id, UInt64 write_rows = 100); + void writeSegmentWithDeletedPack(PageId segment_id); + void deleteRangeSegment(PageId segment_id); + + + void writeRandomSegment(); + void writeRandomSegmentWithDeletedPack(); + void deleteRangeRandomSegment(); + void splitRandomSegment(); + void mergeRandomSegment(); + void mergeDeltaRandomSegment(); + void flushCacheRandomSegment(); + + void randomSegmentTest(size_t operator_count); + + PageId createNewSegmentWithSomeData(); + size_t getSegmentRowNumWithoutMVCC(PageId segment_id); + size_t getSegmentRowNum(PageId segment_id); + void checkSegmentRow(PageId segment_id, size_t expected_row_num); + std::pair getSegmentKeyRange(SegmentPtr segment); + +protected: + // + std::map segments; + + enum SegmentOperaterType + { + Write = 0, + DeleteRange, + Split, + Merge, + MergeDelta, + FlushCache, + WriteDeletedPack, + SegmentOperaterMax + }; + + const std::vector> segment_operator_entries = { + [this] { writeRandomSegment(); }, + [this] { deleteRangeRandomSegment(); }, + [this] { splitRandomSegment(); }, + [this] { mergeRandomSegment(); }, + [this] { mergeDeltaRandomSegment(); }, + [this] { flushCacheRandomSegment(); }, + [this] { + writeRandomSegmentWithDeletedPack(); + }}; + + PageId getRandomSegmentId(); + + std::pair getRandomMergeablePair(); + + RowKeyRange commanHandleKeyRange(); + + SegmentPtr reload(bool is_common_handle, const ColumnDefinesPtr & pre_define_columns = {}, DB::Settings && db_settings = DB::Settings()); + + // setColumns should update dm_context at the same time + void setColumns(const ColumnDefinesPtr & columns); + + const ColumnDefinesPtr & tableColumns() const { return table_columns; } + + DMContext & dmContext() { return *dm_context; } + +protected: + /// all these var lives as ref in dm_context + std::unique_ptr storage_path_pool; + std::unique_ptr storage_pool; + /// dm_context + std::unique_ptr dm_context; + ColumnDefinesPtr table_columns; + DM::DeltaMergeStore::Settings settings; + + SegmentPtr root_segment; + UInt64 version = 0; + SegmentTestOptions options; +}; +} // namespace tests +} // namespace DM +} // namespace DB \ No newline at end of file From 640c1033cdfe29264df722c0a512ecd4b32c4de6 Mon Sep 17 00:00:00 2001 From: hehechen Date: Wed, 22 Jun 2022 18:58:37 +0800 Subject: [PATCH 124/127] optimize ps v3 restore (#5163) ref pingcap/tiflash#4914 --- dbms/src/Storages/Page/V3/PageDirectory.cpp | 20 ++++++++++++------- dbms/src/Storages/Page/V3/PageDirectory.h | 8 +++++--- .../Storages/Page/V3/PageDirectoryFactory.cpp | 6 ++++-- .../Page/V3/tests/gtest_page_directory.cpp | 4 ++-- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/dbms/src/Storages/Page/V3/PageDirectory.cpp b/dbms/src/Storages/Page/V3/PageDirectory.cpp index 5eb275f5af5..951da42de1c 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectory.cpp @@ -478,7 +478,7 @@ PageSize VersionedPageEntries::getEntriesByBlobIds( bool VersionedPageEntries::cleanOutdatedEntries( UInt64 lowest_seq, std::map> * normal_entries_to_deref, - PageEntriesV3 & entries_removed, + PageEntriesV3 * entries_removed, const PageLock & /*page_lock*/) { if (type == EditRecordType::VAR_EXTERNAL) @@ -541,7 +541,10 @@ bool VersionedPageEntries::cleanOutdatedEntries( { if (iter->second.being_ref_count == 1) { - entries_removed.emplace_back(iter->second.entry); + if (entries_removed) + { + entries_removed->emplace_back(iter->second.entry); + } iter = entries.erase(iter); } // The `being_ref_count` for this version is valid. While for older versions, @@ -551,7 +554,10 @@ bool VersionedPageEntries::cleanOutdatedEntries( else { // else there are newer "entry" in the version list, the outdated entries should be removed - entries_removed.emplace_back(iter->second.entry); + if (entries_removed) + { + entries_removed->emplace_back(iter->second.entry); + } iter = entries.erase(iter); } } @@ -564,7 +570,7 @@ bool VersionedPageEntries::cleanOutdatedEntries( return entries.empty() || (entries.size() == 1 && entries.begin()->second.isDelete()); } -bool VersionedPageEntries::derefAndClean(UInt64 lowest_seq, PageIdV3Internal page_id, const PageVersion & deref_ver, const Int64 deref_count, PageEntriesV3 & entries_removed) +bool VersionedPageEntries::derefAndClean(UInt64 lowest_seq, PageIdV3Internal page_id, const PageVersion & deref_ver, const Int64 deref_count, PageEntriesV3 * entries_removed) { auto page_lock = acquireLock(); if (type == EditRecordType::VAR_EXTERNAL) @@ -1239,7 +1245,7 @@ bool PageDirectory::tryDumpSnapshot(const ReadLimiterPtr & read_limiter, const W return done_any_io; } -PageEntriesV3 PageDirectory::gcInMemEntries() +PageEntriesV3 PageDirectory::gcInMemEntries(bool return_removed_entries) { UInt64 lowest_seq = sequence.load(); @@ -1303,7 +1309,7 @@ PageEntriesV3 PageDirectory::gcInMemEntries() const bool all_deleted = iter->second->cleanOutdatedEntries( lowest_seq, &normal_entries_to_deref, - all_del_entries, + return_removed_entries ? &all_del_entries : nullptr, iter->second->acquireLock()); { @@ -1342,7 +1348,7 @@ PageEntriesV3 PageDirectory::gcInMemEntries() page_id, /*deref_ver=*/deref_counter.first, /*deref_count=*/deref_counter.second, - all_del_entries); + return_removed_entries ? &all_del_entries : nullptr); if (all_deleted) { diff --git a/dbms/src/Storages/Page/V3/PageDirectory.h b/dbms/src/Storages/Page/V3/PageDirectory.h index bd7c433022f..2f0f09f4e42 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.h +++ b/dbms/src/Storages/Page/V3/PageDirectory.h @@ -223,14 +223,14 @@ class VersionedPageEntries bool cleanOutdatedEntries( UInt64 lowest_seq, std::map> * normal_entries_to_deref, - PageEntriesV3 & entries_removed, + PageEntriesV3 * entries_removed, const PageLock & page_lock); bool derefAndClean( UInt64 lowest_seq, PageIdV3Internal page_id, const PageVersion & deref_ver, Int64 deref_count, - PageEntriesV3 & entries_removed); + PageEntriesV3 * entries_removed); void collapseTo(UInt64 seq, PageIdV3Internal page_id, PageEntriesEdit & edit); @@ -360,7 +360,9 @@ class PageDirectory bool tryDumpSnapshot(const ReadLimiterPtr & read_limiter = nullptr, const WriteLimiterPtr & write_limiter = nullptr); - PageEntriesV3 gcInMemEntries(); + // Perform a GC for in-memory entries and return the removed entries. + // If `return_removed_entries` is false, then just return an empty set. + PageEntriesV3 gcInMemEntries(bool return_removed_entries = true); std::set getAliveExternalIds(NamespaceId ns_id) const; diff --git a/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp b/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp index 483c5073ab5..968049a3273 100644 --- a/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp @@ -44,7 +44,8 @@ PageDirectoryPtr PageDirectoryFactory::createFromReader(String storage_name, WAL // After restoring from the disk, we need cleanup all invalid entries in memory, or it will // try to run GC again on some entries that are already marked as invalid in BlobStore. - dir->gcInMemEntries(); + // It's no need to remove the expired entries in BlobStore, so skip filling removed_entries to imporve performance. + dir->gcInMemEntries(/*return_removed_entries=*/false); LOG_FMT_INFO(DB::Logger::get("PageDirectoryFactory", storage_name), "PageDirectory restored [max_page_id={}] [max_applied_ver={}]", dir->getMaxId(), dir->sequence); if (blob_stats) @@ -84,7 +85,8 @@ PageDirectoryPtr PageDirectoryFactory::createFromEdit(String storage_name, FileP // After restoring from the disk, we need cleanup all invalid entries in memory, or it will // try to run GC again on some entries that are already marked as invalid in BlobStore. - dir->gcInMemEntries(); + // It's no need to remove the expired entries in BlobStore when restore, so no need to fill removed_entries. + dir->gcInMemEntries(/*return_removed_entries=*/false); if (blob_stats) { diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp index 83e07f75d37..6d6ef41630f 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp @@ -644,14 +644,14 @@ class VersionedEntriesTest : public ::testing::Test { DerefCounter deref_counter; PageEntriesV3 removed_entries; - bool all_removed = entries.cleanOutdatedEntries(seq, &deref_counter, removed_entries, entries.acquireLock()); + bool all_removed = entries.cleanOutdatedEntries(seq, &deref_counter, &removed_entries, entries.acquireLock()); return {all_removed, removed_entries, deref_counter}; } std::tuple runDeref(UInt64 seq, PageVersion ver, Int64 decrease_num) { PageEntriesV3 removed_entries; - bool all_removed = entries.derefAndClean(seq, buildV3Id(TEST_NAMESPACE_ID, page_id), ver, decrease_num, removed_entries); + bool all_removed = entries.derefAndClean(seq, buildV3Id(TEST_NAMESPACE_ID, page_id), ver, decrease_num, &removed_entries); return {all_removed, removed_entries}; } From 69cbfdf8a6bfb1d98ac76dea6e70d87ab3a1ed84 Mon Sep 17 00:00:00 2001 From: Shenghui Wu <793703860@qq.com> Date: Thu, 23 Jun 2022 09:52:36 +0800 Subject: [PATCH 125/127] Fix build failed (#5196) close pingcap/tiflash#5195 --- dbms/src/Common/FailPoint.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dbms/src/Common/FailPoint.cpp b/dbms/src/Common/FailPoint.cpp index 1dff46c273b..ad5010d7826 100644 --- a/dbms/src/Common/FailPoint.cpp +++ b/dbms/src/Common/FailPoint.cpp @@ -272,9 +272,9 @@ void FailPointHelper::disableFailPoint(const String &) {} void FailPointHelper::wait(const String &) {} -void FailPointHelper::initRandomFailPoints(Poco::Util::LayeredConfiguration & config, Poco::Logger * log) {} +void FailPointHelper::initRandomFailPoints(Poco::Util::LayeredConfiguration &, Poco::Logger *) {} -void FailPointHelper::enableRandomFailPoint(const String & fail_point_name, double rate) {} +void FailPointHelper::enableRandomFailPoint(const String &, double) {} #endif } // namespace DB From dab31a5e786fce7e2a064977df411a024ea55d6e Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Fri, 24 Jun 2022 20:32:37 +0800 Subject: [PATCH 126/127] feat: delta tree dispatching (#5199) close pingcap/tiflash#5200 --- dbms/src/Storages/DeltaMerge/DeltaTree.h | 223 ++++++++------------- dbms/src/Storages/DeltaMerge/DeltaTree.ipp | 165 +++++++++++++++ 2 files changed, 248 insertions(+), 140 deletions(-) create mode 100644 dbms/src/Storages/DeltaMerge/DeltaTree.ipp diff --git a/dbms/src/Storages/DeltaMerge/DeltaTree.h b/dbms/src/Storages/DeltaMerge/DeltaTree.h index 47674ab2cfc..29e127fe35f 100644 --- a/dbms/src/Storages/DeltaMerge/DeltaTree.h +++ b/dbms/src/Storages/DeltaMerge/DeltaTree.h @@ -14,6 +14,7 @@ #pragma once +#include #include #include #include @@ -810,6 +811,20 @@ class DeltaTree template InternPtr afterNodeUpdated(T * node); +#ifdef __x86_64__ + template + InternPtr afterNodeUpdatedGeneric(T * node); + + template + InternPtr afterNodeUpdatedAVX512(T * node); + + template + InternPtr afterNodeUpdatedAVX(T * node); + + template + InternPtr afterNodeUpdatedSSE4(T * node); +#endif + inline void afterLeafUpdated(LeafPtr leaf) { if (leaf->count == 0 && isRootOnly()) @@ -1348,158 +1363,86 @@ typename DT_CLASS::InterAndSid DT_CLASS::submitMinSid(T * node, UInt64 subtree_m } } -DT_TEMPLATE -template -typename DT_CLASS::InternPtr DT_CLASS::afterNodeUpdated(T * node) +#ifndef __x86_64__ +#define TIFLASH_DT_IMPL_NAME afterNodeUpdated +#include "DeltaTree.ipp" +#undef TIFLASH_DT_IMPL_NAME +#else + +// generic implementation +#define TIFLASH_DT_IMPL_NAME afterNodeUpdatedGeneric +#include "DeltaTree.ipp" +#undef TIFLASH_DT_IMPL_NAME + +// avx512 implementation +TIFLASH_BEGIN_AVX512_SPECIFIC_CODE +#define TIFLASH_DT_IMPL_NAME afterNodeUpdatedAVX512 +#include "DeltaTree.ipp" +#undef TIFLASH_DT_IMPL_NAME +TIFLASH_END_TARGET_SPECIFIC_CODE + +// avx implementation +TIFLASH_BEGIN_AVX_SPECIFIC_CODE +#define TIFLASH_DT_IMPL_NAME afterNodeUpdatedAVX +#include "DeltaTree.ipp" +#undef TIFLASH_DT_IMPL_NAME +TIFLASH_END_TARGET_SPECIFIC_CODE + +// sse4 implementation +TIFLASH_BEGIN_SSE4_SPECIFIC_CODE +#define TIFLASH_DT_IMPL_NAME afterNodeUpdatedSSE4 +#include "DeltaTree.ipp" +#undef TIFLASH_DT_IMPL_NAME +TIFLASH_END_TARGET_SPECIFIC_CODE + +namespace Impl { - if (!node) - return {}; - - constexpr bool is_leaf = std::is_same::value; +enum class DeltaTreeVariant +{ + Generic, + SSE4, + AVX, + AVX512 +}; - if (root == asNode(node) && !isLeaf(root) && node->count == 1) +static inline DeltaTreeVariant resolveDeltaTreeVariant() +{ + if (DB::TargetSpecific::AVX512Checker::runtimeSupport()) { - /// Decrease tree height. - root = as(Intern, root)->children[0]; - - --(node->count); - freeNode(node); - - if (isLeaf(root)) - as(Leaf, root)->parent = nullptr; - else - as(Intern, root)->parent = nullptr; - --height; - - LOG_FMT_TRACE(log, "height {} -> {}", (height + 1), height); - - return {}; + return DeltaTreeVariant::AVX512; } - - auto parent = node->parent; - bool parent_updated = false; - - if (T::overflow(node->count)) // split + if (DB::TargetSpecific::AVXChecker::runtimeSupport()) { - if (!parent) - { - /// Increase tree height. - parent = createNode(); - root = asNode(parent); - - parent->deltas[0] = checkDelta(node->getDelta()); - parent->children[0] = asNode(node); - ++(parent->count); - parent->refreshChildParent(); - - ++height; - - LOG_FMT_TRACE(log, "height {} -> {}", (height - 1), height); - } - - auto pos = parent->searchChild(asNode(node)); - - T * next_n = createNode(); - - UInt64 sep_sid = node->split(next_n); - - // handle parent update - parent->shiftEntries(pos + 1, 1); - // for current node - parent->deltas[pos] = checkDelta(node->getDelta()); - // for next node - parent->sids[pos] = sep_sid; - parent->deltas[pos + 1] = checkDelta(next_n->getDelta()); - parent->children[pos + 1] = asNode(next_n); - - ++(parent->count); - - if constexpr (is_leaf) - { - if (as(Leaf, node) == right_leaf) - right_leaf = as(Leaf, next_n); - } - - parent_updated = true; + return DeltaTreeVariant::AVX; } - else if (T::underflow(node->count) && root != asNode(node)) // adopt or merge + if (DB::TargetSpecific::SSE4Checker::runtimeSupport()) { - auto pos = parent->searchChild(asNode(node)); - - // currently we always adopt from the right one if possible - bool is_sibling_left; - size_t sibling_pos; - T * sibling; - - if (unlikely(parent->count <= 1)) - throw Exception("Unexpected parent entry count: " + DB::toString(parent->count)); - - if (pos == parent->count - 1) - { - is_sibling_left = true; - sibling_pos = pos - 1; - sibling = as(T, parent->children[sibling_pos]); - } - else - { - is_sibling_left = false; - sibling_pos = pos + 1; - sibling = as(T, parent->children[sibling_pos]); - } - - if (unlikely(sibling->parent != node->parent)) - throw Exception("parent not the same"); - - auto after_adopt = (node->count + sibling->count) / 2; - if (T::underflow(after_adopt)) - { - // Do merge. - // adoption won't work because the sibling doesn't have enough entries. - - node->merge(sibling, is_sibling_left, pos); - freeNode(sibling); - - pos = std::min(pos, sibling_pos); - parent->deltas[pos] = checkDelta(node->getDelta()); - parent->children[pos] = asNode(node); - parent->shiftEntries(pos + 2, -1); - - if constexpr (is_leaf) - { - if (is_sibling_left && (as(Leaf, sibling) == left_leaf)) - left_leaf = as(Leaf, node); - else if (!is_sibling_left && as(Leaf, sibling) == right_leaf) - right_leaf = as(Leaf, node); - } - --(parent->count); - } - else - { - // Do adoption. - - auto adopt_count = after_adopt - node->count; - auto new_sep_sid = node->adopt(sibling, is_sibling_left, adopt_count, pos); + return DeltaTreeVariant::SSE4; + } + return DeltaTreeVariant::Generic; +} - parent->sids[std::min(pos, sibling_pos)] = new_sep_sid; - parent->deltas[pos] = checkDelta(node->getDelta()); - parent->deltas[sibling_pos] = checkDelta(sibling->getDelta()); - } +static inline DeltaTreeVariant DELTA_TREE_VARIANT = resolveDeltaTreeVariant(); +} // namespace Impl - parent_updated = true; - } - else if (parent) +DT_TEMPLATE +template +typename DT_CLASS::InternPtr DT_CLASS::afterNodeUpdated(T * node) +{ + switch (Impl::DELTA_TREE_VARIANT) { - auto pos = parent->searchChild(asNode(node)); - auto delta = node->getDelta(); - parent_updated = parent->deltas[pos] != delta; - parent->deltas[pos] = checkDelta(delta); + case Impl::DeltaTreeVariant::Generic: + return afterNodeUpdatedGeneric(node); + case Impl::DeltaTreeVariant::SSE4: + return afterNodeUpdatedSSE4(node); + case Impl::DeltaTreeVariant::AVX: + return afterNodeUpdatedAVX(node); + case Impl::DeltaTreeVariant::AVX512: + return afterNodeUpdatedAVX512(node); } - - if (parent_updated) - return parent; - else - return {}; } +#endif + #undef as #undef asNode diff --git a/dbms/src/Storages/DeltaMerge/DeltaTree.ipp b/dbms/src/Storages/DeltaMerge/DeltaTree.ipp new file mode 100644 index 00000000000..27b8a3b96f1 --- /dev/null +++ b/dbms/src/Storages/DeltaMerge/DeltaTree.ipp @@ -0,0 +1,165 @@ +// Copyright 2022 PingCAP, Ltd. +// +// 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. +DT_TEMPLATE +template +__attribute__((noinline, flatten)) typename DT_CLASS::InternPtr DT_CLASS::TIFLASH_DT_IMPL_NAME(T * node) +{ + if (!node) + return {}; + + constexpr bool is_leaf = std::is_same::value; + + if (root == asNode(node) && !isLeaf(root) && node->count == 1) + { + /// Decrease tree height. + root = as(Intern, root)->children[0]; + + --(node->count); + freeNode(node); + + if (isLeaf(root)) + as(Leaf, root)->parent = nullptr; + else + as(Intern, root)->parent = nullptr; + --height; + + LOG_FMT_TRACE(log, "height {} -> {}", (height + 1), height); + + return {}; + } + + auto parent = node->parent; + bool parent_updated = false; + + if (T::overflow(node->count)) // split + { + if (!parent) + { + /// Increase tree height. + parent = createNode(); + root = asNode(parent); + + parent->deltas[0] = checkDelta(node->getDelta()); + parent->children[0] = asNode(node); + ++(parent->count); + parent->refreshChildParent(); + + ++height; + + LOG_FMT_TRACE(log, "height {} -> {}", (height - 1), height); + } + + auto pos = parent->searchChild(asNode(node)); + + T * next_n = createNode(); + + UInt64 sep_sid = node->split(next_n); + + // handle parent update + parent->shiftEntries(pos + 1, 1); + // for current node + parent->deltas[pos] = checkDelta(node->getDelta()); + // for next node + parent->sids[pos] = sep_sid; + parent->deltas[pos + 1] = checkDelta(next_n->getDelta()); + parent->children[pos + 1] = asNode(next_n); + + ++(parent->count); + + if constexpr (is_leaf) + { + if (as(Leaf, node) == right_leaf) + right_leaf = as(Leaf, next_n); + } + + parent_updated = true; + } + else if (T::underflow(node->count) && root != asNode(node)) // adopt or merge + { + auto pos = parent->searchChild(asNode(node)); + + // currently we always adopt from the right one if possible + bool is_sibling_left; + size_t sibling_pos; + T * sibling; + + if (unlikely(parent->count <= 1)) + throw Exception("Unexpected parent entry count: " + DB::toString(parent->count)); + + if (pos == parent->count - 1) + { + is_sibling_left = true; + sibling_pos = pos - 1; + sibling = as(T, parent->children[sibling_pos]); + } + else + { + is_sibling_left = false; + sibling_pos = pos + 1; + sibling = as(T, parent->children[sibling_pos]); + } + + if (unlikely(sibling->parent != node->parent)) + throw Exception("parent not the same"); + + auto after_adopt = (node->count + sibling->count) / 2; + if (T::underflow(after_adopt)) + { + // Do merge. + // adoption won't work because the sibling doesn't have enough entries. + + node->merge(sibling, is_sibling_left, pos); + freeNode(sibling); + + pos = std::min(pos, sibling_pos); + parent->deltas[pos] = checkDelta(node->getDelta()); + parent->children[pos] = asNode(node); + parent->shiftEntries(pos + 2, -1); + + if constexpr (is_leaf) + { + if (is_sibling_left && (as(Leaf, sibling) == left_leaf)) + left_leaf = as(Leaf, node); + else if (!is_sibling_left && as(Leaf, sibling) == right_leaf) + right_leaf = as(Leaf, node); + } + --(parent->count); + } + else + { + // Do adoption. + + auto adopt_count = after_adopt - node->count; + auto new_sep_sid = node->adopt(sibling, is_sibling_left, adopt_count, pos); + + parent->sids[std::min(pos, sibling_pos)] = new_sep_sid; + parent->deltas[pos] = checkDelta(node->getDelta()); + parent->deltas[sibling_pos] = checkDelta(sibling->getDelta()); + } + + parent_updated = true; + } + else if (parent) + { + auto pos = parent->searchChild(asNode(node)); + auto delta = node->getDelta(); + parent_updated = parent->deltas[pos] != delta; + parent->deltas[pos] = checkDelta(delta); + } + + if (parent_updated) + return parent; + else + return {}; +} \ No newline at end of file From 73e708cd22b935ca240a236a87e261aabddd770e Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Fri, 24 Jun 2022 21:12:37 +0800 Subject: [PATCH 127/127] feat: introduce specialized API to write fixed length data rapidly (#5181) close pingcap/tiflash#5183 --- dbms/src/Flash/Coprocessor/TiDBColumn.cpp | 8 ++++---- dbms/src/IO/WriteBuffer.h | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/dbms/src/Flash/Coprocessor/TiDBColumn.cpp b/dbms/src/Flash/Coprocessor/TiDBColumn.cpp index 7183374a5c1..eef89696d3a 100644 --- a/dbms/src/Flash/Coprocessor/TiDBColumn.cpp +++ b/dbms/src/Flash/Coprocessor/TiDBColumn.cpp @@ -28,7 +28,7 @@ template void encodeLittleEndian(const T & value, WriteBuffer & ss) { auto v = toLittleEndian(value); - ss.write(reinterpret_cast(&v), sizeof(v)); + ss.template writeFixed(&v); } TiDBColumn::TiDBColumn(Int8 element_len_) @@ -141,10 +141,10 @@ void TiDBColumn::append(const TiDBDecimal & decimal) encodeLittleEndian(decimal.digits_int, *data); encodeLittleEndian(decimal.digits_frac, *data); encodeLittleEndian(decimal.result_frac, *data); - encodeLittleEndian((UInt8)decimal.negative, *data); - for (int i = 0; i < MAX_WORD_BUF_LEN; i++) + encodeLittleEndian(static_cast(decimal.negative), *data); + for (int i : decimal.word_buf) { - encodeLittleEndian(decimal.word_buf[i], *data); + encodeLittleEndian(i, *data); } finishAppendFixed(); } diff --git a/dbms/src/IO/WriteBuffer.h b/dbms/src/IO/WriteBuffer.h index 361081d1176..0c0fa2cb545 100644 --- a/dbms/src/IO/WriteBuffer.h +++ b/dbms/src/IO/WriteBuffer.h @@ -96,6 +96,24 @@ class WriteBuffer : public BufferBase } } + template + __attribute__((always_inline)) void writeFixed(const T * __restrict from) + { + if (likely(working_buffer.end() - pos >= static_cast(sizeof(T)))) + { + tiflash_compiler_builtin_memcpy(pos, from, sizeof(T)); + pos += sizeof(T); + } + else + { + [&]() __attribute__((noinline)) + { + write(reinterpret_cast(from), sizeof(T)); + } + (); + } + } + inline void write(char x) {