Skip to content

Commit 3c8f1f0

Browse files
authored
chore(tiering): Optimize serialization (#6164)
1 parent 6926f23 commit 3c8f1f0

17 files changed

+168
-139
lines changed

src/core/compact_object.cc

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// #define XXH_INLINE_ALL
88
#include <xxhash.h>
99

10+
#include <array>
11+
1012
extern "C" {
1113
#include "redis/intset.h"
1214
#include "redis/listpack.h"
@@ -1463,8 +1465,7 @@ bool CompactObj::CmpEncoded(string_view sv) const {
14631465
// has only 9-10 bytes in its inline prefix storage.
14641466
DCHECK_GT(sv.size(), 16u); // we would not be in SMALL_TAG, otherwise.
14651467

1466-
string_view slice[2];
1467-
u_.small_str.Get(slice);
1468+
auto slice = u_.small_str.Get();
14681469
DCHECK_LT(slice[0].size(), 14u);
14691470

14701471
uint8_t tmpbuf[14];
@@ -1587,19 +1588,17 @@ void CompactObj::EncodeString(string_view str, bool is_key) {
15871588
u_.r_obj.SetString(encoded, tl.local_mr);
15881589
}
15891590

1590-
StringOrView CompactObj::GetRawString() const {
1591+
std::array<std::string_view, 2> CompactObj::GetRawString() const {
15911592
DCHECK(!IsExternal());
15921593

15931594
if (taglen_ == ROBJ_TAG) {
15941595
CHECK_EQ(OBJ_STRING, u_.r_obj.type());
15951596
DCHECK_EQ(OBJ_ENCODING_RAW, u_.r_obj.encoding());
1596-
return StringOrView::FromView(u_.r_obj.AsView());
1597+
return {u_.r_obj.AsView(), {}};
15971598
}
15981599

15991600
if (taglen_ == SMALL_TAG) {
1600-
string tmp;
1601-
u_.small_str.Get(&tmp);
1602-
return StringOrView::FromString(std::move(tmp));
1601+
return u_.small_str.Get();
16031602
}
16041603

16051604
LOG(FATAL) << "Unsupported tag for GetRawString(): " << int(taglen_);

src/core/compact_object.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,9 @@ class CompactObj {
426426
memory_resource()->deallocate(ptr, sizeof(T), alignof(T));
427427
}
428428

429-
// returns raw (non-decoded) string. Used to bypass decoding layer.
429+
// Return raw (non-decoded) string as two views. First is guaranteed to be non-empty.
430430
// Precondition: the object is a non-inline string.
431-
StringOrView GetRawString() const;
431+
std::array<std::string_view, 2> GetRawString() const;
432432

433433
StrEncoding GetStrEncoding() const {
434434
return StrEncoding{mask_bits_.encoding, bool(huffman_domain_)};

src/core/compact_object_test.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,8 @@ TEST_F(CompactObjectTest, StrEncodingAndMaterialize) {
612612
obj.SetString(test_str, false);
613613

614614
// Test StrEncoding helper
615-
string raw_str = obj.GetRawString().Take();
615+
auto strs = obj.GetRawString();
616+
string raw_str = string{strs[0]} + string{strs[1]};
616617
CompactObj::StrEncoding enc = obj.GetStrEncoding();
617618
EXPECT_EQ(test_str, enc.Decode(raw_str).Take());
618619

src/core/detail/listpack_wrap.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ ListpackWrap::Iterator ListpackWrap::end() const {
118118
return Iterator{lp_, nullptr, intbuf_};
119119
}
120120

121+
size_t ListpackWrap::UsedBytes() const {
122+
return lpBytes(lp_);
123+
}
124+
121125
std::string_view ListpackWrap::GetView(uint8_t* lp_it, uint8_t int_buf[]) {
122126
int64_t ele_len = 0;
123127
uint8_t* elem = lpGet(lp_it, &ele_len, int_buf);

src/core/detail/listpack_wrap.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ struct ListpackWrap {
5959
Iterator begin() const;
6060
Iterator end() const;
6161
size_t size() const; // number of entries
62+
size_t UsedBytes() const;
6263

6364
// Get view from raw listpack iterator
6465
static std::string_view GetView(uint8_t* lp_it, uint8_t int_buf[]);

src/core/small_string.cc

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,11 @@ bool SmallString::Equal(const SmallString& os) const {
107107
if (size_ != os.size_)
108108
return false;
109109

110-
string_view me[2], other[2];
111-
Get(me);
112-
os.Get(other);
113-
114-
return me[0] == other[0] && me[1] == other[1];
110+
return Get() == os.Get();
115111
}
116112

117113
uint64_t SmallString::HashCode() const {
118-
string_view slice[2];
119-
Get(slice);
114+
array<string_view, 2> slice = Get();
120115

121116
XXH3_state_t* state = tl.xxh_state.get();
122117
XXH3_64bits_reset_withSeed(state, kHashSeed);
@@ -126,17 +121,18 @@ uint64_t SmallString::HashCode() const {
126121
return XXH3_64bits_digest(state);
127122
}
128123

129-
void SmallString::Get(string_view dest[2]) const {
124+
array<string_view, 2> SmallString::Get() const {
130125
DCHECK(size_);
131126

127+
array<string_view, 2> dest;
132128
dest[0] = string_view{prefix_, kPrefLen};
133129
uint8_t* ptr = tl.seg_alloc->Translate(small_ptr_);
134130
dest[1] = string_view{reinterpret_cast<char*>(ptr), size_ - kPrefLen};
131+
return dest;
135132
}
136133

137134
void SmallString::Get(char* out) const {
138-
string_view strs[2];
139-
Get(strs);
135+
auto strs = Get();
140136
memcpy(out, strs[0].data(), strs[0].size());
141137
memcpy(out + strs[0].size(), strs[1].data(), strs[1].size());
142138
}

src/core/small_string.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//
44
#pragma once
55

6+
#include <array>
67
#include <cstdint>
78
#include <string_view>
89

@@ -31,7 +32,7 @@ class SmallString {
3132
uint64_t HashCode() const;
3233
uint16_t MallocUsed() const;
3334

34-
void Get(std::string_view dest[2]) const;
35+
std::array<std::string_view, 2> Get() const;
3536
void Get(char* out) const;
3637
void Get(std::string* dest) const;
3738

src/server/tiered_storage.cc

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include "absl/cleanup/cleanup.h"
1616
#include "absl/flags/internal/flag.h"
17+
#include "absl/functional/bind_front.h"
1718
#include "base/flag_utils.h"
1819
#include "base/flags.h"
1920
#include "base/logging.h"
@@ -82,23 +83,24 @@ tiering::DiskSegment FromCoolItem(const PrimeValue::CoolItem& item) {
8283
}
8384

8485
// Determine required byte size and encoding type based on value.
85-
// TODO(vlad): Maybe split into different accessors?
8686
// Do NOT enforce rules depending on dynamic runtime values as this is called
8787
// when scheduling stash and just before succeeeding and is expected to return the same results
8888
pair<size_t /*size*/, CompactObj::ExternalRep> DetermineSerializationParams(const PrimeValue& pv) {
8989
switch (pv.ObjType()) {
90-
case OBJ_STRING:
90+
case OBJ_STRING: {
9191
if (pv.IsInline())
9292
return {};
93-
return std::make_pair(pv.GetRawString().view().size(), CompactObj::ExternalRep::STRING);
94-
case OBJ_HASH:
93+
auto strs = pv.GetRawString();
94+
return std::make_pair(strs[0].size() + strs[1].size(), CompactObj::ExternalRep::STRING);
95+
}
96+
case OBJ_HASH: {
9597
if (pv.Encoding() == kEncodingListPack) {
96-
auto* lp = static_cast<uint8_t*>(pv.RObjPtr());
97-
size_t bytes = 4 + lpBytes(lp); // encoded length and data bytes
98-
bytes += lpLength(lp) * 2 * 4; // 4 bytes for encoded key/value lengths
99-
return std::make_pair(bytes, CompactObj::ExternalRep::SERIALIZED_MAP);
98+
detail::ListpackWrap lw{static_cast<uint8_t*>(pv.RObjPtr())};
99+
return std::make_pair(tiering::SerializedMap::EstimateSize(lw.UsedBytes(), lw.size()),
100+
CompactObj::ExternalRep::SERIALIZED_MAP);
100101
}
101102
return {};
103+
}
102104
default:
103105
return {};
104106
};
@@ -108,18 +110,17 @@ size_t Serialize(CompactObj::ExternalRep rep, const PrimeValue& pv, io::MutableB
108110
DCHECK_LE(DetermineSerializationParams(pv).first, buffer.size());
109111
switch (rep) {
110112
case CompactObj::ExternalRep::STRING: {
111-
auto sv = pv.GetRawString();
112-
memcpy(buffer.data(), sv.view().data(), sv.view().size());
113-
return sv.view().size();
113+
auto strs = pv.GetRawString();
114+
memcpy(buffer.data(), strs[0].data(), strs[0].size());
115+
if (!strs[1].empty())
116+
memcpy(buffer.data() + strs[0].size(), strs[1].data(), strs[1].size());
117+
return strs[0].size() + strs[1].size();
114118
}
115119
case CompactObj::ExternalRep::SERIALIZED_MAP: {
116120
DCHECK_EQ(pv.Encoding(), kEncodingListPack);
117-
118-
// TODO(vlad): Optimize copy for serialization
119121
detail::ListpackWrap lw{static_cast<uint8_t*>(pv.RObjPtr())};
120-
vector<pair<string, string>> entries(lw.begin(), lw.end());
121122
return tiering::SerializedMap::Serialize(
122-
entries, {reinterpret_cast<char*>(buffer.data()), buffer.length()});
123+
lw, {reinterpret_cast<char*>(buffer.data()), buffer.length()});
123124
}
124125
};
125126
return 0;
@@ -447,11 +448,9 @@ std::optional<util::fb2::Future<bool>> TieredStorage::TryStash(DbIndex dbid, str
447448
if (!ShouldStash(*value))
448449
return {};
449450

450-
// This invariant should always hold because ShouldStash tests for IoPending flag.
451-
CHECK(!bins_->IsPending(dbid, key));
451+
CHECK(!bins_->IsPending(dbid, key)); // Because has stash pending is false (ShouldStash checks)
452452

453-
// TODO: When we are low on memory we should introduce a back-pressure, to avoid OOMs
454-
// with a lot of underutilized disk space.
453+
// Limit write depth. TODO: Provide backpressure?
455454
if (op_manager_->GetStats().pending_stash_cnt >= config_.write_depth_limit) {
456455
++stats_.stash_overflow_cnt;
457456
return {};
@@ -463,35 +462,22 @@ std::optional<util::fb2::Future<bool>> TieredStorage::TryStash(DbIndex dbid, str
463462
tiering::OpManager::EntryId id;
464463
error_code ec;
465464

466-
value->SetStashPending(true);
465+
value->SetStashPending(true); // Optimistically set ahead, unset in case of error
466+
467467
if (OccupiesWholePages(est_size)) { // large enough for own page
468468
id = KeyRef(dbid, key);
469-
if (auto prepared = op_manager_->PrepareStash(est_size); prepared) {
470-
auto [offset, buf] = *prepared;
471-
size_t written = Serialize(rep, *value, buf.bytes);
472-
tiering::DiskSegment segment{offset, written};
473-
op_manager_->Stash(id, segment, buf);
474-
} else {
475-
ec = prepared.error();
476-
}
469+
auto serialize = absl::bind_front(Serialize, rep, cref(*value));
470+
ec = op_manager_->PrepareAndStash(id, est_size, serialize);
477471
} else if (auto bin = bins_->Stash(dbid, key, SerializeToString(*value)); bin) {
478-
id = bin->first;
479-
// TODO(vlad): Write bin to prepared buffer instead of allocating one
480-
if (auto prepared = op_manager_->PrepareStash(bin->second.length()); prepared) {
481-
auto [offset, buf] = *prepared;
482-
memcpy(buf.bytes.data(), bin->second.data(), bin->second.size());
483-
tiering::DiskSegment segment{offset, bin->second.size()};
484-
op_manager_->Stash(id, segment, buf);
485-
} else {
486-
ec = prepared.error();
487-
bins_->ReportStashAborted(bin->first);
488-
}
472+
id = bin->id;
473+
auto serialize = absl::bind_front(&tiering::SmallBins::SerializeBin, bins_.get(), &*bin);
474+
ec = op_manager_->PrepareAndStash(id, 4_KB, serialize);
489475
} else {
490-
return {}; // silently added to bin
476+
return {}; // added to bin, no operations pending
491477
}
492478

479+
// Set stash pending to false on single value or whole bin
493480
if (ec) {
494-
value->SetStashPending(false);
495481
LOG_IF(ERROR, ec != errc::file_too_large) << "Stash failed immediately" << ec.message();
496482
visit([this](auto id) { op_manager_->ClearIoPending(id); }, id);
497483
return {};

src/server/tiering/op_manager.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ void OpManager::Stash(EntryId id_ref, tiering::DiskSegment segment, util::fb2::U
9999
storage_.Stash(segment, buf, std::move(io_cb));
100100
}
101101

102+
std::error_code OpManager::PrepareAndStash(EntryId id, size_t length,
103+
const std::function<size_t(io::MutableBytes)>& writer) {
104+
auto buf = PrepareStash(length);
105+
if (!buf.has_value())
106+
return buf.error();
107+
108+
size_t written = writer(buf->second.bytes);
109+
Stash(id, {buf->first, written}, buf->second);
110+
return {};
111+
}
112+
102113
OpManager::ReadOp& OpManager::PrepareRead(DiskSegment aligned_segment) {
103114
DCHECK_EQ(aligned_segment.offset % kPageSize, 0u);
104115
DCHECK_EQ(aligned_segment.length % kPageSize, 0u);

src/server/tiering/op_manager.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ class OpManager {
6868
// Stash value to be offloaded. It is opaque to OpManager.
6969
void Stash(EntryId id, tiering::DiskSegment segment, util::fb2::UringBuf buf);
7070

71+
// PrepareStash + Stash via function
72+
std::error_code PrepareAndStash(
73+
EntryId id, size_t length,
74+
const std::function<size_t /*written*/ (io::MutableBytes)>& writer);
75+
7176
Stats GetStats() const;
7277

7378
protected:

0 commit comments

Comments
 (0)