alexfisher 2 недель назад
Родитель
Сommit
359b3ad2ed
5 измененных файлов: 19 добавлений и 14 удалений
  1. Двоичные данные
      ttl.assets/image-20241106203303238.png
  2. Двоичные данные
      ttl.assets/old_record-17307966031002.png
  3. Двоичные данные
      ttl.assets/old_record-17308259248204.png
  4. Двоичные данные
      ttl.assets/old_record.png
  5. +19
    -14
      ttl.md

Двоичные данные
ttl.assets/image-20241106203303238.png Просмотреть файл

До После
Ширина: 1018  |  Высота: 714  |  Размер: 92 KiB

Двоичные данные
ttl.assets/old_record-17307966031002.png Просмотреть файл

До После
Ширина: 681  |  Высота: 150  |  Размер: 10 KiB Ширина: 681  |  Высота: 150  |  Размер: 12 KiB

Двоичные данные
ttl.assets/old_record-17308259248204.png Просмотреть файл

До После
Ширина: 681  |  Высота: 150  |  Размер: 10 KiB Ширина: 681  |  Высота: 150  |  Размер: 15 KiB

Двоичные данные
ttl.assets/old_record.png Просмотреть файл

До После
Ширина: 681  |  Высота: 150  |  Размер: 10 KiB Ширина: 681  |  Высота: 150  |  Размер: 10 KiB

+ 19
- 14
ttl.md Просмотреть файл

@ -1,5 +1,3 @@
# LEVELDB TTL
#### 小组成员
@ -76,7 +74,7 @@ Slice input(rep_);
}
```
之后,使用MemTable::Add将解析出来的数据重新编码,并插入至memtable,这种新的编码形式同样适用于SSTable
之后,使用MemTable::Add将解析出来的数据重新编码,并插入至memtable。对于SSTable来说,InternalKey中的SequenceNumber和Type被隐藏,但存储数据的形式未发生改变
```c++
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存储设计如下:
![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的结构)。
@ -126,7 +124,7 @@ void MemTable::Add(SequenceNumber s, ValueType type, const Slice& key,
我们的第二版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的全部所需功能。
@ -134,7 +132,7 @@ void MemTable::Add(SequenceNumber s, ValueType type, const Slice& key,
### 实现流程
### 第一版设计
#### 第一版设计
首先,我们需要修改在 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);
char* new_data = new char[len];
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 + 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++
#include "gtest/gtest.h"
#include "leveldb/env.h"
@ -612,17 +618,17 @@ TEST(TestTTL, OurTTL) {
for (int i = 0; i < 10000; i++) {
std::string key = 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++) {
std::string key = std::to_string(i);
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++) {
std::string key = std::to_string(i);
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++) {
@ -630,7 +636,7 @@ TEST(TestTTL, OurTTL) {
std::string value;
Status status = db->Get(readOptions, key, &value);
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);
@ -640,7 +646,7 @@ TEST(TestTTL, OurTTL) {
std::string value;
Status status = db->Get(readOptions, key, &value);
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);
@ -650,7 +656,7 @@ TEST(TestTTL, OurTTL) {
std::string value;
Status status = db->Get(readOptions, key, &value);
ASSERT_TRUE(status.ok());
ASSERT_TRUE(value==std::to_string(i));
ASSERT_TRUE(value==std::to_string(i));//32秒后读取,期望得到value=key
}
delete db;
@ -663,4 +669,3 @@ int main(int argc, char** argv) {
return RUN_ALL_TESTS();
}
```

Загрузка…
Отмена
Сохранить