diff --git a/ydb/core/tablet_flat/flat_page_btree_index.h b/ydb/core/tablet_flat/flat_page_btree_index.h index 515d04a08145..ee8461f85a37 100644 --- a/ydb/core/tablet_flat/flat_page_btree_index.h +++ b/ydb/core/tablet_flat/flat_page_btree_index.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "flat_page_base.h" #include "flat_page_label.h" @@ -282,6 +283,23 @@ namespace NKikimr::NTable::NPage { return Count() > 0; } + void Describe(IOutputStream& out, const TKeyCellDefaults& keyDefaults) const + { + out << '{'; + + auto iter = Iter(); + for (TPos pos : xrange(iter.Count())) { + if (pos != 0) { + out << ", "; + } + TString value; + DbgPrintValue(value, iter.Next(), keyDefaults.BasicTypes()[pos]); + out << value; + } + + out << '}'; + } + private: const TIsNullBitmap* IsNullBitmap; TColumns Columns; diff --git a/ydb/core/tablet_flat/flat_part_dump.cpp b/ydb/core/tablet_flat/flat_part_dump.cpp index e1bdd4e253d4..8fa28bca7f6b 100644 --- a/ydb/core/tablet_flat/flat_part_dump.cpp +++ b/ydb/core/tablet_flat/flat_part_dump.cpp @@ -281,7 +281,7 @@ namespace { void TDump::Key(TCellsRef key, const TPartScheme &scheme) noexcept { - Out << "("; + Out << "{"; for (auto off : xrange(key.size())) { TString str; @@ -291,7 +291,7 @@ namespace { Out << (off ? ", " : "") << str; } - Out << ")"; + Out << "}"; } void TDump::BTreeIndexNode(const TPart &part, NPage::TBtreeIndexNode::TChild meta, ui32 level) noexcept diff --git a/ydb/core/tablet_flat/flat_part_slice.cpp b/ydb/core/tablet_flat/flat_part_slice.cpp index 950234122939..c828a19c0ac9 100644 --- a/ydb/core/tablet_flat/flat_part_slice.cpp +++ b/ydb/core/tablet_flat/flat_part_slice.cpp @@ -183,7 +183,7 @@ void TSlice::Describe(IOutputStream& out) const noexcept { out << (FirstInclusive ? '[' : '('); out << FirstRowId; - out << ','; + out << ", "; if (LastRowId != Max()) { out << LastRowId; } else { @@ -192,6 +192,15 @@ void TSlice::Describe(IOutputStream& out) const noexcept out << (LastInclusive ? ']' : ')'); } +void TSlice::Describe(IOutputStream& out, const TKeyCellDefaults& keyDefaults) const +{ + out << "{rows: "; + Describe(out); + out << " keys: "; + TBounds::Describe(out, keyDefaults); + out << "}"; +} + void TSlices::Describe(IOutputStream& out) const noexcept { bool first = true; @@ -206,6 +215,20 @@ void TSlices::Describe(IOutputStream& out) const noexcept out << (first ? "}" : " }"); } +void TSlices::Describe(IOutputStream& out, const TKeyCellDefaults& keyDefaults) const +{ + bool first = true; + out << "{ "; + for (const auto& bounds : *this) { + if (first) + first = false; + else + out << ", "; + bounds.Describe(out, keyDefaults); + } + out << (first ? "}" : " }"); +} + void TSlices::Validate() const noexcept { TRowId lastEnd = 0; diff --git a/ydb/core/tablet_flat/flat_part_slice.h b/ydb/core/tablet_flat/flat_part_slice.h index 3fa7903bd222..bf07f773bdf4 100644 --- a/ydb/core/tablet_flat/flat_part_slice.h +++ b/ydb/core/tablet_flat/flat_part_slice.h @@ -133,6 +133,7 @@ namespace NTable { } void Describe(IOutputStream& out) const noexcept; + void Describe(IOutputStream& out, const TKeyCellDefaults& keyDefaults) const; /** * Returns true if first row of a is less than first row of b @@ -328,6 +329,8 @@ namespace NTable { void Describe(IOutputStream& out) const noexcept; + void Describe(IOutputStream& out, const TKeyCellDefaults& keyDefaults) const; + /** * Validate slices are correct, crash otherwise */ diff --git a/ydb/core/tablet_flat/flat_part_writer.h b/ydb/core/tablet_flat/flat_part_writer.h index bef4e44a5176..8a89b54da96e 100644 --- a/ydb/core/tablet_flat/flat_part_writer.h +++ b/ydb/core/tablet_flat/flat_part_writer.h @@ -969,7 +969,7 @@ namespace NTable { TPos it; for (it = 0; it < Key.size(); it++) { - if (int cmp = CompareTypedCells(PrevPageLastKey[it], Key[it], layout.KeyTypes[it])) { + if (CompareTypedCells(PrevPageLastKey[it], Key[it], layout.KeyTypes[it]) != 0) { break; } } diff --git a/ydb/core/tablet_flat/flat_stat_table_btree_index_histogram.cpp b/ydb/core/tablet_flat/flat_stat_table_btree_index_histogram.cpp index 51c1f957a377..13357fc23d72 100644 --- a/ydb/core/tablet_flat/flat_stat_table_btree_index_histogram.cpp +++ b/ydb/core/tablet_flat/flat_stat_table_btree_index_histogram.cpp @@ -48,7 +48,7 @@ class TTableHistogramBuilderBtreeIndex { { } - TString ToString() const noexcept { + TString ToString(const TKeyCellDefaults &keyDefaults) const { return TStringBuilder() << "Part: " << Part->Label.ToString() << " PageId: " << PageId @@ -57,8 +57,8 @@ class TTableHistogramBuilderBtreeIndex { << " EndRowId: " << EndRowId << " BeginDataSize: " << BeginDataSize << " EndDataSize: " << EndDataSize - << " BeginKey: " << BeginKey.Count() - << " EndKey: " << EndKey.Count() + << " BeginKey: " << NFmt::Do(BeginKey, keyDefaults) + << " EndKey: " << NFmt::Do(EndKey, keyDefaults) << " State: " << (ui32)State; } @@ -130,15 +130,17 @@ class TTableHistogramBuilderBtreeIndex { }; struct TEvent { - TCellsIterable Key; - bool IsBegin; TNodeState* Node; + bool IsBegin; - TString ToString() const noexcept { + TString ToString(const TKeyCellDefaults &keyDefaults) const { return TStringBuilder() - << Node->ToString() - << " IsBegin: " << IsBegin - << " Key: " << Key.Count(); + << "IsBegin: " << IsBegin + << " " << Node->ToString(keyDefaults); + } + + const TCellsIterable& GetKey() const { + return IsBegin ? Node->BeginKey : Node->EndKey; } }; @@ -149,7 +151,7 @@ class TTableHistogramBuilderBtreeIndex { return Compare(a, b) > 0; } - i8 Compare(const TEvent& a, const TEvent& b) const noexcept { + int Compare(const TEvent& a, const TEvent& b) const { // events go in order: // - Key = {}, IsBegin = true // - ... @@ -161,13 +163,16 @@ class TTableHistogramBuilderBtreeIndex { // - ... // - Key = {}, IsBegin = false - if (a.Key && b.Key) { // compare by keys - auto cmp = CompareKeys(a.Key, b.Key, KeyDefaults); + // end goes before begin in order to + // close previous node before open the next one + + if (a.GetKey() && b.GetKey()) { // compare by keys + auto cmp = CompareKeys(a.GetKey(), b.GetKey(), KeyDefaults); if (cmp != 0) { return cmp; } // keys are the same, compare by begin flag, end events first: - return Compare(a.IsBegin ? 1 : -1, b.IsBegin ? 1 : -1); + return Compare(a.IsBegin ? +1 : -1, b.IsBegin ? +1 : -1); } // category = -1 for Key = { }, IsBegin = true @@ -177,14 +182,14 @@ class TTableHistogramBuilderBtreeIndex { } private: - static i8 GetCategory(const TEvent& a) noexcept { - if (a.Key) { + static int GetCategory(const TEvent& a) { + if (a.GetKey()) { return 0; } return a.IsBegin ? -1 : +1; } - static i8 Compare(i8 a, i8 b) noexcept { + static int Compare(int a, int b) { if (a < b) return -1; if (a > b) return +1; return 0; @@ -226,6 +231,9 @@ class TTableHistogramBuilderBtreeIndex { for (auto index : xrange(Subset.Flatten.size())) { auto& part = Subset.Flatten[index]; + if (part.Slices) { + LOG_BUILD_STATS("slicing part " << part->Label << ": " << NFmt::Do(*part.Slices, KeyDefaults)); + } auto& meta = part->IndexPages.GetBTree({}); TCellsIterable beginKey = EmptyKey; if (part.Slices && part.Slices->front().FirstKey.GetCells()) { @@ -235,7 +243,7 @@ class TTableHistogramBuilderBtreeIndex { if (part.Slices && part.Slices->back().LastKey.GetCells()) { endKey = MakeCellsIterableKey(part.Part.Get(), part.Slices->back().LastKey); } - LoadedStateNodes.emplace_back(part.Part.Get(), meta.GetPageId(), meta.LevelCount, 0, meta.GetRowCount(), 0, meta.GetDataSize(), beginKey, endKey); + LoadedStateNodes.emplace_back(part.Part.Get(), meta.GetPageId(), meta.LevelCount, 0, meta.GetRowCount(), 0, meta.GetTotalDataSize(), beginKey, endKey); ready &= SlicePart(*part.Slices, LoadedStateNodes.back()); } @@ -261,13 +269,13 @@ class TTableHistogramBuilderBtreeIndex { if (it == slices.end() || node.EndRowId <= it->BeginRowId() || it->EndRowId() <= node.BeginRowId) { // skip the node - LOG_BUILD_STATS("slicing node " << node.ToString() << " => skip"); + LOG_BUILD_STATS("slicing node " << node.ToString(KeyDefaults) << " => skip"); return true; } if (it->BeginRowId() <= node.BeginRowId && node.EndRowId <= it->EndRowId()) { // take the node - LOG_BUILD_STATS("slicing node " << node.ToString() << " => take"); + LOG_BUILD_STATS("slicing node " << node.ToString(KeyDefaults) << " => take"); AddFutureEvents(node); return true; } @@ -278,17 +286,20 @@ class TTableHistogramBuilderBtreeIndex { // can't split, decide by node.EndRowId - 1 // TODO: decide by non-empty slice and node intersection, but this requires size calculation changes too if (it->Has(node.EndRowId - 1)) { - LOG_BUILD_STATS("slicing node " << node.ToString() << " => take root"); + LOG_BUILD_STATS("slicing node " << node.ToString(KeyDefaults) << " => take leaf"); + // the slice may start after node begin, shift the node begin to make it more sensible + node.BeginRowId = it->BeginRowId(); + node.BeginKey = MakeCellsIterableKey(node.Part, it->FirstKey); AddFutureEvents(node); } else { - LOG_BUILD_STATS("slicing node " << node.ToString() << " => skip root"); + LOG_BUILD_STATS("slicing node " << node.ToString(KeyDefaults) << " => skip leaf"); } return true; } bool ready = true; - LOG_BUILD_STATS("slicing node " << node.ToString() << " => split"); + LOG_BUILD_STATS("slicing node " << node.ToString(KeyDefaults) << " => split"); const auto addNode = [&](TNodeState& child) { ready &= SlicePart(slices, child); }; @@ -341,10 +352,10 @@ class TTableHistogramBuilderBtreeIndex { << " openedSortedByRowCount: " << openedSortedByRowCount.size() << " openedSortedByDataSize: " << openedSortedByDataSize.size() << " FutureEvents: " << FutureEvents.size() - << " currentKeyPointer: " << currentKeyPointer.ToString()); + << " currentKeyPointer: " << currentKeyPointer.ToString(KeyDefaults)); auto processEvent = [&](const TEvent& event) { - LOG_BUILD_STATS("processing event " << event.ToString()); + LOG_BUILD_STATS("processing event " << event.ToString(KeyDefaults)); Y_DEBUG_ABORT_UNLESS(NodeEventKeyGreater.Compare(event, currentKeyPointer) <= 0, "Can't process future events"); if (event.IsBegin) { if (event.Node->Open(openedRowCount, openedDataSize)) { @@ -370,7 +381,7 @@ class TTableHistogramBuilderBtreeIndex { // TODO: skip all closed nodes and don't process them here // TODO: don't compare each node key and replace it with parentNode.Seek(currentKeyPointer) auto cmp = NodeEventKeyGreater.Compare(event, currentKeyPointer); - LOG_BUILD_STATS("adding event " << (i32)cmp << " " << event.ToString()); + LOG_BUILD_STATS("adding event " << (i32)cmp << " " << event.ToString(KeyDefaults)); if (cmp <= 0) { // event happened processEvent(event); if (cmp == 0) { @@ -381,8 +392,8 @@ class TTableHistogramBuilderBtreeIndex { } }; const auto addNode = [&](TNodeState& node) { - addEvent(TEvent{node.BeginKey, true, &node}); - addEvent(TEvent{node.EndKey, false, &node}); + addEvent(TEvent{&node, true}); + addEvent(TEvent{&node, false}); }; // may safely skip current key pointer and go further only if at the next iteration @@ -395,7 +406,7 @@ class TTableHistogramBuilderBtreeIndex { openedSortedByRowCount.pop(); LOG_BUILD_STATS("loading node by row count trigger" - << node->ToString() + << node->ToString(KeyDefaults) << " closedRowCount: " << closedRowCount << " openedRowCount: " << openedRowCount << " nextHistogramRowCount: " << nextHistogramRowCount); @@ -413,7 +424,7 @@ class TTableHistogramBuilderBtreeIndex { openedSortedByDataSize.pop(); LOG_BUILD_STATS("loading node by data size trigger" - << node->ToString() + << node->ToString(KeyDefaults) << " closedDataSize: " << closedDataSize << " openedDataSize: " << openedDataSize << " nextHistogramDataSize: " << nextHistogramDataSize); @@ -439,7 +450,7 @@ class TTableHistogramBuilderBtreeIndex { << " openedSortedByRowCount: " << openedSortedByRowCount.size() << " openedSortedByDataSize: " << openedSortedByDataSize.size() << " FutureEvents: " << FutureEvents.size() - << " currentKeyPointer: " << currentKeyPointer.ToString()); + << " currentKeyPointer: " << currentKeyPointer.ToString(KeyDefaults)); // add current key pointer to a histogram if we either: // - failed to split opened nodes and may exceed a next histogram bucket value (plus its gaps) @@ -449,7 +460,7 @@ class TTableHistogramBuilderBtreeIndex { // - minus size of all nodes that start at current key pointer // - plus half of size of all ohter opened nodes (as they exact position is unknown) // also check that current key pointer value is > then last presented value in a histogram - if (currentKeyPointer.Key) { + if (currentKeyPointer.GetKey()) { if (nextHistogramRowCount != Max()) { if (closedRowCount + openedRowCount > nextHistogramRowCount + RowCountResolutionGap || closedRowCount > nextHistogramRowCount - RowCountResolutionGap) { ui64 currentKeyRowCountOpens = 0; @@ -461,7 +472,7 @@ class TTableHistogramBuilderBtreeIndex { Y_ABORT_UNLESS(currentKeyRowCountOpens <= openedRowCount); ui64 currentKeyPointerRowCount = closedRowCount + (openedRowCount - currentKeyRowCountOpens) / 2; if ((stats.RowCountHistogram.empty() ? 0 : stats.RowCountHistogram.back().Value) < currentKeyPointerRowCount && currentKeyPointerRowCount < stats.RowCount) { - AddKey(stats.RowCountHistogram, currentKeyPointer.Key, currentKeyPointerRowCount); + AddKey(stats.RowCountHistogram, currentKeyPointer.GetKey(), currentKeyPointerRowCount); nextHistogramRowCount = Max(currentKeyPointerRowCount + 1, nextHistogramRowCount + RowCountResolution); if (nextHistogramRowCount + RowCountResolutionGap > stats.RowCount) { nextHistogramRowCount = Max(); @@ -480,7 +491,7 @@ class TTableHistogramBuilderBtreeIndex { Y_ABORT_UNLESS(currentKeyDataSizeOpens <= openedDataSize); ui64 currentKeyPointerDataSize = closedDataSize + (openedDataSize - currentKeyDataSizeOpens) / 2; if ((stats.DataSizeHistogram.empty() ? 0 : stats.DataSizeHistogram.back().Value) < currentKeyPointerDataSize && currentKeyPointerDataSize < stats.DataSize.Size) { - AddKey(stats.DataSizeHistogram, currentKeyPointer.Key, currentKeyPointerDataSize); + AddKey(stats.DataSizeHistogram, currentKeyPointer.GetKey(), currentKeyPointerDataSize); nextHistogramDataSize = Max(currentKeyPointerDataSize + 1, nextHistogramDataSize + DataSizeResolution); if (nextHistogramDataSize + DataSizeResolutionGap > stats.DataSize.Size) { nextHistogramDataSize = Max(); @@ -507,7 +518,7 @@ class TTableHistogramBuilderBtreeIndex { return true; } - void AddKey(THistogram& histogram, TCellsIterable& key, ui64 value) { + void AddKey(THistogram& histogram, const TCellsIterable& key, ui64 value) { TVector keyCells; // add columns that are present in the part: @@ -555,8 +566,14 @@ class TTableHistogramBuilderBtreeIndex { } void AddFutureEvents(TNodeState& node) { - FutureEvents.push(TEvent{node.BeginKey, true, &node}); - FutureEvents.push(TEvent{node.EndKey, false, &node}); + auto cmp = NodeEventKeyGreater.Compare(TEvent{&node, true}, TEvent{&node, false}); + LOG_BUILD_STATS("adding node future events " << (i32)cmp << " " << node.ToString(KeyDefaults)); + if (node.GetRowCount() > 1) { + Y_DEBUG_ABORT_UNLESS(cmp < 0); + } + + FutureEvents.push(TEvent{&node, true}); + FutureEvents.push(TEvent{&node, false}); } private: diff --git a/ydb/core/tablet_flat/ut/ut_btree_index_nodes.cpp b/ydb/core/tablet_flat/ut/ut_btree_index_nodes.cpp index 9e266f69f9ec..c9c700152e41 100644 --- a/ydb/core/tablet_flat/ut/ut_btree_index_nodes.cpp +++ b/ydb/core/tablet_flat/ut/ut_btree_index_nodes.cpp @@ -99,7 +99,7 @@ namespace { dumpChild(node, 0); for (TRecIdx i : xrange(node.GetKeysCount())) { - Cerr << intend << " | > "; + Cerr << intend << " | > {"; auto cells = node.GetKeyCellsIter(i, groupInfo.ColsKeyIdx); for (TPos pos : xrange(cells.Count())) { @@ -111,7 +111,7 @@ namespace { Cerr << (pos ? ", " : "") << str; } - Cerr << Endl; + Cerr << "}" << Endl; dumpChild(node, i + 1); } diff --git a/ydb/core/tablet_flat/ut/ut_stat.cpp b/ydb/core/tablet_flat/ut/ut_stat.cpp index 0111f48f6d68..89b0fe762c68 100644 --- a/ydb/core/tablet_flat/ut/ut_stat.cpp +++ b/ydb/core/tablet_flat/ut/ut_stat.cpp @@ -461,7 +461,7 @@ Y_UNIT_TEST_SUITE(BuildStatsHistogram) { for (auto &part : subset.Flatten) { TTestEnv env; auto index = CreateIndexIter(part.Part.Get(), &env, {}); - Cerr << " " << index->GetEndRowId() << " rows, " + Cerr << " " << part->Label << " " << index->GetEndRowId() << " rows, " << IndexTools::CountMainPages(*part.Part) << " pages, " << (part->IndexPages.HasBTree() ? part->IndexPages.GetBTree({}).LevelCount : -1) << " levels: "; for (ui32 sample : xrange(1u, samples + 1)) { diff --git a/ydb/core/tx/datashard/datashard__stats.cpp b/ydb/core/tx/datashard/datashard__stats.cpp index 9bfd5e64e551..01e971cd9002 100644 --- a/ydb/core/tx/datashard/datashard__stats.cpp +++ b/ydb/core/tx/datashard/datashard__stats.cpp @@ -175,6 +175,14 @@ class TTableStatsCoroBuilder : public TActorCoroImpl, private IPages, TTableStat << (ev->PartOwners.size() > 1 || ev->PartOwners.size() == 1 && *ev->PartOwners.begin() != TabletId ? ", with borrowed parts" : "") << (ev->HasSchemaChanges ? ", with schema changes" : "") << ", LoadedSize " << PagesSize << ", " << NFmt::Do(*Spent)); + + if (const auto& stats = ev->Stats; stats.DataSize.Size > 10_MB && stats.RowCount > 100 + && Min(stats.RowCountHistogram.size(), stats.DataSizeHistogram.size()) < HistogramBucketsCount / 2) + { + LOG_ERROR_S(GetActorContext(), NKikimrServices::TABLET_STATS_BUILDER, "Stats at datashard " << TabletId << ", for tableId " << TableId + << " don't have enough keys: " + << ev->Stats.ToString()); + } Send(ReplyTo, ev.Release());