2021-03-04 11:10:21 +00:00
|
|
|
#include <IO/AsynchronousInsertionQueue.h>
|
|
|
|
|
|
|
|
#include <Core/Settings.h>
|
|
|
|
#include <DataStreams/BlockIO.h>
|
|
|
|
#include <DataStreams/IBlockStream_fwd.h>
|
2021-03-17 14:11:47 +00:00
|
|
|
#include <DataStreams/InputStreamFromASTInsertQuery.h>
|
2021-04-21 16:56:06 +00:00
|
|
|
#include <DataStreams/copyData.h>
|
2021-03-04 11:10:21 +00:00
|
|
|
#include <IO/ConcatReadBuffer.h>
|
|
|
|
#include <IO/ReadBufferFromMemory.h>
|
2021-03-17 14:11:47 +00:00
|
|
|
#include <IO/ReadBufferFromString.h>
|
2021-03-04 11:10:21 +00:00
|
|
|
#include <IO/copyData.h>
|
|
|
|
#include <Parsers/ASTInsertQuery.h>
|
2021-04-21 16:56:06 +00:00
|
|
|
#include <Parsers/formatAST.h>
|
2021-03-04 11:10:21 +00:00
|
|
|
#include <Common/getNumberOfPhysicalCPUCores.h>
|
|
|
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
{
|
|
|
|
|
|
|
|
struct AsynchronousInsertQueue::InsertData
|
|
|
|
{
|
|
|
|
std::mutex mutex;
|
|
|
|
std::list<std::string> data;
|
|
|
|
size_t size = 0;
|
|
|
|
BlockIO io;
|
2021-03-17 14:11:47 +00:00
|
|
|
|
|
|
|
/// Timestamp of the first insert into queue, or after the last queue dump.
|
|
|
|
/// Used to detect for how long the queue is active, so we can dump it by timer.
|
2021-04-19 14:51:26 +00:00
|
|
|
std::chrono::time_point<std::chrono::steady_clock> first_update = std::chrono::steady_clock::now();
|
2021-03-17 14:11:47 +00:00
|
|
|
|
|
|
|
/// Timestamp of the last insert into queue.
|
|
|
|
/// Used to detect for how long the queue is stale, so we can dump it by another timer.
|
|
|
|
std::chrono::time_point<std::chrono::steady_clock> last_update;
|
|
|
|
|
|
|
|
/// Indicates that the BlockIO should be updated, because we can't read/write prefix and suffix more than once.
|
|
|
|
bool reset = false;
|
2021-03-04 11:10:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
std::size_t AsynchronousInsertQueue::InsertQueryHash::operator() (const InsertQuery & query) const
|
|
|
|
{
|
|
|
|
const auto * insert_query = query.query->as<ASTInsertQuery>();
|
|
|
|
std::size_t hash = 0;
|
|
|
|
|
|
|
|
hash ^= std::hash<String>()(insert_query->table_id.getFullTableName());
|
|
|
|
hash ^= std::hash<String>()(insert_query->format);
|
|
|
|
// TODO: insert_query->columns
|
|
|
|
// TODO: insert_query->table_function
|
|
|
|
// TODO: insert_query->settings_ast
|
|
|
|
|
|
|
|
// TODO: some of query.settings
|
|
|
|
|
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AsynchronousInsertQueue::InsertQueryEquality::operator() (const InsertQuery & query1, const InsertQuery & query2) const
|
|
|
|
{
|
|
|
|
const auto * insert_query1 = query1.query->as<ASTInsertQuery>();
|
|
|
|
const auto * insert_query2 = query2.query->as<ASTInsertQuery>();
|
|
|
|
|
|
|
|
if (insert_query1->table_id != insert_query2->table_id)
|
|
|
|
return false;
|
|
|
|
if (insert_query1->format != insert_query2->format)
|
|
|
|
return false;
|
|
|
|
// TODO: same fields as in InsertQueryHash.
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-04-19 14:51:26 +00:00
|
|
|
AsynchronousInsertQueue::AsynchronousInsertQueue(size_t pool_size, size_t max_data_size_, const Timeout & timeouts)
|
|
|
|
: max_data_size(max_data_size_)
|
|
|
|
, busy_timeout(timeouts.busy)
|
|
|
|
, stale_timeout(timeouts.stale)
|
|
|
|
, lock(RWLockImpl::create())
|
|
|
|
, queue(new Queue)
|
|
|
|
, pool(pool_size)
|
|
|
|
, dump_by_first_update_thread(&AsynchronousInsertQueue::busyCheck, this)
|
2021-03-04 11:10:21 +00:00
|
|
|
{
|
2021-04-19 19:16:34 +00:00
|
|
|
using namespace std::chrono;
|
|
|
|
|
2021-04-21 13:16:16 +00:00
|
|
|
assert(pool_size);
|
|
|
|
|
2021-04-19 19:16:34 +00:00
|
|
|
if (stale_timeout > 0s)
|
|
|
|
dump_by_last_update_thread = ThreadFromGlobalPool(&AsynchronousInsertQueue::staleCheck, this);
|
2021-03-04 11:10:21 +00:00
|
|
|
}
|
|
|
|
|
2021-04-19 14:51:26 +00:00
|
|
|
AsynchronousInsertQueue::~AsynchronousInsertQueue()
|
|
|
|
{
|
|
|
|
/// TODO: add a setting for graceful shutdown.
|
|
|
|
|
|
|
|
shutdown = true;
|
|
|
|
|
|
|
|
assert(dump_by_first_update_thread.joinable());
|
|
|
|
dump_by_first_update_thread.join();
|
|
|
|
|
2021-04-19 19:16:34 +00:00
|
|
|
if (dump_by_last_update_thread.joinable())
|
|
|
|
dump_by_last_update_thread.join();
|
2021-04-19 14:51:26 +00:00
|
|
|
|
|
|
|
pool.wait();
|
|
|
|
}
|
|
|
|
|
2021-03-04 11:10:21 +00:00
|
|
|
bool AsynchronousInsertQueue::push(ASTInsertQuery * query, const Settings & settings)
|
|
|
|
{
|
|
|
|
auto read_lock = lock->getLock(RWLockImpl::Read, String());
|
|
|
|
|
2021-03-17 14:11:47 +00:00
|
|
|
auto it = queue->find(InsertQuery{query->shared_from_this(), settings});
|
2021-04-19 14:51:26 +00:00
|
|
|
|
2021-04-19 17:49:02 +00:00
|
|
|
if (it != queue->end())
|
2021-03-04 11:10:21 +00:00
|
|
|
{
|
2021-04-19 17:49:02 +00:00
|
|
|
std::unique_lock<std::mutex> data_lock(it->second->mutex);
|
|
|
|
|
|
|
|
if (it->second->reset)
|
|
|
|
return false;
|
|
|
|
|
2021-03-04 11:10:21 +00:00
|
|
|
pushImpl(query, it);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AsynchronousInsertQueue::push(ASTInsertQuery * query, BlockIO && io, const Settings & settings)
|
|
|
|
{
|
|
|
|
auto write_lock = lock->getLock(RWLockImpl::Write, String());
|
|
|
|
|
2021-04-19 14:51:26 +00:00
|
|
|
InsertQuery key{query->shared_from_this(), settings};
|
|
|
|
auto it = queue->find(key);
|
2021-03-17 14:11:47 +00:00
|
|
|
if (it == queue->end())
|
2021-03-04 11:10:21 +00:00
|
|
|
{
|
2021-03-17 14:11:47 +00:00
|
|
|
it = queue->insert({key, std::make_shared<InsertData>()}).first;
|
2021-03-04 11:10:21 +00:00
|
|
|
it->second->io = std::move(io);
|
|
|
|
}
|
2021-04-19 14:51:26 +00:00
|
|
|
else if (it->second->reset)
|
2021-03-17 14:11:47 +00:00
|
|
|
{
|
2021-04-19 14:51:26 +00:00
|
|
|
it->second = std::make_shared<InsertData>();
|
2021-03-17 14:11:47 +00:00
|
|
|
it->second->io = std::move(io);
|
|
|
|
}
|
2021-03-04 11:10:21 +00:00
|
|
|
|
2021-04-19 17:49:02 +00:00
|
|
|
std::unique_lock<std::mutex> data_lock(it->second->mutex);
|
2021-03-04 11:10:21 +00:00
|
|
|
pushImpl(query, it);
|
|
|
|
}
|
|
|
|
|
2021-04-19 14:51:26 +00:00
|
|
|
void AsynchronousInsertQueue::busyCheck()
|
|
|
|
{
|
|
|
|
auto timeout = busy_timeout;
|
|
|
|
|
|
|
|
while (!shutdown)
|
|
|
|
{
|
|
|
|
std::this_thread::sleep_for(timeout);
|
|
|
|
|
|
|
|
auto read_lock = lock->getLock(RWLockImpl::Read, String());
|
|
|
|
|
|
|
|
/// TODO: use priority queue instead of raw unsorted queue.
|
|
|
|
timeout = busy_timeout;
|
|
|
|
for (auto & [_, data] : *queue)
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> data_lock(data->mutex);
|
|
|
|
|
|
|
|
auto lag = std::chrono::steady_clock::now() - data->first_update;
|
|
|
|
|
|
|
|
if (lag >= busy_timeout)
|
|
|
|
pool.scheduleOrThrowOnError([data = data] { processData(data); });
|
|
|
|
else
|
|
|
|
timeout = std::min(timeout, std::chrono::ceil<std::chrono::seconds>(busy_timeout - lag));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AsynchronousInsertQueue::staleCheck()
|
|
|
|
{
|
2021-04-21 16:56:06 +00:00
|
|
|
while (!shutdown)
|
2021-04-19 14:51:26 +00:00
|
|
|
{
|
|
|
|
std::this_thread::sleep_for(stale_timeout);
|
|
|
|
|
|
|
|
auto read_lock = lock->getLock(RWLockImpl::Read, String());
|
|
|
|
|
|
|
|
for (auto & [_, data] : *queue)
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> data_lock(data->mutex);
|
|
|
|
|
|
|
|
auto lag = std::chrono::steady_clock::now() - data->last_update;
|
|
|
|
|
|
|
|
if (lag >= stale_timeout)
|
|
|
|
pool.scheduleOrThrowOnError([data = data] { processData(data); });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-04 11:10:21 +00:00
|
|
|
void AsynchronousInsertQueue::pushImpl(ASTInsertQuery * query, QueueIterator & it)
|
|
|
|
{
|
2021-04-19 14:51:26 +00:00
|
|
|
ConcatReadBuffer concat_buf;
|
2021-03-04 11:10:21 +00:00
|
|
|
|
|
|
|
auto ast_buf = std::make_unique<ReadBufferFromMemory>(query->data, query->data ? query->end - query->data : 0);
|
2021-03-17 14:11:47 +00:00
|
|
|
|
2021-03-04 11:10:21 +00:00
|
|
|
if (query->data)
|
2021-04-19 14:51:26 +00:00
|
|
|
concat_buf.appendBuffer(std::move(ast_buf));
|
2021-03-04 11:10:21 +00:00
|
|
|
|
|
|
|
if (query->tail)
|
2021-04-19 14:51:26 +00:00
|
|
|
concat_buf.appendBuffer(wrapReadBufferReference(*query->tail));
|
2021-03-04 11:10:21 +00:00
|
|
|
|
|
|
|
/// NOTE: must not read from |query->tail| before read all between |query->data| and |query->end|.
|
|
|
|
|
|
|
|
/// It's important to read the whole data per query as a single chunk, so we can safely drop it in case of parsing failure.
|
|
|
|
auto & new_data = it->second->data.emplace_back();
|
|
|
|
new_data.reserve(concat_buf.totalSize());
|
|
|
|
WriteBufferFromString write_buf(new_data);
|
|
|
|
|
|
|
|
copyData(concat_buf, write_buf);
|
|
|
|
it->second->size += concat_buf.count();
|
|
|
|
it->second->last_update = std::chrono::steady_clock::now();
|
|
|
|
|
2021-03-17 14:11:47 +00:00
|
|
|
String query_string;
|
|
|
|
{
|
|
|
|
WriteBufferFromString buf(query_string);
|
|
|
|
formatAST(*query, buf);
|
|
|
|
}
|
|
|
|
LOG_INFO(&Poco::Logger::get("AsynchronousInsertQueue"), "Queue size {} for query '{}'", it->second->size, query_string);
|
|
|
|
|
2021-03-04 11:10:21 +00:00
|
|
|
if (it->second->size > max_data_size)
|
2021-03-17 14:11:47 +00:00
|
|
|
/// Since we're under lock here, it's safe to pass-by-copy the shared_ptr
|
2021-03-04 11:10:21 +00:00
|
|
|
/// without a race with the cleanup thread, which may reset last shared_ptr instance.
|
2021-03-17 14:11:47 +00:00
|
|
|
pool.scheduleOrThrowOnError([data = it->second] { processData(data); });
|
2021-03-04 11:10:21 +00:00
|
|
|
}
|
|
|
|
|
2021-03-17 14:11:47 +00:00
|
|
|
// static
|
2021-03-04 11:10:21 +00:00
|
|
|
void AsynchronousInsertQueue::processData(std::shared_ptr<InsertData> data)
|
|
|
|
{
|
2021-03-17 14:11:47 +00:00
|
|
|
std::unique_lock<std::mutex> data_lock(data->mutex);
|
|
|
|
|
2021-04-19 14:51:26 +00:00
|
|
|
if (data->reset)
|
|
|
|
return;
|
|
|
|
|
2021-03-17 14:11:47 +00:00
|
|
|
auto in = std::dynamic_pointer_cast<InputStreamFromASTInsertQuery>(data->io.in);
|
|
|
|
assert(in);
|
|
|
|
|
2021-04-19 14:51:26 +00:00
|
|
|
auto log_progress = [](const Block & block)
|
|
|
|
{
|
|
|
|
LOG_INFO(&Poco::Logger::get("AsynchronousInsertQueue"), "Flushed {} rows", block.rows());
|
|
|
|
};
|
|
|
|
|
2021-03-17 14:11:47 +00:00
|
|
|
for (const auto & datum : data->data)
|
|
|
|
in->appendBuffer(std::make_unique<ReadBufferFromString>(datum));
|
2021-04-19 14:51:26 +00:00
|
|
|
copyData(*in, *data->io.out, [] {return false;}, log_progress);
|
2021-03-17 14:11:47 +00:00
|
|
|
|
2021-04-21 16:56:06 +00:00
|
|
|
data->io = BlockIO(); /// Release all potential table locks
|
2021-03-17 14:11:47 +00:00
|
|
|
data->reset = true;
|
2021-03-04 11:10:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|