選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
马也驰 0cefdf5ace TestTTL.CompactionTTL passed 1ヶ月前
.github/workflows initialize 删除 1ヶ月前
benchmarks initialize 删除 1ヶ月前
cmake initialize 删除 1ヶ月前
db TestTTL.CompactionTTL passed 删除 1ヶ月前
doc initialize 删除 1ヶ月前
helpers/memenv initialize 删除 1ヶ月前
include/leveldb TestTTL.ReadTTL passed 删除 1ヶ月前
issues initialize 删除 1ヶ月前
port initialize 删除 1ヶ月前
table initialize 删除 1ヶ月前
test TestTTL.CompactionTTL passed 删除 1ヶ月前
third_party initialize 删除 1ヶ月前
util initialize 删除 1ヶ月前
.clang-format initialize 1ヶ月前
.gitignore initialize 1ヶ月前
.gitmodules initialize 1ヶ月前
AUTHORS initialize 1ヶ月前
CMakeLists.txt initialize 1ヶ月前
CONTRIBUTING.md initialize 1ヶ月前
LICENSE initialize 1ヶ月前
NEWS initialize 1ヶ月前
README.md write README.md 1ヶ月前
TODO initialize 1ヶ月前

README.md

LevelDB TTL 实验报告

小组成员:

王雪飞 10225501435

马也驰 10215501408

1.实验目的

  1. 深入了解LevelDB的内部原理和数据结构。
  2. 掌握TTL(Time To Live,生存时间)功能的设计与实现方法。
  3. 学习如何在开源项目中添加新功能,提升代码阅读和修改能力。

2.实验要求

  1. 在LevelDB中实现键值对的TTL功能,使得过期的数据在读取时自动失效,并在适当的时候被合并清理。
  2. 修改LevelDB的源码,实现对TTL的支持,包括数据的写入、读取和过期数据的清理。
  3. 编写测试用例,验证TTL功能的正确性和稳定性。

3.实验内容

3.1. TTL功能介绍

TTL(Time To Live),即生存时间,是指数据在存储系统中的有效期。设置TTL可以使得过期的数据自动失效,减少存储空间占用,提高系统性能。

为什么需要TTL功能:

  1. 数据自动过期:无需手动删除过期数据,简化数据管理。
  2. 节省存储空间:定期清理无效数据,优化资源利用。
  3. 提高性能:减少无效数据的干扰,提升读写效率。

3.2. 设计方案

在LevelDB中添加TTL功能的方案:

  1. 数据编码方式修改:在键或值中增加过期时间的信息。
  2. 读取时判断过期:在Get操作时,检查数据是否过期,过期则返回NotFound。
  3. Compaction清理:在数据压缩过程中,删除过期的数据。

3.3. 实现步骤

3.3.1. 修改数据结构

在 Put 操作中,将 TTL 与当前时间相加获得 DDL,DDL 为数据失效的时间,将 DDL 与值一起存储,存储格式为<TTL_value>

Status DB::Put(const WriteOptions& opt, const Slice& key,
                   const Slice& value, uint64_t ttl) {
  WriteBatch batch;
  auto now = std::chrono::system_clock::now();
  auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count();
  auto microsecondsTimestamp = static_cast<uint64_t>(timestamp) + ttl*1000000;
  std::string value_ttl = value.ToString();
  value_ttl += "_" + std::to_string(microsecondsTimestamp);
  Slice new_value(value_ttl.c_str(), value_ttl.size());
  batch.Put(key, new_value);
  return Write(opt, &batch);
}

3.3.2 修改读取流程

在 Get 操作中,获得 value 之后,找到第一个下划线,下划线前面的数据为 DDL,然后用当前时间与 DDL 作比较,判断数据是否过期,过期则返回NotFound。

size_t pos = value->find_last_of('_');
if (pos != std::string::npos) {
std::string substring = value->substr(pos + 1);
auto ddl = static_cast<uint64_t>(std::stoll(substring));
auto now = std::chrono::system_clock::now();
auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count();
auto microsecondsTimestamp = static_cast<uint64_t>(timestamp);
if (ddl <= microsecondsTimestamp) {
    value->clear();
    Slice msg1("value not found!");
    Slice msg2("value has expired!");
    s = leveldb::Status::NotFound(msg1, msg2);
} else {
    value->resize(pos);
    }
}

实验中遇到的问题

1. TTL存储的位置以及存储方式

我们最初的想法是把TTL跟value存储在一起,形式为<TTL value>,这样Put操作会很简单,仅仅把两个字符串拼接起来即可,但这样的话,在Get操作中时,无法判断从何处分割TTL和value,所以我们决定在TTL和value之间添加一个标志符,存放形式改为<TTL_value>,这样,在Get操作时,只需先找到第一个下划线,下划线前面的为TTL,后面的为value,这样就能把TTL和value区分开来。但还有一个问题,判断条件为:写入数据的时间+ TTL < 读取数据的时间 ,如果仅存放TTL,虽然在调用get时我们可以获得读取数据的时间,并通过解码value获得TTL,但我们没有办法获得写入数据的时间,所以只能通过在Put操作时,把写入数据的时间也写入value中,这样在Get时,就能获得写入数据的时间,从而判断是否过期。所以,我们又把value的形式改为<TTL_写入时间_value>,这样,通过两个下划线把TTL、写入时间和value区分开来,就能实现在get操作时判断是否过期。但我们又想到,既然在get操作解码得到TTL和写入时间之后要加在一块,并且TTL和写入时间都是在get操作时与value进行编码,那么我们为什么不在get操作时就把TTL和写入时间加在一起,再与value编码呢,把写入时间+TTL记为DDL,这样就可以把value编码为<DDL_value>,在get操作时,只需解码得到DDL,然后拿当前时间跟DDL作比较,即可知道数据是否过期。

综上所述,我们的编码格式经过多次迭代:<TTL value>-><TTL_value>-><TTL_写入时间_value>-><DDL_value>,最终得到一个比较满意的编码方式。