Skip to content

Commit 5bbaa0d

Browse files
authored
feat(tiering): Serialized map (#5896)
1 parent 3b61568 commit 5bbaa0d

File tree

4 files changed

+185
-2
lines changed

4 files changed

+185
-2
lines changed

src/server/CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,16 @@ set_property(SOURCE dfly_main.cc APPEND PROPERTY COMPILE_DEFINITIONS
2525
SOURCE_PATH_FROM_BUILD_ENV=${CMAKE_SOURCE_DIR})
2626

2727
if (WITH_TIERING)
28-
SET(TX_LINUX_SRCS tiering/disk_storage.cc tiering/op_manager.cc tiering/small_bins.cc
29-
tiering/external_alloc.cc tiering/decoders.cc)
28+
SET(TX_LINUX_SRCS tiering/disk_storage.cc tiering/op_manager.cc
29+
tiering/small_bins.cc tiering/external_alloc.cc tiering/serialized_map.cc tiering/decoders.cc)
3030

3131
add_executable(dfly_bench dfly_bench.cc)
3232
cxx_link(dfly_bench dfly_parser_lib fibers2 absl::random_random redis_lib)
3333
cxx_test(tiering/disk_storage_test dfly_test_lib LABELS DFLY)
3434
cxx_test(tiering/op_manager_test dfly_test_lib LABELS DFLY)
3535
cxx_test(tiering/small_bins_test dfly_test_lib LABELS DFLY)
3636
cxx_test(tiering/external_alloc_test dfly_test_lib LABELS DFLY)
37+
cxx_test(tiering/serialized_map_test dfly_test_lib LABELS DFLY)
3738
endif()
3839

3940
# Optionally include command families as needed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#include "server/tiering/serialized_map.h"
2+
3+
#include <absl/base/internal/endian.h>
4+
5+
#include "base/logging.h"
6+
7+
namespace dfly::tiering {
8+
9+
SerializedMap::Iterator& SerializedMap::Iterator::operator++() {
10+
slice_.remove_prefix(8 + key_.size() + value_.size());
11+
Read();
12+
return *this;
13+
}
14+
15+
SerializedMap::Iterator::Iterator(std::string_view buffer) : slice_{buffer} {
16+
Read();
17+
}
18+
19+
void SerializedMap::Iterator::Read() {
20+
if (slice_.empty())
21+
return;
22+
23+
uint32_t key_len = absl::little_endian::Load32(slice_.data());
24+
uint32_t value_len = absl::little_endian::Load32(slice_.data() + 4);
25+
key_ = {slice_.data() + 8, key_len};
26+
value_ = {slice_.data() + 8 + key_len, value_len};
27+
}
28+
29+
SerializedMap::SerializedMap(std::string_view slice) {
30+
size_ = absl::little_endian::Load32(slice.data());
31+
DCHECK_GT(size_, 0u);
32+
slice_ = slice;
33+
}
34+
35+
SerializedMap::Iterator SerializedMap::Find(std::string_view key) const {
36+
return std::find_if(begin(), end(), [key](auto p) { return p.first == key; });
37+
}
38+
39+
SerializedMap::Iterator SerializedMap::begin() const {
40+
return Iterator{slice_.substr(4)};
41+
}
42+
43+
SerializedMap::Iterator SerializedMap::end() const {
44+
return Iterator{slice_.substr(slice_.size(), 0)};
45+
}
46+
47+
size_t SerializedMap::size() const {
48+
return size_;
49+
}
50+
51+
constexpr size_t kLenBytes = 4;
52+
53+
size_t SerializedMap::SerializeSize(Input input) {
54+
size_t out = kLenBytes; // number of entries
55+
for (const auto& [key, value] : input)
56+
out += kLenBytes * 2 /* string lengts */ + key.size() + value.size();
57+
return out;
58+
}
59+
60+
size_t SerializedMap::Serialize(Input input, absl::Span<char> buffer) {
61+
DCHECK_GE(buffer.size(), SerializeSize(input));
62+
char* ptr = buffer.data();
63+
absl::little_endian::Store32(ptr, input.size());
64+
ptr += kLenBytes;
65+
66+
for (const auto& [key, value] : input) {
67+
absl::little_endian::Store32(ptr, key.length());
68+
ptr += kLenBytes;
69+
absl::little_endian::Store32(ptr, value.length());
70+
ptr += kLenBytes;
71+
memcpy(ptr, key.data(), key.length());
72+
ptr += key.length();
73+
memcpy(ptr, value.data(), value.length());
74+
ptr += value.length();
75+
}
76+
77+
return ptr - buffer.data();
78+
}
79+
80+
} // namespace dfly::tiering
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#pragma once
2+
3+
#include <absl/types/span.h>
4+
5+
#include <string_view>
6+
7+
namespace dfly::tiering {
8+
9+
// Map built over single continuous byte slice to allow easy read operations.
10+
struct SerializedMap {
11+
struct Iterator {
12+
using iterator_category = std::forward_iterator_tag;
13+
using difference_type = std::ptrdiff_t;
14+
using value_type = std::pair<std::string_view, std::string_view>;
15+
using reference = value_type;
16+
using pointer = value_type*;
17+
18+
Iterator& operator++();
19+
20+
bool operator==(const Iterator& other) const {
21+
return slice_.data() == other.slice_.data() && slice_.size() == other.slice_.size();
22+
}
23+
24+
bool operator!=(const Iterator& other) const {
25+
return !operator==(other);
26+
}
27+
28+
std::pair<std::string_view, std::string_view> operator*() const {
29+
return {key_, value_};
30+
}
31+
32+
private:
33+
friend struct SerializedMap;
34+
35+
explicit Iterator(std::string_view buffer);
36+
void Read();
37+
38+
std::string_view slice_; // the part left
39+
std::string_view key_, value_;
40+
};
41+
42+
explicit SerializedMap(std::string_view slice);
43+
44+
Iterator Find(std::string_view key) const; // Linear search
45+
Iterator begin() const;
46+
Iterator end() const;
47+
size_t size() const;
48+
49+
// Input for serialization
50+
using Input = const absl::Span<const std::pair<std::string_view, std::string_view>>;
51+
52+
// Buffer size required for serialization
53+
static size_t SerializeSize(Input);
54+
55+
// Write a slice that can be used to a SerializedMap on top of it.
56+
// Returns number of bytes written
57+
static size_t Serialize(Input, absl::Span<char> buffer);
58+
59+
private:
60+
size_t size_;
61+
std::string_view slice_;
62+
};
63+
64+
} // namespace dfly::tiering
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#include "server/tiering/serialized_map.h"
2+
3+
#include <map>
4+
5+
#include "base/logging.h"
6+
#include "gmock/gmock.h"
7+
8+
namespace dfly::tiering {
9+
10+
using namespace std;
11+
12+
class SerializedMapTest : public ::testing::Test {};
13+
14+
TEST_F(SerializedMapTest, TestBasic) {
15+
const vector<std::pair<string_view, string_view>> kBase = {{"first key", "first value"},
16+
{"second key", "second value"},
17+
{"third key", "third value"},
18+
{"fourth key", "fourth value"},
19+
{"fifth key", "fifth value"}};
20+
21+
// Serialize kBase to buffer
22+
std::string buffer;
23+
buffer.resize(SerializedMap::SerializeSize(kBase));
24+
size_t written = SerializedMap::Serialize(kBase, absl::MakeSpan(buffer));
25+
EXPECT_GT(written, 0u);
26+
27+
// Build map over buffer and check size
28+
SerializedMap map{buffer};
29+
EXPECT_EQ(map.size(), kBase.size());
30+
31+
// Check entries
32+
size_t idx = 0;
33+
for (auto it = map.begin(); it != map.end(); ++it, ++idx) {
34+
EXPECT_EQ(*it, kBase[idx]);
35+
}
36+
}
37+
38+
} // namespace dfly::tiering

0 commit comments

Comments
 (0)