Skip to content

Commit 5d66195

Browse files
authored
Disable merges for indexImplTables partitions when build is in progress (#10166)
1 parent 0da7943 commit 5d66195

File tree

7 files changed

+247
-12
lines changed

7 files changed

+247
-12
lines changed

ydb/core/tx/schemeshard/schemeshard__table_stats.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,18 +264,19 @@ bool TTxStoreTableStats::PersistSingleStats(const TPathId& pathId,
264264
subDomainInfo->EffectiveStoragePools(),
265265
shardInfo->BindedChannels);
266266

267+
const auto pathElement = Self->PathsById[pathId];
267268
LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD,
268269
"TTxStoreTableStats.PersistSingleStats: main stats from"
269270
<< " datashardId(TabletID)=" << datashardId << " maps to shardIdx: " << shardIdx
270-
<< ", pathId: " << pathId << ", pathId map=" << Self->PathsById[pathId]->Name
271+
<< ", pathId: " << pathId << ", pathId map=" << pathElement->Name
271272
<< ", is column=" << isColumnTable << ", is olap=" << isOlapStore);
272273

273274
const TPartitionStats newStats = PrepareStats(ctx, rec, channelsMapping);
274275

275276
LOG_INFO_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD,
276-
"Add stats from shard with datashardId(TabletID)=" << datashardId
277+
"Add stats from shard with datashardId(TabletID)=" << datashardId
277278
<< ", pathId " << pathId.LocalPathId
278-
<< ": RowCount " << newStats.RowCount
279+
<< ": RowCount " << newStats.RowCount
279280
<< ", DataSize " << newStats.DataSize
280281
<< (newStats.HasBorrowedData ? ", with borrowed parts" : ""));
281282

@@ -404,11 +405,14 @@ bool TTxStoreTableStats::PersistSingleStats(const TPathId& pathId,
404405
Self->TabletCounters->Percentile()[COUNTER_NUM_SHARDS_BY_TTL_LAG].IncrementFor(lag->Seconds());
405406
}
406407

408+
const TTableIndexInfo* index = Self->Indexes.Value(pathElement->ParentPathId, nullptr).Get();
407409
const TTableInfo* mainTableForIndex = Self->GetMainTableForIndex(pathId);
408410

409411
const auto forceShardSplitSettings = Self->SplitSettings.GetForceShardSplitSettings();
410412
TVector<TShardIdx> shardsToMerge;
411-
if (table->CheckCanMergePartitions(Self->SplitSettings, forceShardSplitSettings, shardIdx, shardsToMerge, mainTableForIndex)) {
413+
if ((!index || index->State == NKikimrSchemeOp::EIndexStateReady)
414+
&& table->CheckCanMergePartitions(Self->SplitSettings, forceShardSplitSettings, shardIdx, shardsToMerge, mainTableForIndex)
415+
) {
412416
TTxId txId = Self->GetCachedTxId(ctx);
413417

414418
if (!txId) {

ydb/core/tx/schemeshard/ut_helpers/helpers.cpp

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <ydb/core/util/pb.h>
1818
#include <ydb/public/api/protos/ydb_export.pb.h>
19+
#include <ydb/public/sdk/cpp/client/ydb_table/table.h>
1920

2021
#include <library/cpp/testing/unittest/registar.h>
2122

@@ -1679,12 +1680,18 @@ namespace NSchemeShardUT_Private {
16791680
*index.mutable_data_columns() = {cfg.DataColumns.begin(), cfg.DataColumns.end()};
16801681

16811682
switch (cfg.IndexType) {
1682-
case NKikimrSchemeOp::EIndexTypeGlobal:
1683-
*index.mutable_global_index() = Ydb::Table::GlobalIndex();
1684-
break;
1685-
case NKikimrSchemeOp::EIndexTypeGlobalAsync:
1686-
*index.mutable_global_async_index() = Ydb::Table::GlobalAsyncIndex();
1687-
break;
1683+
case NKikimrSchemeOp::EIndexTypeGlobal: {
1684+
auto& settings = *index.mutable_global_index()->mutable_settings();
1685+
if (cfg.GlobalIndexSettings) {
1686+
cfg.GlobalIndexSettings[0].SerializeTo(settings);
1687+
}
1688+
} break;
1689+
case NKikimrSchemeOp::EIndexTypeGlobalAsync: {
1690+
auto& settings = *index.mutable_global_async_index()->mutable_settings();
1691+
if (cfg.GlobalIndexSettings) {
1692+
cfg.GlobalIndexSettings[0].SerializeTo(settings);
1693+
}
1694+
} break;
16881695
case NKikimrSchemeOp::EIndexTypeGlobalVectorKmeansTree: {
16891696
auto& settings = *index.mutable_global_vector_kmeans_tree_index();
16901697
settings = Ydb::Table::GlobalVectorKMeansTreeIndex();
@@ -2011,7 +2018,7 @@ namespace NSchemeShardUT_Private {
20112018
Runtime.SendToPipe(shardData.ShardId, sender, proposal);
20122019
TAutoPtr<IEventHandle> handle;
20132020
auto event = Runtime.GrabEdgeEventIf<TEvDataShard::TEvProposeTransactionResult>(handle,
2014-
[=](const TEvDataShard::TEvProposeTransactionResult& event) {
2021+
[this, shardData](const TEvDataShard::TEvProposeTransactionResult& event) {
20152022
return event.GetTxId() == TxId && event.GetOrigin() == shardData.ShardId;
20162023
});
20172024
activeZone = true;

ydb/core/tx/schemeshard/ut_helpers/helpers.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@
6262
template<bool OPT1, bool OPT2> \
6363
void N(NUnitTest::TTestContext&)
6464

65+
namespace NYdb::NTable {
66+
struct TGlobalIndexSettings;
67+
}
68+
6569
namespace NSchemeShardUT_Private {
6670
using namespace NKikimr;
6771

@@ -361,6 +365,7 @@ namespace NSchemeShardUT_Private {
361365
NKikimrSchemeOp::EIndexType IndexType = NKikimrSchemeOp::EIndexTypeGlobal;
362366
TVector<TString> IndexColumns;
363367
TVector<TString> DataColumns;
368+
TVector<NYdb::NTable::TGlobalIndexSettings> GlobalIndexSettings = {};
364369
};
365370

366371
std::unique_ptr<TEvIndexBuilder::TEvCreateRequest> CreateBuildColumnRequest(ui64 id, const TString& dbName, const TString& src, const TString& columnName, const Ydb::TypedValue& literal);

ydb/core/tx/schemeshard/ut_helpers/ya.make

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ PEERDIR(
2222
ydb/public/lib/scheme_types
2323
ydb/library/yql/public/issue
2424
ydb/public/sdk/cpp/client/ydb_driver
25+
ydb/public/sdk/cpp/client/ydb_table
2526
)
2627

2728
SRCS(

ydb/core/tx/schemeshard/ut_index/ut_async_index.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <ydb/core/tx/schemeshard/ut_helpers/test_with_reboots.h>
66
#include <ydb/core/testlib/tablet_helpers.h>
77
#include <ydb/public/lib/deprecated/kicli/kicli.h>
8+
#include <ydb/public/sdk/cpp/client/ydb_table/table.h>
89

910
using namespace NKikimr;
1011
using namespace NSchemeShard;
@@ -574,5 +575,5 @@ Y_UNIT_TEST_SUITE(TAsyncIndexTests) {
574575
NLs::IndexState(NKikimrSchemeOp::EIndexStateReady),
575576
NLs::IndexKeys({"indexed"}),
576577
});
577-
}
578+
}
578579
}

ydb/core/tx/schemeshard/ut_index_build/ut_index_build.cpp

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
#include <ydb/core/tx/scheme_board/events.h>
33
#include <ydb/core/tx/schemeshard/ut_helpers/helpers.h>
44
#include <ydb/core/tx/schemeshard/schemeshard_billing_helpers.h>
5+
#include <ydb/core/testlib/actors/block_events.h>
6+
#include <ydb/core/testlib/actors/wait_events.h>
57
#include <ydb/core/testlib/tablet_helpers.h>
68

79
#include <ydb/core/tx/datashard/datashard.h>
810
#include <ydb/core/metering/metering.h>
911

12+
#include <ydb/public/sdk/cpp/client/ydb_table/table.h>
13+
1014
using namespace NKikimr;
1115
using namespace NSchemeShard;
1216
using namespace NSchemeShardUT_Private;
@@ -781,6 +785,155 @@ Y_UNIT_TEST_SUITE(IndexBuildTest) {
781785

782786
}
783787

788+
Y_UNIT_TEST(MergeIndexTableShardsOnlyWhenReady) {
789+
TTestBasicRuntime runtime;
790+
791+
TTestEnvOptions opts;
792+
opts.EnableBackgroundCompaction(false);
793+
opts.DisableStatsBatching(true);
794+
TTestEnv env(runtime, opts);
795+
796+
NDataShard::gDbStatsReportInterval = TDuration::Seconds(0);
797+
798+
ui64 txId = 100;
799+
800+
TestCreateTable(runtime, ++txId, "/MyRoot", R"(
801+
Name: "Table"
802+
Columns { Name: "key" Type: "Uint64" }
803+
Columns { Name: "value" Type: "Uint64" }
804+
KeyColumnNames: ["key"]
805+
)");
806+
env.TestWaitNotification(runtime, txId);
807+
808+
Ydb::Table::GlobalIndexSettings settings;
809+
UNIT_ASSERT(google::protobuf::TextFormat::ParseFromString(R"(
810+
partition_at_keys {
811+
split_points {
812+
type { tuple_type { elements { optional_type { item { type_id: UINT64 } } } } }
813+
value { items { uint64_value: 10 } }
814+
}
815+
split_points {
816+
type { tuple_type { elements { optional_type { item { type_id: UINT64 } } } } }
817+
value { items { uint64_value: 20 } }
818+
}
819+
split_points {
820+
type { tuple_type { elements { optional_type { item { type_id: UINT64 } } } } }
821+
value { items { uint64_value: 30 } }
822+
}
823+
}
824+
)", &settings));
825+
826+
TBlockEvents<TEvSchemeShard::TEvModifySchemeTransaction> indexApplicationBlocker(runtime, [](const auto& ev) {
827+
const auto& modifyScheme = ev->Get()->Record.GetTransaction(0);
828+
return modifyScheme.GetOperationType() == NKikimrSchemeOp::ESchemeOpApplyIndexBuild;
829+
});
830+
831+
ui64 indexInitializationTx = 0;
832+
TWaitForFirstEvent<TEvSchemeShard::TEvModifySchemeTransaction> indexInitializationWaiter(runtime,
833+
[&indexInitializationTx](const auto& ev){
834+
const auto& record = ev->Get()->Record;
835+
if (record.GetTransaction(0).GetOperationType() == NKikimrSchemeOp::ESchemeOpCreateIndexBuild) {
836+
indexInitializationTx = record.GetTxId();
837+
return true;
838+
}
839+
return false;
840+
}
841+
);
842+
843+
const ui64 buildIndexTx = ++txId;
844+
TestBuildIndex(runtime, buildIndexTx, TTestTxConfig::SchemeShard, "/MyRoot", "/MyRoot/Table", TBuildIndexConfig{
845+
"ByValue", NKikimrSchemeOp::EIndexTypeGlobal, { "value" }, {},
846+
{ NYdb::NTable::TGlobalIndexSettings::FromProto(settings) }
847+
});
848+
849+
indexInitializationWaiter.Wait();
850+
UNIT_ASSERT_VALUES_UNEQUAL(indexInitializationTx, 0);
851+
env.TestWaitNotification(runtime, indexInitializationTx);
852+
853+
TestDescribeResult(DescribePrivatePath(runtime, "/MyRoot/Table/ByValue"), {
854+
NLs::PathExist,
855+
NLs::IndexState(NKikimrSchemeOp::EIndexStateWriteOnly)
856+
});
857+
858+
TVector<ui64> indexShards;
859+
auto shardCollector = [&indexShards](const NKikimrScheme::TEvDescribeSchemeResult& record) {
860+
UNIT_ASSERT_VALUES_EQUAL(record.GetStatus(), NKikimrScheme::StatusSuccess);
861+
const auto& partitions = record.GetPathDescription().GetTablePartitions();
862+
indexShards.clear();
863+
indexShards.reserve(partitions.size());
864+
for (const auto& partition : partitions) {
865+
indexShards.emplace_back(partition.GetDatashardId());
866+
}
867+
};
868+
TestDescribeResult(DescribePrivatePath(runtime, "/MyRoot/Table/ByValue/indexImplTable", true), {
869+
NLs::PathExist,
870+
NLs::PartitionCount(4),
871+
shardCollector
872+
});
873+
UNIT_ASSERT_VALUES_EQUAL(indexShards.size(), 4);
874+
875+
{
876+
// make sure no shards are merged
877+
TBlockEvents<TEvSchemeShard::TEvModifySchemeTransaction> mergeBlocker(runtime, [](const auto& ev) {
878+
const auto& modifyScheme = ev->Get()->Record.GetTransaction(0);
879+
return modifyScheme.GetOperationType() == NKikimrSchemeOp::ESchemeOpSplitMergeTablePartitions;
880+
});
881+
882+
{
883+
// wait for all index shards to send statistics
884+
THashSet<ui64> shardsWithStats;
885+
using TEvType = TEvDataShard::TEvPeriodicTableStats;
886+
auto statsObserver = runtime.AddObserver<TEvType>([&shardsWithStats](const TEvType::TPtr& ev) {
887+
shardsWithStats.emplace(ev->Get()->Record.GetDatashardId());
888+
});
889+
890+
runtime.WaitFor("all index shards to send statistics", [&]{
891+
return AllOf(indexShards, [&shardsWithStats](ui64 indexShard) {
892+
return shardsWithStats.contains(indexShard);
893+
});
894+
});
895+
}
896+
897+
// we expect to not have observed any attempts to merge
898+
UNIT_ASSERT(mergeBlocker.empty());
899+
900+
// wait for 1 minute to ensure that no merges have been started by SchemeShard
901+
env.SimulateSleep(runtime, TDuration::Minutes(1));
902+
UNIT_ASSERT(mergeBlocker.empty());
903+
}
904+
905+
// splits are allowed even if the index is not ready
906+
TestSplitTable(runtime, ++txId, "/MyRoot/Table/ByValue/indexImplTable", Sprintf(R"(
907+
SourceTabletId: %lu
908+
SplitBoundary { KeyPrefix { Tuple { Optional { Uint64: 5 } } } }
909+
)",
910+
indexShards.front()
911+
)
912+
);
913+
env.TestWaitNotification(runtime, txId);
914+
915+
indexApplicationBlocker.Stop().Unblock();
916+
env.TestWaitNotification(runtime, buildIndexTx);
917+
918+
TestDescribeResult(DescribePrivatePath(runtime, "/MyRoot/Table/ByValue"), {
919+
NLs::IndexState(NKikimrSchemeOp::EIndexStateReady)
920+
});
921+
922+
// wait until all index impl table shards are merged into one
923+
while (true) {
924+
TestDescribeResult(DescribePrivatePath(runtime, "/MyRoot/Table/ByValue/indexImplTable", true), {
925+
shardCollector
926+
});
927+
if (indexShards.size() > 1) {
928+
// If a merge happens, old shards are deleted and replaced with a new one.
929+
// That is why we need to wait for * all * the shards to be deleted.
930+
env.TestWaitTabletDeletion(runtime, indexShards);
931+
} else {
932+
break;
933+
}
934+
}
935+
}
936+
784937
Y_UNIT_TEST(DropIndex) {
785938
TTestBasicRuntime runtime;
786939
TTestEnv env(runtime);

ydb/core/tx/schemeshard/ut_split_merge/ut_split_merge.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <ydb/core/tablet_flat/util_fmt_cell.h>
2+
#include <ydb/core/testlib/actors/block_events.h>
23
#include <ydb/core/tx/schemeshard/ut_helpers/helpers.h>
34
#include <ydb/core/tx/schemeshard/schemeshard_utils.h>
45

@@ -277,6 +278,69 @@ Y_UNIT_TEST_SUITE(TSchemeShardSplitBySizeTest) {
277278
// test requires more txids than cached at start
278279
}
279280

281+
Y_UNIT_TEST(MergeIndexTableShards) {
282+
TTestBasicRuntime runtime;
283+
284+
TTestEnvOptions opts;
285+
opts.EnableBackgroundCompaction(false);
286+
TTestEnv env(runtime, opts);
287+
288+
ui64 txId = 100;
289+
290+
TBlockEvents<TEvDataShard::TEvPeriodicTableStats> statsBlocker(runtime);
291+
292+
TestCreateIndexedTable(runtime, ++txId, "/MyRoot", R"(
293+
TableDescription {
294+
Name: "Table"
295+
Columns { Name: "key" Type: "Uint64" }
296+
Columns { Name: "value" Type: "Utf8" }
297+
KeyColumnNames: ["key"]
298+
}
299+
IndexDescription {
300+
Name: "ByValue"
301+
KeyColumnNames: ["value"]
302+
IndexImplTableDescriptions {
303+
SplitBoundary { KeyPrefix { Tuple { Optional { Text: "A" } } } }
304+
SplitBoundary { KeyPrefix { Tuple { Optional { Text: "B" } } } }
305+
SplitBoundary { KeyPrefix { Tuple { Optional { Text: "C" } } } }
306+
}
307+
}
308+
)"
309+
);
310+
env.TestWaitNotification(runtime, txId);
311+
312+
TestDescribeResult(DescribePrivatePath(runtime, "/MyRoot/Table/ByValue/indexImplTable", true),
313+
{ NLs::PartitionCount(4) }
314+
);
315+
316+
statsBlocker.Stop().Unblock();
317+
318+
TVector<ui64> indexShards;
319+
auto shardCollector = [&indexShards](const NKikimrScheme::TEvDescribeSchemeResult& record) {
320+
UNIT_ASSERT_VALUES_EQUAL(record.GetStatus(), NKikimrScheme::StatusSuccess);
321+
const auto& partitions = record.GetPathDescription().GetTablePartitions();
322+
indexShards.clear();
323+
indexShards.reserve(partitions.size());
324+
for (const auto& partition : partitions) {
325+
indexShards.emplace_back(partition.GetDatashardId());
326+
}
327+
};
328+
329+
// wait until all index impl table shards are merged into one
330+
while (true) {
331+
TestDescribeResult(DescribePrivatePath(runtime, "/MyRoot/Table/ByValue/indexImplTable", true), {
332+
shardCollector
333+
});
334+
if (indexShards.size() > 1) {
335+
// If a merge happens, old shards are deleted and replaced with a new one.
336+
// That is why we need to wait for * all * the shards to be deleted.
337+
env.TestWaitTabletDeletion(runtime, indexShards);
338+
} else {
339+
break;
340+
}
341+
}
342+
}
343+
280344
Y_UNIT_TEST(AutoMergeInOne) {
281345
TTestWithReboots t;
282346
t.Run([&](TTestActorRuntime& runtime, bool& activeZone) {

0 commit comments

Comments
 (0)