4 Commits

Author SHA1 Message Date
  xxy 4507932a5d minor compaction test ttl & enhance effeciency of reading current time 10 months ago
  alexfisher 3c7369b76c update readme and report 10 months ago
  alexfisher 912fdbc849 update report 10 months ago
  alexfisher 359b3ad2ed update report 10 months ago
10 changed files with 45 additions and 28 deletions
Split View
  1. +5
    -8
      README.md
  2. +10
    -1
      db/builder.cc
  3. +5
    -2
      db/db_impl.cc
  4. +1
    -1
      test/ttl_test.cc
  5. BIN
      ttl.assets/image-20241106203303238.png
  6. BIN
      ttl.assets/image-20241106220858020.png
  7. BIN
      ttl.assets/old_record-17307966031002.png
  8. BIN
      ttl.assets/old_record-17308259248204.png
  9. BIN
      ttl.assets/old_record.png
  10. +24
    -16
      ttl.md

+ 5
- 8
README.md View File

@ -1,9 +1,6 @@
**本仓库提供TTL基本的测试用例**
克隆代码:
```bash
git clone --recurse-submodules https://gitea.shuishan.net.cn/building_data_management_systems.Xuanzhou.2024Fall.DaSE/leveldb_base.git
```
## LEVELDB(增加TTL功能)
#### 小组成员: 谢瑞阳 徐翔宇
### 报告内容详见ttl.md
git用户对应关系:
alexfisher-谢瑞阳 xxy-徐翔宇

+ 10
- 1
db/builder.cc View File

@ -30,8 +30,17 @@ Status BuildTable(const std::string& dbname, Env* env, const Options& options,
TableBuilder* builder = new TableBuilder(options, file);
meta->smallest.DecodeFrom(iter->key());
Slice key;
Slice key=iter->key();
// 小合并时检查ttl
time_t now = time(nullptr); // 获得当前时间
uint64_t now_time=static_cast<uint64_t>(now);
for (; iter->Valid(); iter->Next()) {
uint64_t ttl=*(uint64_t*)(iter->value().data()+iter->value().size()-sizeof(uint64_t)); // 将 TTL 从 new_data 的末尾取出
// 如果 TTL 超过当前时间,说明数据已经过期
if(ttl < static_cast<uint64_t>(now_time)){
continue;
}
key = iter->key();
builder->Add(key, iter->value());
}

+ 5
- 2
db/db_impl.cc View File

@ -57,6 +57,8 @@ struct DBImpl::CompactionState {
uint64_t number;
uint64_t file_size;
InternalKey smallest, largest;
// uint64_t now = static_cast<uint64_t>(time(nullptr)); // 合并发起的时间
};
Output* current_output() { return &outputs[outputs.size() - 1]; }
@ -923,12 +925,13 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) {
std::string current_user_key;
bool has_current_user_key = false;
SequenceNumber last_sequence_for_key = kMaxSequenceNumber;
time_t now = time(nullptr); // 获得当前时间
uint64_t now_time=static_cast<uint64_t>(now);
while (input->Valid() && !shutting_down_.load(std::memory_order_acquire)) {
auto x=input->value(); // 获取键值对 value
uint64_t ttl=*(uint64_t*)(x.data()+x.size()-sizeof(uint64_t));// 将 TTL 从 new_data 的末尾取出
time_t now = time(nullptr); // 获得当前时间
// 如果 TTL 超过当前时间,说明数据已经过期
if(ttl < static_cast<uint64_t>(now)){
if(ttl < now_time){
Log(options_.info_log, "delete record for ttl");
input->Next(); // 将 input 指向下一个键值对
continue;

+ 1
- 1
test/ttl_test.cc View File

@ -65,7 +65,7 @@ TEST(TestTTL, ReadTTL) {
ASSERT_TRUE(status.ok());
}
Env::Default()->SleepForMicroseconds((ttl+1) * 1000000);
Env::Default()->SleepForMicroseconds((ttl+2) * 1000000);
for (int i = 0; i < 100; i++) {
int key_ = rand() % key_num+1;

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

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

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

Before After
Width: 1161  |  Height: 439  |  Size: 80 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

+ 24
- 16
ttl.md View File

@ -1,5 +1,3 @@
# LEVELDB TTL
#### 小组成员
@ -16,11 +14,11 @@
其中每个分支对应的内容为:
master分支:主分支:第二版TTL设计。
master 分支:主分支:第二版TTL设计。
xry分支:第一版TTL设计,完成了在memtable中的带有TTL数据的存取(即在小数据量情况下可以正确运行测试脚本中的第一个测试)。
xry 分支:第一版TTL设计,完成了在 memtable 中的带有 TTL 数据的存取(即在小数据量情况下可以正确运行测试脚本中的第一个测试)。
xxy分支、new_version分支:第二版TTL设计。其中new_version分支与master分支内容相同。
xxy 分支、new_version 分支:第二版 TTL 设计。其中 new_version 分支与 master 分支内容相同。
### 设计思路
@ -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;
@ -664,3 +670,5 @@ int main(int argc, char** argv) {
}
```
测试结果
![image-20241106220858020](ttl.assets/image-20241106220858020.png)

Loading…
Cancel
Save