4 コミット

作成者 SHA1 メッセージ 日付
  xxy 4507932a5d minor compaction test ttl & enhance effeciency of reading current time 2週間前
  alexfisher 3c7369b76c update readme and report 2週間前
  alexfisher 912fdbc849 update report 2週間前
  alexfisher 359b3ad2ed update report 2週間前
10個のファイルの変更45行の追加28行の削除
分割表示
  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. バイナリ
      ttl.assets/image-20241106203303238.png
  6. バイナリ
      ttl.assets/image-20241106220858020.png
  7. バイナリ
      ttl.assets/old_record-17307966031002.png
  8. バイナリ
      ttl.assets/old_record-17308259248204.png
  9. バイナリ
      ttl.assets/old_record.png
  10. +24
    -16
      ttl.md

+ 5
- 8
README.md ファイルの表示

@ -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 ファイルの表示

@ -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 ファイルの表示

@ -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 ファイルの表示

@ -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;

バイナリ
ttl.assets/image-20241106203303238.png ファイルの表示

変更前 変更後
幅: 1018  |  高さ: 714  |  サイズ: 92 KiB

バイナリ
ttl.assets/image-20241106220858020.png ファイルの表示

変更前 変更後
幅: 1161  |  高さ: 439  |  サイズ: 80 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

+ 24
- 16
ttl.md ファイルの表示

@ -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)

読み込み中…
キャンセル
保存