mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2025-05-11 10:25:47 +02:00
fix: Batch array length before sending its contents (#1287)
fix: Coalesce array contents to optimize response traffic. Also pull the latest helio. Fixes #1285 Signed-off-by: Roman Gershman <roman@dragonflydb.io>
This commit is contained in:
parent
6e21686406
commit
34c8939a3a
7 changed files with 65 additions and 9 deletions
2
helio
2
helio
|
@ -1 +1 @@
|
||||||
Subproject commit 288d85e4fad988994ff236fca8969e7751d19443
|
Subproject commit 724eb6441516fe07517ec1841e96e6b68f6ff183
|
|
@ -45,8 +45,9 @@ void SinkReplyBuilder::CloseConnection() {
|
||||||
|
|
||||||
void SinkReplyBuilder::Send(const iovec* v, uint32_t len) {
|
void SinkReplyBuilder::Send(const iovec* v, uint32_t len) {
|
||||||
DCHECK(sink_);
|
DCHECK(sink_);
|
||||||
|
constexpr uint32_t kMaxBatchCnt = 25;
|
||||||
|
|
||||||
if (should_batch_) {
|
if ((should_batch_ || should_aggregate_) && batch_cnt_ < kMaxBatchCnt) {
|
||||||
size_t total_size = batch_.size();
|
size_t total_size = batch_.size();
|
||||||
for (unsigned i = 0; i < len; ++i) {
|
for (unsigned i = 0; i < len; ++i) {
|
||||||
total_size += v[i].iov_len;
|
total_size += v[i].iov_len;
|
||||||
|
@ -57,6 +58,7 @@ void SinkReplyBuilder::Send(const iovec* v, uint32_t len) {
|
||||||
std::string_view src((char*)v[i].iov_base, v[i].iov_len);
|
std::string_view src((char*)v[i].iov_base, v[i].iov_len);
|
||||||
DVLOG(2) << "Appending to stream " << src;
|
DVLOG(2) << "Appending to stream " << src;
|
||||||
batch_.append(src.data(), src.size());
|
batch_.append(src.data(), src.size());
|
||||||
|
++batch_cnt_;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -64,10 +66,12 @@ void SinkReplyBuilder::Send(const iovec* v, uint32_t len) {
|
||||||
|
|
||||||
error_code ec;
|
error_code ec;
|
||||||
++io_write_cnt_;
|
++io_write_cnt_;
|
||||||
|
size_t bsize = 0;
|
||||||
for (unsigned i = 0; i < len; ++i) {
|
for (unsigned i = 0; i < len; ++i) {
|
||||||
io_write_bytes_ += v[i].iov_len;
|
bsize += v[i].iov_len;
|
||||||
}
|
}
|
||||||
|
io_write_bytes_ += bsize;
|
||||||
|
DVLOG(2) << "Writing " << bsize << " bytes of len " << len;
|
||||||
|
|
||||||
if (batch_.empty()) {
|
if (batch_.empty()) {
|
||||||
ec = sink_->Write(v, len);
|
ec = sink_->Write(v, len);
|
||||||
|
@ -75,6 +79,7 @@ void SinkReplyBuilder::Send(const iovec* v, uint32_t len) {
|
||||||
DVLOG(1) << "Sending batch to stream " << sink_ << "\n" << batch_;
|
DVLOG(1) << "Sending batch to stream " << sink_ << "\n" << batch_;
|
||||||
|
|
||||||
io_write_bytes_ += batch_.size();
|
io_write_bytes_ += batch_.size();
|
||||||
|
batch_cnt_ = 0;
|
||||||
|
|
||||||
iovec tmp[len + 1];
|
iovec tmp[len + 1];
|
||||||
tmp[0].iov_base = batch_.data();
|
tmp[0].iov_base = batch_.data();
|
||||||
|
@ -105,6 +110,20 @@ void SinkReplyBuilder::SendRawVec(absl::Span<const std::string_view> msg_vec) {
|
||||||
Send(arr.data(), msg_vec.size());
|
Send(arr.data(), msg_vec.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SinkReplyBuilder::StopAggregate() {
|
||||||
|
should_aggregate_ = false;
|
||||||
|
|
||||||
|
if (should_batch_ || batch_.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
error_code ec = sink_->Write(io::Buffer(batch_));
|
||||||
|
batch_.clear();
|
||||||
|
batch_cnt_ = 0;
|
||||||
|
|
||||||
|
if (ec)
|
||||||
|
ec_ = ec;
|
||||||
|
}
|
||||||
|
|
||||||
MCReplyBuilder::MCReplyBuilder(::io::Sink* sink) : SinkReplyBuilder(sink), noreply_(false) {
|
MCReplyBuilder::MCReplyBuilder(::io::Sink* sink) : SinkReplyBuilder(sink), noreply_(false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,6 +306,7 @@ void RedisReplyBuilder::SendLong(long num) {
|
||||||
|
|
||||||
void RedisReplyBuilder::SendScoredArray(const std::vector<std::pair<std::string, double>>& arr,
|
void RedisReplyBuilder::SendScoredArray(const std::vector<std::pair<std::string, double>>& arr,
|
||||||
bool with_scores) {
|
bool with_scores) {
|
||||||
|
ReplyAggregator agg(this);
|
||||||
if (!with_scores) {
|
if (!with_scores) {
|
||||||
StartArray(arr.size());
|
StartArray(arr.size());
|
||||||
for (const auto& p : arr) {
|
for (const auto& p : arr) {
|
||||||
|
@ -385,7 +405,13 @@ void RedisReplyBuilder::StartCollection(unsigned len, CollectionType type) {
|
||||||
type = ARRAY;
|
type = ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We do not want to send multiple packets for small responses because these
|
||||||
|
// trigger TCP-related artifacts (e.g. Nagle's algorithm) that slow down the delivery of the whole
|
||||||
|
// response.
|
||||||
|
bool prev = should_aggregate_;
|
||||||
|
should_aggregate_ |= (len > 0);
|
||||||
SendRaw(absl::StrCat(START_SYMBOLS[type], len, kCRLF));
|
SendRaw(absl::StrCat(START_SYMBOLS[type], len, kCRLF));
|
||||||
|
should_aggregate_ = prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This implementation a bit complicated because it uses vectorized
|
// This implementation a bit complicated because it uses vectorized
|
||||||
|
|
|
@ -86,12 +86,31 @@ class SinkReplyBuilder {
|
||||||
return err_count_;
|
return err_count_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ReplyAggregator {
|
||||||
|
explicit ReplyAggregator(SinkReplyBuilder* builder) : builder_(builder) {
|
||||||
|
builder_->StartAggregate();
|
||||||
|
}
|
||||||
|
|
||||||
|
~ReplyAggregator() {
|
||||||
|
builder_->StopAggregate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SinkReplyBuilder* builder_;
|
||||||
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void SendRaw(std::string_view str); // Sends raw without any formatting.
|
void SendRaw(std::string_view str); // Sends raw without any formatting.
|
||||||
void SendRawVec(absl::Span<const std::string_view> msg_vec);
|
void SendRawVec(absl::Span<const std::string_view> msg_vec);
|
||||||
|
|
||||||
void Send(const iovec* v, uint32_t len);
|
void Send(const iovec* v, uint32_t len);
|
||||||
|
|
||||||
|
void StartAggregate() {
|
||||||
|
should_aggregate_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StopAggregate();
|
||||||
|
|
||||||
std::string batch_;
|
std::string batch_;
|
||||||
::io::Sink* sink_;
|
::io::Sink* sink_;
|
||||||
std::error_code ec_;
|
std::error_code ec_;
|
||||||
|
@ -100,7 +119,11 @@ class SinkReplyBuilder {
|
||||||
size_t io_write_bytes_ = 0;
|
size_t io_write_bytes_ = 0;
|
||||||
absl::flat_hash_map<std::string, uint64_t> err_count_;
|
absl::flat_hash_map<std::string, uint64_t> err_count_;
|
||||||
|
|
||||||
bool should_batch_ = false;
|
bool should_batch_ : 1 = false;
|
||||||
|
|
||||||
|
// Similarly to batch mode but is controlled by at operation level.
|
||||||
|
bool should_aggregate_ : 2 = false;
|
||||||
|
uint32_t batch_cnt_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MCReplyBuilder : public SinkReplyBuilder {
|
class MCReplyBuilder : public SinkReplyBuilder {
|
||||||
|
@ -161,6 +184,7 @@ class RedisReplyBuilder : public SinkReplyBuilder {
|
||||||
bool with_scores);
|
bool with_scores);
|
||||||
|
|
||||||
void StartArray(unsigned len); // StartCollection(len, ARRAY)
|
void StartArray(unsigned len); // StartCollection(len, ARRAY)
|
||||||
|
|
||||||
virtual void StartCollection(unsigned len, CollectionType type);
|
virtual void StartCollection(unsigned len, CollectionType type);
|
||||||
|
|
||||||
static char* FormatDouble(double val, char* dest, unsigned dest_len);
|
static char* FormatDouble(double val, char* dest, unsigned dest_len);
|
||||||
|
|
|
@ -185,12 +185,11 @@ TEST_F(RedisReplyBuilderTest, TestMessageSend) {
|
||||||
builder_->SendOk();
|
builder_->SendOk();
|
||||||
ASSERT_EQ(TakePayload(), kOKMessage);
|
ASSERT_EQ(TakePayload(), kOKMessage);
|
||||||
builder_->StartArray(10);
|
builder_->StartArray(10);
|
||||||
ASSERT_EQ(TakePayload(), "*10\r\n");
|
|
||||||
sink_.Clear();
|
|
||||||
std::string_view hello_msg = "hello";
|
std::string_view hello_msg = "hello";
|
||||||
builder_->SendBulkString(hello_msg);
|
builder_->SendBulkString(hello_msg);
|
||||||
std::string expected_bulk_string =
|
std::string expected_bulk_string = absl::StrCat(
|
||||||
absl::StrCat(kBulkStringStart, std::to_string(hello_msg.size()), kCRLF, hello_msg, kCRLF);
|
"*10\r\n", kBulkStringStart, std::to_string(hello_msg.size()), kCRLF, hello_msg, kCRLF);
|
||||||
ASSERT_EQ(TakePayload(), expected_bulk_string);
|
ASSERT_EQ(TakePayload(), expected_bulk_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -775,6 +775,8 @@ void HSetFamily::HMGet(CmdArgList args, ConnectionContext* cntx) {
|
||||||
OpResult<vector<OptStr>> result = cntx->transaction->ScheduleSingleHopT(std::move(cb));
|
OpResult<vector<OptStr>> result = cntx->transaction->ScheduleSingleHopT(std::move(cb));
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
SinkReplyBuilder::ReplyAggregator agg(cntx->reply_builder());
|
||||||
|
|
||||||
(*cntx)->StartArray(result->size());
|
(*cntx)->StartArray(result->size());
|
||||||
for (const auto& val : *result) {
|
for (const auto& val : *result) {
|
||||||
if (val) {
|
if (val) {
|
||||||
|
@ -784,6 +786,8 @@ void HSetFamily::HMGet(CmdArgList args, ConnectionContext* cntx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (result.status() == OpStatus::KEY_NOTFOUND) {
|
} else if (result.status() == OpStatus::KEY_NOTFOUND) {
|
||||||
|
SinkReplyBuilder::ReplyAggregator agg(cntx->reply_builder());
|
||||||
|
|
||||||
(*cntx)->StartArray(args.size());
|
(*cntx)->StartArray(args.size());
|
||||||
for (unsigned i = 0; i < args.size(); ++i) {
|
for (unsigned i = 0; i < args.size(); ++i) {
|
||||||
(*cntx)->SendNull();
|
(*cntx)->SendNull();
|
||||||
|
|
|
@ -954,6 +954,7 @@ void ListFamily::LPos(CmdArgList args, ConnectionContext* cntx) {
|
||||||
(*cntx)->SendLong((*result)[0]);
|
(*cntx)->SendLong((*result)[0]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
SinkReplyBuilder::ReplyAggregator agg(cntx->reply_builder());
|
||||||
(*cntx)->StartArray(result->size());
|
(*cntx)->StartArray(result->size());
|
||||||
const auto& array = result.value();
|
const auto& array = result.value();
|
||||||
for (const auto& v : array) {
|
for (const auto& v : array) {
|
||||||
|
|
|
@ -1524,6 +1524,7 @@ void Service::Exec(CmdArgList args, ConnectionContext* cntx) {
|
||||||
Transaction* trans = cntx->transaction;
|
Transaction* trans = cntx->transaction;
|
||||||
cntx->transaction = nullptr;
|
cntx->transaction = nullptr;
|
||||||
|
|
||||||
|
SinkReplyBuilder::ReplyAggregator agg(rb);
|
||||||
rb->StartArray(body.size());
|
rb->StartArray(body.size());
|
||||||
for (auto& scmd : body) {
|
for (auto& scmd : body) {
|
||||||
arg_vec.resize(scmd.NumArgs() + 1);
|
arg_vec.resize(scmd.NumArgs() + 1);
|
||||||
|
@ -1549,6 +1550,7 @@ void Service::Exec(CmdArgList args, ConnectionContext* cntx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
VLOG(1) << "StartExec " << exec_info.body.size();
|
VLOG(1) << "StartExec " << exec_info.body.size();
|
||||||
|
SinkReplyBuilder::ReplyAggregator agg(rb);
|
||||||
rb->StartArray(exec_info.body.size());
|
rb->StartArray(exec_info.body.size());
|
||||||
|
|
||||||
if (!exec_info.body.empty()) {
|
if (!exec_info.body.empty()) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue