2015-07-30 23:41:02 +00:00
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <DB/Interpreters/Aggregator.h>
|
|
|
|
|
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Доагрегирует потоки блоков, держа в оперативной памяти только по одному блоку из каждого потока.
|
|
|
|
|
* Это экономит оперативку в случае использования двухуровневой агрегации, где в каждом потоке будет до 256 блоков с частями результата.
|
|
|
|
|
*
|
|
|
|
|
* Агрегатные функции в блоках не должны быть финализированы, чтобы их состояния можно было объединить.
|
2015-09-07 21:20:28 +00:00
|
|
|
|
*
|
|
|
|
|
* Замечания:
|
|
|
|
|
*
|
|
|
|
|
* На хорошей сети (10Gbit) может работать заметно медленнее, так как чтения блоков с разных
|
|
|
|
|
* удалённых серверов делаются последовательно, при этом, чтение упирается в CPU.
|
|
|
|
|
* Это несложно исправить.
|
|
|
|
|
*
|
|
|
|
|
* Также, чтения и вычисления (слияние состояний) делаются по очереди.
|
|
|
|
|
* Есть возможность делать чтения асинхронно - при этом будет расходоваться в два раза больше памяти, но всё-равно немного.
|
|
|
|
|
* Это можно сделать с помощью UnionBlockInputStream.
|
|
|
|
|
*
|
|
|
|
|
* Можно держать в памяти не по одному блоку из каждого источника, а по несколько, и распараллелить мердж.
|
|
|
|
|
* При этом будет расходоваться кратно больше оперативки.
|
2015-07-30 23:41:02 +00:00
|
|
|
|
*/
|
|
|
|
|
class MergingAggregatedMemoryEfficientBlockInputStream : public IProfilingBlockInputStream
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
MergingAggregatedMemoryEfficientBlockInputStream(BlockInputStreams inputs_, const Names & keys_names_,
|
|
|
|
|
const AggregateDescriptions & aggregates_, bool overflow_row_, bool final_)
|
|
|
|
|
: aggregator(keys_names_, aggregates_, overflow_row_, 0, OverflowMode::THROW, nullptr, 0, 0),
|
2015-09-07 07:40:14 +00:00
|
|
|
|
final(final_),
|
|
|
|
|
inputs(inputs_.begin(), inputs_.end())
|
2015-07-30 23:41:02 +00:00
|
|
|
|
{
|
|
|
|
|
children = inputs_;
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-07 20:08:02 +00:00
|
|
|
|
String getName() const override { return "MergingAggregatedMemoryEfficient"; }
|
2015-07-30 23:41:02 +00:00
|
|
|
|
|
|
|
|
|
String getID() const override
|
|
|
|
|
{
|
|
|
|
|
std::stringstream res;
|
2015-09-07 20:08:02 +00:00
|
|
|
|
res << "MergingAggregatedMemoryEfficient(" << aggregator.getID();
|
2015-07-30 23:41:02 +00:00
|
|
|
|
for (size_t i = 0, size = children.size(); i < size; ++i)
|
|
|
|
|
res << ", " << children.back()->getID();
|
|
|
|
|
res << ")";
|
|
|
|
|
return res.str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
Block readImpl() override
|
|
|
|
|
{
|
|
|
|
|
/// Если child - RemoteBlockInputStream, то отправляет запрос на все удалённые серверы, инициируя вычисления.
|
2015-09-07 07:40:14 +00:00
|
|
|
|
if (!started)
|
|
|
|
|
{
|
|
|
|
|
started = true;
|
2015-07-30 23:41:02 +00:00
|
|
|
|
for (auto & child : children)
|
|
|
|
|
child->readPrefix();
|
2015-09-07 07:40:14 +00:00
|
|
|
|
}
|
2015-07-30 23:41:02 +00:00
|
|
|
|
|
2015-09-07 07:40:14 +00:00
|
|
|
|
/** Имеем несколько источников.
|
|
|
|
|
* Из каждого из них могут приходить следующие данные:
|
|
|
|
|
*
|
|
|
|
|
* 1. Блок, с указанным bucket_num.
|
|
|
|
|
* Это значит, что на удалённом сервере, данные были разрезаны по корзинам.
|
|
|
|
|
* И данные для одного bucket_num с разных серверов можно независимо объединять.
|
|
|
|
|
* При этом, даннные для разных bucket_num будут идти по возрастанию.
|
|
|
|
|
*
|
|
|
|
|
* 2. Блок без указания bucket_num.
|
|
|
|
|
* Это значит, что на удалённом сервере, данные не были разрезаны по корзинам.
|
|
|
|
|
* В случае, когда со всех серверов прийдут такие данные, их можно всех объединить.
|
|
|
|
|
* А если с другой части серверов прийдут данные, разрезанные по корзинам,
|
|
|
|
|
* то данные, не разрезанные по корзинам, нужно сначала разрезать, а потом объединять.
|
|
|
|
|
*
|
|
|
|
|
* 3. Блоки с указанием is_overflows.
|
|
|
|
|
* Это дополнительные данные для строк, не прошедших через max_rows_to_group_by.
|
|
|
|
|
* Они должны объединяться друг с другом отдельно.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
constexpr size_t NUM_BUCKETS = 256;
|
2015-07-30 23:41:02 +00:00
|
|
|
|
|
2015-09-07 07:40:14 +00:00
|
|
|
|
++current_bucket_num;
|
|
|
|
|
|
|
|
|
|
for (auto & input : inputs)
|
|
|
|
|
{
|
|
|
|
|
if (input.is_exhausted)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (input.block.info.bucket_num >= current_bucket_num)
|
|
|
|
|
continue;
|
|
|
|
|
|
2015-09-07 20:08:02 +00:00
|
|
|
|
/// Если придёт блок не с основными данными, а с overflows, то запомним его и повторим чтение.
|
|
|
|
|
while (true)
|
2015-09-07 07:40:14 +00:00
|
|
|
|
{
|
2015-09-07 20:08:02 +00:00
|
|
|
|
// std::cerr << "reading block\n";
|
|
|
|
|
Block block = input.stream->read();
|
2015-09-07 07:40:14 +00:00
|
|
|
|
|
2015-09-07 20:08:02 +00:00
|
|
|
|
if (!block)
|
|
|
|
|
{
|
|
|
|
|
// std::cerr << "input is exhausted\n";
|
|
|
|
|
input.is_exhausted = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-09-07 07:40:14 +00:00
|
|
|
|
|
2015-09-07 20:08:02 +00:00
|
|
|
|
if (block.info.bucket_num != -1)
|
|
|
|
|
{
|
|
|
|
|
/// Один из разрезанных блоков для двухуровневых данных.
|
|
|
|
|
// std::cerr << "block for bucket " << block.info.bucket_num << "\n";
|
2015-09-07 07:40:14 +00:00
|
|
|
|
|
2015-09-07 20:08:02 +00:00
|
|
|
|
has_two_level = true;
|
|
|
|
|
input.block = block;
|
|
|
|
|
}
|
|
|
|
|
else if (block.info.is_overflows)
|
|
|
|
|
{
|
|
|
|
|
// std::cerr << "block for overflows\n";
|
|
|
|
|
|
|
|
|
|
has_overflows = true;
|
|
|
|
|
input.overflow_block = block;
|
2015-09-07 07:40:14 +00:00
|
|
|
|
|
2015-09-07 20:08:02 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/// Блок для неразрезанных (одноуровневых) данных.
|
|
|
|
|
// std::cerr << "block without bucket\n";
|
|
|
|
|
|
|
|
|
|
input.block = block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
2015-09-07 07:40:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (true)
|
2015-07-30 23:41:02 +00:00
|
|
|
|
{
|
2015-09-07 07:40:14 +00:00
|
|
|
|
if (current_bucket_num == NUM_BUCKETS)
|
2015-07-30 23:41:02 +00:00
|
|
|
|
{
|
2015-09-07 20:08:02 +00:00
|
|
|
|
/// Обработали все основные данные. Остались, возможно, только overflows-блоки.
|
|
|
|
|
// std::cerr << "at end\n";
|
2015-07-30 23:41:02 +00:00
|
|
|
|
|
2015-09-07 07:40:14 +00:00
|
|
|
|
if (has_overflows)
|
2015-07-30 23:41:02 +00:00
|
|
|
|
{
|
2015-09-07 20:08:02 +00:00
|
|
|
|
// std::cerr << "merging overflows\n";
|
2015-09-07 07:40:14 +00:00
|
|
|
|
|
|
|
|
|
has_overflows = false;
|
|
|
|
|
BlocksList blocks_to_merge;
|
|
|
|
|
|
|
|
|
|
for (auto & input : inputs)
|
|
|
|
|
if (input.overflow_block)
|
|
|
|
|
blocks_to_merge.emplace_back(std::move(input.overflow_block));
|
|
|
|
|
|
|
|
|
|
return aggregator.mergeBlocks(blocks_to_merge, final);
|
2015-07-30 23:41:02 +00:00
|
|
|
|
}
|
2015-09-07 07:40:14 +00:00
|
|
|
|
else
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
else if (has_two_level)
|
|
|
|
|
{
|
2015-09-07 20:08:02 +00:00
|
|
|
|
/** Есть двухуровневые данные.
|
|
|
|
|
* Будем обрабатывать номера корзин по возрастанию.
|
|
|
|
|
* Найдём минимальный номер корзины, для которой есть данные,
|
|
|
|
|
* затем померджим эти данные.
|
|
|
|
|
*/
|
|
|
|
|
// std::cerr << "has two level\n";
|
2015-09-07 07:40:14 +00:00
|
|
|
|
|
|
|
|
|
int min_bucket_num = NUM_BUCKETS;
|
|
|
|
|
|
|
|
|
|
for (auto & input : inputs)
|
2015-07-30 23:41:02 +00:00
|
|
|
|
{
|
2015-09-07 20:08:02 +00:00
|
|
|
|
/// Изначально разрезанные (двухуровневые) блоки.
|
2015-09-07 07:40:14 +00:00
|
|
|
|
if (input.block.info.bucket_num != -1 && input.block.info.bucket_num < min_bucket_num)
|
|
|
|
|
min_bucket_num = input.block.info.bucket_num;
|
|
|
|
|
|
2015-09-07 20:08:02 +00:00
|
|
|
|
/// Ещё не разрезанный по корзинам блок. Разрезаем его и кладём результат в splitted_blocks.
|
2015-09-07 07:40:14 +00:00
|
|
|
|
if (input.block.info.bucket_num == -1 && input.block && input.splitted_blocks.empty())
|
|
|
|
|
{
|
2015-09-07 20:08:02 +00:00
|
|
|
|
LOG_TRACE(&Logger::get("MergingAggregatedMemoryEfficient"), "Having block without bucket: will split.");
|
2015-09-07 07:40:14 +00:00
|
|
|
|
|
|
|
|
|
input.splitted_blocks = aggregator.convertBlockToTwoLevel(input.block);
|
2015-09-07 20:08:02 +00:00
|
|
|
|
|
|
|
|
|
/** Нельзя уничтожать исходный блок.
|
|
|
|
|
* Потому что он владеет Arena с состояниями агрегатных функций,
|
|
|
|
|
* а splitted_blocks ей не владеют, но ссылаются на эти состояния.
|
|
|
|
|
*/
|
2015-09-07 07:40:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-07 20:08:02 +00:00
|
|
|
|
/// Блоки, которые мы получили разрезанием одноуровневых блоков.
|
2015-09-07 07:40:14 +00:00
|
|
|
|
if (!input.splitted_blocks.empty())
|
|
|
|
|
{
|
|
|
|
|
for (const auto & block : input.splitted_blocks)
|
|
|
|
|
{
|
|
|
|
|
if (block && block.info.bucket_num < min_bucket_num)
|
|
|
|
|
{
|
|
|
|
|
min_bucket_num = block.info.bucket_num;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-07-30 23:41:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-07 07:40:14 +00:00
|
|
|
|
current_bucket_num = min_bucket_num;
|
|
|
|
|
|
2015-09-07 20:08:02 +00:00
|
|
|
|
// std::cerr << "current_bucket_num = " << current_bucket_num << "\n";
|
2015-07-30 23:41:02 +00:00
|
|
|
|
|
2015-09-07 20:08:02 +00:00
|
|
|
|
/// Блоков с основными данными больше нет.
|
2015-09-07 07:40:14 +00:00
|
|
|
|
if (current_bucket_num == NUM_BUCKETS)
|
|
|
|
|
continue;
|
2015-07-30 23:41:02 +00:00
|
|
|
|
|
2015-09-07 20:08:02 +00:00
|
|
|
|
/// Теперь собираем блоки для current_bucket_num, чтобы их померджить.
|
2015-09-07 07:40:14 +00:00
|
|
|
|
BlocksList blocks_to_merge;
|
2015-07-30 23:41:02 +00:00
|
|
|
|
|
2015-09-07 07:40:14 +00:00
|
|
|
|
for (auto & input : inputs)
|
|
|
|
|
{
|
|
|
|
|
if (input.block.info.bucket_num == current_bucket_num)
|
|
|
|
|
{
|
2015-09-07 20:08:02 +00:00
|
|
|
|
// std::cerr << "having block for current_bucket_num\n";
|
2015-09-07 07:40:14 +00:00
|
|
|
|
|
|
|
|
|
blocks_to_merge.emplace_back(std::move(input.block));
|
|
|
|
|
input.block = Block();
|
|
|
|
|
}
|
|
|
|
|
else if (!input.splitted_blocks.empty() && input.splitted_blocks[min_bucket_num])
|
|
|
|
|
{
|
2015-09-07 20:08:02 +00:00
|
|
|
|
// std::cerr << "having splitted data for bucket\n";
|
2015-09-07 07:40:14 +00:00
|
|
|
|
|
|
|
|
|
blocks_to_merge.emplace_back(std::move(input.splitted_blocks[min_bucket_num]));
|
|
|
|
|
input.splitted_blocks[min_bucket_num] = Block();
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-07-30 23:41:02 +00:00
|
|
|
|
|
2015-09-07 07:40:14 +00:00
|
|
|
|
return aggregator.mergeBlocks(blocks_to_merge, final);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-09-07 20:08:02 +00:00
|
|
|
|
/// Есть только одноуровневые данные. Просто мерджим их.
|
|
|
|
|
// std::cerr << "don't have two level\n";
|
2015-07-30 23:41:02 +00:00
|
|
|
|
|
2015-09-07 07:40:14 +00:00
|
|
|
|
BlocksList blocks_to_merge;
|
2015-07-30 23:41:02 +00:00
|
|
|
|
|
2015-09-07 07:40:14 +00:00
|
|
|
|
for (auto & input : inputs)
|
|
|
|
|
if (input.block)
|
|
|
|
|
blocks_to_merge.emplace_back(std::move(input.block));
|
2015-07-30 23:41:02 +00:00
|
|
|
|
|
2015-09-07 07:40:14 +00:00
|
|
|
|
current_bucket_num = NUM_BUCKETS;
|
|
|
|
|
return aggregator.mergeBlocks(blocks_to_merge, final);
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-07-30 23:41:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
Aggregator aggregator;
|
|
|
|
|
bool final;
|
|
|
|
|
|
2015-09-07 07:40:14 +00:00
|
|
|
|
bool started = false;
|
|
|
|
|
bool has_two_level = false;
|
|
|
|
|
bool has_overflows = false;
|
|
|
|
|
int current_bucket_num = -1;
|
|
|
|
|
|
|
|
|
|
struct Input
|
|
|
|
|
{
|
|
|
|
|
BlockInputStreamPtr stream;
|
|
|
|
|
Block block;
|
|
|
|
|
Block overflow_block;
|
|
|
|
|
std::vector<Block> splitted_blocks;
|
|
|
|
|
bool is_exhausted = false;
|
|
|
|
|
|
|
|
|
|
Input(BlockInputStreamPtr & stream_) : stream(stream_) {}
|
|
|
|
|
};
|
2015-07-30 23:41:02 +00:00
|
|
|
|
|
2015-09-07 07:40:14 +00:00
|
|
|
|
std::vector<Input> inputs;
|
2015-07-30 23:41:02 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|