Skip to content

Commit cdcd6b8

Browse files
authored
Enhanced parallelism of data restoring in ydb tools restore (#12203)
1 parent d3a36a5 commit cdcd6b8

File tree

9 files changed

+232
-95
lines changed

9 files changed

+232
-95
lines changed

ydb/apps/ydb/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11

2+
* Enhanced parallelism of data restoring in `ydb tools restore`
3+
24
## 2.16.0 ##
35

46
* Improved throughput of `ydb import file csv` command. It is now approximately x3 times faster

ydb/public/lib/ydb_cli/dump/restore_compat.cpp

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ class TDataAccumulator: public NPrivate::IDataAccumulator, protected TQueryBuild
9595
Bytes = 0;
9696
RequestUnitsX2 = 0;
9797

98-
return {}; // Writer gets data directly from accumulator
98+
// Writer gets data directly from accumulator
99+
NPrivate::TBatch batch;
100+
batch.SetOriginAccumulator(this);
101+
return batch;
99102
}
100103

101104
private:
@@ -115,35 +118,37 @@ class TDataWriter: public NPrivate::IDataWriter {
115118
explicit TDataWriter(
116119
const TString& path,
117120
TTableClient& tableClient,
118-
NPrivate::IDataAccumulator* accumulator,
121+
const NPrivate::IDataAccumulator* accumulator,
119122
const TRestoreSettings& settings)
120123
: Path(path)
121124
, TableClient(tableClient)
122-
, Accumulator(dynamic_cast<TDataAccumulator*>(accumulator))
123125
, UseBulkUpsert(settings.Mode_ == TRestoreSettings::EMode::BulkUpsert)
124-
{
125-
Y_ENSURE(Accumulator);
126+
{
127+
const auto* dataAccumulator = dynamic_cast<const TDataAccumulator*>(accumulator);
128+
Y_ENSURE(dataAccumulator);
126129

127130
TUploader::TOptions opts;
128131
opts.InFly = settings.InFly_;
129132
opts.Rate = settings.RateLimiterSettings_.Rate_;
130133
opts.Interval = settings.RateLimiterSettings_.Interval_;
131134
opts.ReactionTime = settings.RateLimiterSettings_.ReactionTime_;
132135

133-
Uploader = MakeHolder<TUploader>(opts, TableClient, Accumulator->GetQueryString());
136+
Uploader = MakeHolder<TUploader>(opts, TableClient, dataAccumulator->GetQueryString());
134137
}
135138

136-
bool Push(NPrivate::TBatch&&) override {
137-
bool ok;
139+
bool Push(NPrivate::TBatch&& batch) override {
140+
auto* accumulator = dynamic_cast<TDataAccumulator*>(batch.GetOriginAccumulator());
141+
Y_ENSURE(accumulator);
138142

143+
bool ok;
139144
if (UseBulkUpsert) {
140-
ok = Uploader->Push(Path, Accumulator->EndAndGetResultingValue());
145+
ok = Uploader->Push(Path, accumulator->EndAndGetResultingValue());
141146
} else {
142-
ok = Uploader->Push(Accumulator->EndAndGetResultingParams());
147+
ok = Uploader->Push(accumulator->EndAndGetResultingParams());
143148
}
144149

145150
if (ok) {
146-
Accumulator->Begin();
151+
accumulator->Begin();
147152
}
148153

149154
return ok;
@@ -156,7 +161,6 @@ class TDataWriter: public NPrivate::IDataWriter {
156161
private:
157162
const TString Path;
158163
TTableClient& TableClient;
159-
TDataAccumulator* Accumulator;
160164
const bool UseBulkUpsert;
161165
THolder<TUploader> Uploader;
162166

@@ -174,7 +178,7 @@ NPrivate::IDataAccumulator* CreateCompatAccumulator(
174178
NPrivate::IDataWriter* CreateCompatWriter(
175179
const TString& path,
176180
TTableClient& tableClient,
177-
NPrivate::IDataAccumulator* accumulator,
181+
const NPrivate::IDataAccumulator* accumulator,
178182
const TRestoreSettings& settings) {
179183
return new TDataWriter(path, tableClient, accumulator, settings);
180184
}

ydb/public/lib/ydb_cli/dump/restore_compat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ NPrivate::IDataAccumulator* CreateCompatAccumulator(
1313
NPrivate::IDataWriter* CreateCompatWriter(
1414
const TString& path,
1515
NTable::TTableClient& tableClient,
16-
NPrivate::IDataAccumulator* accumulator,
16+
const NPrivate::IDataAccumulator* accumulator,
1717
const TRestoreSettings& settings);
1818

1919
} // NDump

ydb/public/lib/ydb_cli/dump/restore_impl.cpp

Lines changed: 148 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,24 @@ bool IsOperationStarted(TStatus operationStatus) {
8080
return operationStatus.IsSuccess() || operationStatus.GetStatus() == EStatus::STATUS_UNDEFINED;
8181
}
8282

83+
ui32 CountDataFiles(const TFsPath& fsPath) {
84+
ui32 dataFileId = 0;
85+
TFsPath dataFile = fsPath.Child(DataFileName(dataFileId));
86+
while (dataFile.Exists()) {
87+
dataFile = fsPath.Child(DataFileName(++dataFileId));
88+
}
89+
return dataFileId;
90+
}
91+
92+
TRestoreResult CombineResults(const TVector<TRestoreResult>& results) {
93+
for (auto result : results) {
94+
if (!result.IsSuccess()) {
95+
return result;
96+
}
97+
}
98+
return Result<TRestoreResult>();
99+
}
100+
83101
} // anonymous
84102

85103
namespace NPrivate {
@@ -392,95 +410,164 @@ TRestoreResult TRestoreClient::CheckSchema(const TString& dbPath, const TTableDe
392410
return Result<TRestoreResult>();
393411
}
394412

395-
struct TWriterWaiter {
396-
NPrivate::IDataWriter& Writer;
397-
398-
TWriterWaiter(NPrivate::IDataWriter& writer)
399-
: Writer(writer)
400-
{
401-
}
402-
403-
~TWriterWaiter() {
404-
Writer.Wait();
405-
}
406-
};
407-
408-
TRestoreResult TRestoreClient::RestoreData(const TFsPath& fsPath, const TString& dbPath, const TRestoreSettings& settings, const TTableDescription& desc) {
409-
THolder<NPrivate::IDataAccumulator> accumulator;
413+
THolder<NPrivate::IDataWriter> TRestoreClient::CreateDataWriter(const TString& dbPath, const TRestoreSettings& settings,
414+
const TTableDescription& desc, const TVector<THolder<NPrivate::IDataAccumulator>>& accumulators)
415+
{
410416
THolder<NPrivate::IDataWriter> writer;
411-
412417
switch (settings.Mode_) {
413418
case TRestoreSettings::EMode::Yql:
414419
case TRestoreSettings::EMode::BulkUpsert: {
415-
accumulator.Reset(CreateCompatAccumulator(dbPath, desc, settings));
416-
writer.Reset(CreateCompatWriter(dbPath, TableClient, accumulator.Get(), settings));
420+
// Need only one accumulator to initialize query string
421+
writer.Reset(CreateCompatWriter(dbPath, TableClient, accumulators[0].Get(), settings));
422+
break;
423+
}
417424

425+
case TRestoreSettings::EMode::ImportData: {
426+
writer.Reset(CreateImportDataWriter(dbPath, desc, ImportClient, TableClient, accumulators, settings, Log));
418427
break;
419428
}
429+
}
430+
return writer;
431+
}
432+
433+
TRestoreResult TRestoreClient::CreateDataAccumulators(TVector<THolder<NPrivate::IDataAccumulator>>& outAccumulators,
434+
const TString& dbPath, const TRestoreSettings& settings, const NTable::TTableDescription& desc, ui32 dataFilesCount)
435+
{
436+
const ui32 accumulatorsCount = std::min(settings.InFly_, dataFilesCount);
437+
outAccumulators.resize(accumulatorsCount);
438+
439+
switch (settings.Mode_) {
440+
case TRestoreSettings::EMode::Yql:
441+
case TRestoreSettings::EMode::BulkUpsert:
442+
for (size_t i = 0; i < accumulatorsCount; ++i) {
443+
outAccumulators[i].Reset(CreateCompatAccumulator(dbPath, desc, settings));
444+
}
445+
break;
420446

421447
case TRestoreSettings::EMode::ImportData: {
422448
TMaybe<TTableDescription> actualDesc;
423449
auto descResult = DescribeTable(TableClient, dbPath, actualDesc);
424450
if (!descResult.IsSuccess()) {
425451
return Result<TRestoreResult>(dbPath, std::move(descResult));
426452
}
427-
428-
accumulator.Reset(CreateImportDataAccumulator(desc, *actualDesc, settings, Log));
429-
writer.Reset(CreateImportDataWriter(dbPath, desc, ImportClient, TableClient, accumulator.Get(), settings, Log));
430-
453+
for (size_t i = 0; i < accumulatorsCount; ++i) {
454+
outAccumulators[i].Reset(CreateImportDataAccumulator(desc, *actualDesc, settings, Log));
455+
}
431456
break;
432457
}
433458
}
459+
return Result<TRestoreResult>();
460+
}
434461

435-
TWriterWaiter waiter(*writer);
436-
437-
ui32 dataFileId = 0;
438-
TFsPath dataFile = fsPath.Child(DataFileName(dataFileId));
439-
TVector<TString> dataFileNames;
440-
441-
while (dataFile.Exists()) {
442-
LOG_D("Read data from " << dataFile.GetPath().Quote());
443-
444-
dataFileNames.push_back(dataFile);
445-
TFileInput input(dataFile, settings.FileBufferSize_);
446-
TString line;
447-
ui64 lineNo = 0;
448-
449-
while (input.ReadLine(line)) {
450-
auto l = NPrivate::TLine(std::move(line), dataFileNames.back(), ++lineNo);
451-
for (auto status = accumulator->Check(l); status != NPrivate::IDataAccumulator::OK; status = accumulator->Check(l)) {
452-
if (status == NPrivate::IDataAccumulator::ERROR) {
453-
return Result<TRestoreResult>(dbPath, EStatus::GENERIC_ERROR,
454-
TStringBuilder() << "Invalid data: " << l.GetLocation());
455-
}
456-
457-
if (!accumulator->Ready(true)) {
458-
LOG_E("Error reading data from " << dataFile.GetPath().Quote());
459-
return Result<TRestoreResult>(dbPath, EStatus::INTERNAL_ERROR, "Data is not ready");
460-
}
462+
TRestoreResult TRestoreClient::RestoreData(const TFsPath& fsPath, const TString& dbPath, const TRestoreSettings& settings, const TTableDescription& desc) {
463+
const ui32 dataFilesCount = CountDataFiles(fsPath);
464+
if (dataFilesCount == 0) {
465+
return Result<TRestoreResult>();
466+
}
461467

462-
if (!writer->Push(accumulator->GetData(true))) {
463-
LOG_E("Error writing data to " << dbPath.Quote() << ", file: " << dataFile.GetPath().Quote());
464-
return Result<TRestoreResult>(dbPath, EStatus::GENERIC_ERROR, "Cannot write data #1");
468+
TVector<THolder<NPrivate::IDataAccumulator>> accumulators;
469+
if (auto res = CreateDataAccumulators(accumulators, dbPath, settings, desc, dataFilesCount); !res.IsSuccess()) {
470+
return res;
471+
}
472+
473+
THolder<NPrivate::IDataWriter> writer = CreateDataWriter(dbPath, settings, desc, accumulators);
474+
475+
TVector<TRestoreResult> accumulatorWorkersResults(accumulators.size(), Result<TRestoreResult>());
476+
TThreadPool accumulatorWorkers(TThreadPool::TParams().SetBlocking(true));
477+
accumulatorWorkers.Start(accumulators.size(), accumulators.size());
478+
479+
const ui32 dataFilesPerAccumulator = dataFilesCount / accumulators.size();
480+
const ui32 dataFilesPerAccumulatorRemainder = dataFilesCount % accumulators.size();
481+
for (ui32 i = 0; i < accumulators.size(); ++i) {
482+
auto* accumulator = accumulators[i].Get();
483+
484+
ui32 dataFileIdStart = dataFilesPerAccumulator * i + std::min(i, dataFilesPerAccumulatorRemainder);
485+
ui32 dataFileIdEnd = dataFilesPerAccumulator * (i + 1) + std::min(i + 1, dataFilesPerAccumulatorRemainder);
486+
auto func = [&, i, dataFileIdStart, dataFileIdEnd, accumulator]() {
487+
for (size_t id = dataFileIdStart; id < dataFileIdEnd; ++id) {
488+
TFsPath dataFile = fsPath.Child(DataFileName(id));
489+
490+
LOG_D("Read data from " << dataFile.GetPath().Quote());
491+
492+
TFileInput input(dataFile, settings.FileBufferSize_);
493+
TString line;
494+
ui64 lineNo = 0;
495+
496+
while (input.ReadLine(line)) {
497+
auto l = NPrivate::TLine(std::move(line), dataFile.GetPath(), ++lineNo);
498+
499+
for (auto status = accumulator->Check(l); status != NPrivate::IDataAccumulator::OK; status = accumulator->Check(l)) {
500+
if (status == NPrivate::IDataAccumulator::ERROR) {
501+
accumulatorWorkersResults[i] = Result<TRestoreResult>(dbPath, EStatus::GENERIC_ERROR,
502+
TStringBuilder() << "Invalid data: " << l.GetLocation());
503+
return;
504+
}
505+
506+
if (!accumulator->Ready(true)) {
507+
LOG_E("Error reading data from " << dataFile.GetPath().Quote());
508+
accumulatorWorkersResults[i] = Result<TRestoreResult>(dbPath, EStatus::INTERNAL_ERROR, "Data is not ready");
509+
return;
510+
}
511+
512+
if (!writer->Push(accumulator->GetData(true))) {
513+
LOG_E("Error writing data to " << dbPath.Quote() << ", file: " << dataFile.GetPath().Quote());
514+
accumulatorWorkersResults[i] = Result<TRestoreResult>(dbPath, EStatus::GENERIC_ERROR, "Cannot write data #1");
515+
return;
516+
}
517+
}
518+
519+
accumulator->Feed(std::move(l));
520+
if (accumulator->Ready()) {
521+
if (!writer->Push(accumulator->GetData())) {
522+
LOG_E("Error writing data to " << dbPath.Quote() << ", file: " << dataFile.GetPath().Quote());
523+
accumulatorWorkersResults[i] = Result<TRestoreResult>(dbPath, EStatus::GENERIC_ERROR, "Cannot write data #2");
524+
return;
525+
}
526+
}
465527
}
466528
}
467529

468-
accumulator->Feed(std::move(l));
469-
if (accumulator->Ready()) {
470-
if (!writer->Push(accumulator->GetData())) {
471-
LOG_E("Error writing data to " << dbPath.Quote() << ", file: " << dataFile.GetPath().Quote());
472-
return Result<TRestoreResult>(dbPath, EStatus::GENERIC_ERROR, "Cannot write data #2");
530+
while (accumulator->Ready(true)) {
531+
if (!writer->Push(accumulator->GetData(true))) {
532+
accumulatorWorkersResults[i] = Result<TRestoreResult>(dbPath, EStatus::GENERIC_ERROR, "Cannot write data #3");
533+
return;
473534
}
474535
}
536+
};
537+
538+
if (!accumulatorWorkers.AddFunc(std::move(func))) {
539+
return Result<TRestoreResult>(dbPath, EStatus::GENERIC_ERROR, "Can't start restoring data: queue is full or shutting down");
475540
}
541+
}
476542

477-
dataFile = fsPath.Child(DataFileName(++dataFileId));
543+
accumulatorWorkers.Stop();
544+
if (auto res = CombineResults(accumulatorWorkersResults); !res.IsSuccess()) {
545+
return res;
478546
}
479547

480-
while (accumulator->Ready(true)) {
481-
if (!writer->Push(accumulator->GetData(true))) {
482-
LOG_E("Error writing data to " << dbPath.Quote() << ", file: " << dataFile.GetPath().Quote());
483-
return Result<TRestoreResult>(dbPath, EStatus::GENERIC_ERROR, "Cannot write data #3");
548+
// ensure that all data is restored
549+
while (true) {
550+
writer->Wait();
551+
552+
bool dataFound = false;
553+
for (auto& acc : accumulators) {
554+
if (acc->Ready(true)) {
555+
dataFound = true;
556+
break;
557+
}
558+
}
559+
560+
if (dataFound) {
561+
writer = CreateDataWriter(dbPath, settings, desc, accumulators);
562+
for (auto& acc : accumulators) {
563+
while (acc->Ready(true)) {
564+
if (!writer->Push(acc->GetData(true))) {
565+
return Result<TRestoreResult>(dbPath, EStatus::GENERIC_ERROR, "Cannot write data #4");
566+
}
567+
}
568+
}
569+
} else {
570+
break;
484571
}
485572
}
486573

ydb/public/lib/ydb_cli/dump/restore_impl.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,12 @@ class TLine {
6161
}
6262
};
6363

64+
class IDataAccumulator;
65+
6466
class TBatch {
6567
TStringBuilder Data;
6668
TVector<TLocation> Locations;
69+
IDataAccumulator* OriginAccumulator;
6770

6871
public:
6972
void Add(const TLine& line);
@@ -84,6 +87,14 @@ class TBatch {
8487
inline auto size() const {
8588
return Data.size();
8689
}
90+
91+
inline void SetOriginAccumulator(IDataAccumulator* originAccumulator) {
92+
OriginAccumulator = originAccumulator;
93+
}
94+
95+
inline IDataAccumulator* GetOriginAccumulator() const {
96+
return OriginAccumulator;
97+
}
8798
};
8899

89100
class IDataAccumulator {
@@ -120,6 +131,12 @@ class TRestoreClient {
120131
TRestoreResult RestoreIndexes(const TString& dbPath, const NTable::TTableDescription& desc);
121132
TRestoreResult RestorePermissions(const TFsPath& fsPath, const TString& dbPath, const TRestoreSettings& settings, const THashSet<TString>& oldEntries);
122133

134+
THolder<NPrivate::IDataWriter> CreateDataWriter(const TString& dbPath, const TRestoreSettings& settings,
135+
const NTable::TTableDescription& desc, const TVector<THolder<NPrivate::IDataAccumulator>>& accumulators);
136+
TRestoreResult CreateDataAccumulators(TVector<THolder<NPrivate::IDataAccumulator>>& outAccumulators,
137+
const TString& dbPath, const TRestoreSettings& settings, const NTable::TTableDescription& desc,
138+
ui32 dataFilesCount);
139+
123140
public:
124141
explicit TRestoreClient(const TDriver& driver, const std::shared_ptr<TLog>& log);
125142

0 commit comments

Comments
 (0)