Browse Source

update report

master
alexfisher 2 weeks ago
parent
commit
359b3ad2ed
5 changed files with 19 additions and 14 deletions
  1. BIN
      ttl.assets/image-20241106203303238.png
  2. BIN
      ttl.assets/old_record-17307966031002.png
  3. BIN
      ttl.assets/old_record-17308259248204.png
  4. BIN
      ttl.assets/old_record.png
  5. +19
    -14
      ttl.md

BIN
ttl.assets/image-20241106203303238.png View File

Before After
Width: 1018  |  Height: 714  |  Size: 92 KiB

BIN
ttl.assets/old_record-17307966031002.png View File

Before After
Width: 681  |  Height: 150  |  Size: 10 KiB Width: 681  |  Height: 150  |  Size: 12 KiB

BIN
ttl.assets/old_record-17308259248204.png View File

Before After
Width: 681  |  Height: 150  |  Size: 10 KiB Width: 681  |  Height: 150  |  Size: 15 KiB

BIN
ttl.assets/old_record.png View File

Before After
Width: 681  |  Height: 150  |  Size: 10 KiB Width: 681  |  Height: 150  |  Size: 10 KiB

+ 19
- 14
ttl.md View File

@ -1,5 +1,3 @@
# LEVELDB TTL # LEVELDB TTL
#### 小组成员 #### 小组成员
@ -76,7 +74,7 @@ Slice input(rep_);
} }
``` ```
之后,使用MemTable::Add将解析出来的数据重新编码,并插入至memtable,这种新的编码形式同样适用于SSTable
之后,使用MemTable::Add将解析出来的数据重新编码,并插入至memtable。对于SSTable来说,InternalKey中的SequenceNumber和Type被隐藏,但存储数据的形式未发生改变
```c++ ```c++
void MemTable::Add(SequenceNumber s, ValueType type, const Slice& key, void MemTable::Add(SequenceNumber s, ValueType type, const Slice& key,
@ -116,7 +114,7 @@ void MemTable::Add(SequenceNumber s, ValueType type, const Slice& key,
我们的第一版TTL存储设计如下: 我们的第一版TTL存储设计如下:
![old_record](ttl.assets/old_record-17307966031002.png)
![old_record-17307966031002](ttl.assets/old_record-17307966031002.png)
在我们最初的思考中,TTL与SequenceNumber、Type一样,属于和主要的存储内容(key,value)分割开的存储数据,因此我们选择将TTL加在SequenceNumber和Type后面(不加在中间和前面,以免破坏原先InternalKey的结构)。 在我们最初的思考中,TTL与SequenceNumber、Type一样,属于和主要的存储内容(key,value)分割开的存储数据,因此我们选择将TTL加在SequenceNumber和Type后面(不加在中间和前面,以免破坏原先InternalKey的结构)。
@ -126,7 +124,7 @@ void MemTable::Add(SequenceNumber s, ValueType type, const Slice& key,
我们的第二版TTL存储设计如下: 我们的第二版TTL存储设计如下:
![old_record](ttl.assets/old_record-17308259248204.png)
![old_record-17308259248204](ttl.assets/old_record-17308259248204.png)
由于 leveldb 本身对于 key、value、sequencenumber 的解析已经非常完善并且有些耦合,因此我们想要在不破坏 leveldb 原先的解析方式下完成 TTL 功能,我们想到的最好最轻量级的方式,就是将 TTL 数据作为 Value的一部分(在原 Value 后追加 8byte 作为 TTL )进行存储,仅当要解析 Value 时(即读取数据时或合并时),才会在同时对 TTL 进行解析。由此一来,我们无需改变原本的存储结构。事实证明,我们在很短时间内就完成了这一版 TTL的全部所需功能。 由于 leveldb 本身对于 key、value、sequencenumber 的解析已经非常完善并且有些耦合,因此我们想要在不破坏 leveldb 原先的解析方式下完成 TTL 功能,我们想到的最好最轻量级的方式,就是将 TTL 数据作为 Value的一部分(在原 Value 后追加 8byte 作为 TTL )进行存储,仅当要解析 Value 时(即读取数据时或合并时),才会在同时对 TTL 进行解析。由此一来,我们无需改变原本的存储结构。事实证明,我们在很短时间内就完成了这一版 TTL的全部所需功能。
@ -134,7 +132,7 @@ void MemTable::Add(SequenceNumber s, ValueType type, const Slice& key,
### 实现流程 ### 实现流程
### 第一版设计
#### 第一版设计
首先,我们需要修改在 writebatch 中数据的存取格式(主要是对 Value 的读取需要向后偏移8位)。我们在 WriteBatch 中新增了 Put_for_ttl 函数,将 ttl 放置在 key,value 之前。 首先,我们需要修改在 writebatch 中数据的存取格式(主要是对 Value 的读取需要向后偏移8位)。我们在 WriteBatch 中新增了 Put_for_ttl 函数,将 ttl 放置在 key,value 之前。
@ -303,7 +301,11 @@ void TableBuilder::Add(const Slice& key, uint64_t ttl, const Slice& value) {
##### 然而,在处理Block中数据的存取时,我们遇到了问题:
![image-20241106203303238](ttl.assets/image-20241106203303238.png)
Leveldb使用Block::Iter对于IndexBlock和dataBlock进行统一的数据读取,在处理dataBlock时,我们期望Block::Iter对数据的处理能够适配ttl,即在key与value中间存有一份TTL。然而在debug过程中我们发现,在改动Block::Iter获取下一个键值对和获取当前键值对的Value的逻辑后(因为要多向后移8字节),使得IndexBlock获取数据出错。在经过一段时间的debug后,我们认为要完成解决这一bug,需要连带改动IndexBlock存取数据的逻辑,而这已经偏离了TTL本身的范围,使得项目的紧耦合度增强,既不轻量也不优雅,因此我们放弃解决这一bug,转而构思出了第二版设计。
#### 第二版设计 #### 第二版设计
@ -359,7 +361,7 @@ Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value, ui
int len = value.size() + sizeof(uint64_t); int len = value.size() + sizeof(uint64_t);
char* new_data = new char[len]; char* new_data = new char[len];
time_t now = time(nullptr); // 获取当前时间,单位为秒 time_t now = time(nullptr); // 获取当前时间,单位为秒
ttl += static_cast<uint64_t>(now); // 将当前时间加上 TTL,计算过期时间
ttl += static_cast<uint64_t>(now); // 将当前时间加上 TTL,计算过期时间,以过期时间戳作为真正存储的TTL
memcpy(new_data, value.data(), value.size()); // 将实际的值复制到 new_data 中 memcpy(new_data, value.data(), value.size()); // 将实际的值复制到 new_data 中
memcpy(new_data + len - sizeof(uint64_t), (char*)(&ttl), sizeof(uint64_t)); // 将 TTL 复制到 new_data 的末尾 memcpy(new_data + len - sizeof(uint64_t), (char*)(&ttl), sizeof(uint64_t)); // 将 TTL 复制到 new_data 的末尾
@ -491,6 +493,10 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s) {
##### 测试 ##### 测试
1.将原有的两个测试的严格sleep ttl秒,改成sleep(ttl+1)秒,因为我们的设计是以秒为存储单位,可能存在一秒以内的读取误差,所以进行修改。(我们认为ttl无需毫秒/微妙级的数据准确性,一秒之内的误差可以接受,因此采取此种设计)
2.在原有设计上增加了第三个测试,其目的在于:①测试新的数据过期后,是否能够重新查找到旧但未过期的数据。②测试无TTL的接口是否可以正确运行,并且数据不过期。
```c++ ```c++
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "leveldb/env.h" #include "leveldb/env.h"
@ -612,17 +618,17 @@ TEST(TestTTL, OurTTL) {
for (int i = 0; i < 10000; i++) { for (int i = 0; i < 10000; i++) {
std::string key = std::to_string(i); std::string key = std::to_string(i);
std::string value = std::to_string(i); std::string value = std::to_string(i);
db->Put(writeOptions, key, value);
db->Put(writeOptions, key, value);//插入永不过期的key value(value=key)
} }
for (int i = 0; i < 10000; i++) { for (int i = 0; i < 10000; i++) {
std::string key = std::to_string(i); std::string key = std::to_string(i);
std::string value = std::to_string(i*2); std::string value = std::to_string(i*2);
db->Put(writeOptions, key, value, 30);
db->Put(writeOptions, key, value, 30);//插入30秒后过期的key value(value=key*2)
} }
for (int i = 0; i < 10000; i++) { for (int i = 0; i < 10000; i++) {
std::string key = std::to_string(i); std::string key = std::to_string(i);
std::string value = std::to_string(i*3); std::string value = std::to_string(i*3);
db->Put(writeOptions, key, value, 15);
db->Put(writeOptions, key, value, 15);//插入15秒后过期的key value(value=key*3)
} }
for (int i = 0; i < 10000; i++) { for (int i = 0; i < 10000; i++) {
@ -630,7 +636,7 @@ TEST(TestTTL, OurTTL) {
std::string value; std::string value;
Status status = db->Get(readOptions, key, &value); Status status = db->Get(readOptions, key, &value);
ASSERT_TRUE(status.ok()); ASSERT_TRUE(status.ok());
ASSERT_TRUE(value==std::to_string(i*3));
ASSERT_TRUE(value==std::to_string(i*3));//立刻读取,期望得到value=key*3
} }
Env::Default()->SleepForMicroseconds((15+1) * 1000000); Env::Default()->SleepForMicroseconds((15+1) * 1000000);
@ -640,7 +646,7 @@ TEST(TestTTL, OurTTL) {
std::string value; std::string value;
Status status = db->Get(readOptions, key, &value); Status status = db->Get(readOptions, key, &value);
ASSERT_TRUE(status.ok()); ASSERT_TRUE(status.ok());
ASSERT_TRUE(value==std::to_string(i*2));
ASSERT_TRUE(value==std::to_string(i*2));//16秒后读取,期望得到value=key*2
} }
Env::Default()->SleepForMicroseconds((15+1) * 1000000); Env::Default()->SleepForMicroseconds((15+1) * 1000000);
@ -650,7 +656,7 @@ TEST(TestTTL, OurTTL) {
std::string value; std::string value;
Status status = db->Get(readOptions, key, &value); Status status = db->Get(readOptions, key, &value);
ASSERT_TRUE(status.ok()); ASSERT_TRUE(status.ok());
ASSERT_TRUE(value==std::to_string(i));
ASSERT_TRUE(value==std::to_string(i));//32秒后读取,期望得到value=key
} }
delete db; delete db;
@ -663,4 +669,3 @@ int main(int argc, char** argv) {
return RUN_ALL_TESTS(); return RUN_ALL_TESTS();
} }
``` ```

Loading…
Cancel
Save