作者: 谢瑞阳 10225101483 徐翔宇 10225101535
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

674 regels
23 KiB

2 weken geleden
2 weken geleden
2 weken geleden
2 weken geleden
2 weken geleden
2 weken geleden
2 weken geleden
2 weken geleden
2 weken geleden
2 weken geleden
2 weken geleden
2 weken geleden
2 weken geleden
2 weken geleden
  1. # LEVELDB TTL
  2. #### 小组成员
  3. 谢瑞阳 徐翔宇
  4. ## 整体介绍
  5. 我们在整体实验过程中,经历了先后两版对于TLL功能的设计和开发过程,在开发第一版TTL功能的过程中,我们遇到了一些困难。经过思考讨论后,我们重构了全部代码,并基于新的设计快速的完成了TTL功能以及对应的垃圾回收功能。
  6. 我们仓库的地址为:https://gitea.shuishan.net.cn/10225101483/XOY-Leveldb
  7. 其中每个分支对应的内容为:
  8. master 分支:主分支:第二版TTL设计。
  9. xry 分支:第一版TTL设计,完成了在 memtable 中的带有 TTL 数据的存取(即在小数据量情况下可以正确运行测试脚本中的第一个测试)。
  10. xxy 分支、new_version 分支:第二版 TTL 设计。其中 new_version 分支与 master 分支内容相同。
  11. ### 设计思路
  12. 要完成TTL功能,首先也是最重要的内容即是要在原本leveldb存储数据的格式上增加一块用于存储TTL的空间,并且想明白如何对其进行读取,以及需要对其他存储空间(key,sequence number,value)的存取需要作出什么改动。
  13. 在写入数据时,涉及到编码的主要函数有以下三个:
  14. leveldb通过WriteBatch::Put对传入的键值对进行编码,写入writebatch中。
  15. ```c++
  16. void WriteBatch::Put(const Slice& key, const Slice& value) {
  17. WriteBatchInternal::SetCount(this, WriteBatchInternal::Count(this) + 1);
  18. rep_.push_back(static_cast<char>(kTypeValue));
  19. PutLengthPrefixedSlice(&rep_, key);
  20. PutLengthPrefixedSlice(&rep_, value);
  21. }
  22. ```
  23. 之后,使用WriteBatch::Iterate将一个writebatch中的所有键值对依次解析并使用handler放入memtable。
  24. ```c++
  25. Slice input(rep_);
  26. if (input.size() < kHeader) {
  27. return Status::Corruption("malformed WriteBatch (too small)");
  28. }
  29. input.remove_prefix(kHeader);
  30. Slice key, value;
  31. int found = 0;
  32. while (!input.empty()) {
  33. found++;
  34. char tag = input[0];
  35. input.remove_prefix(1);
  36. switch (tag) {
  37. case kTypeValue:
  38. if (GetLengthPrefixedSlice(&input, &key) &&
  39. GetLengthPrefixedSlice(&input, &value)) {
  40. handler->Put(key, value);
  41. } else {
  42. return Status::Corruption("bad WriteBatch Put");
  43. }
  44. break;
  45. case kTypeDeletion:
  46. if (GetLengthPrefixedSlice(&input, &key)) {
  47. handler->Delete(key);
  48. } else {
  49. return Status::Corruption("bad WriteBatch Delete");
  50. }
  51. break;
  52. default:
  53. return Status::Corruption("unknown WriteBatch tag");
  54. }
  55. }
  56. ```
  57. 之后,使用MemTable::Add将解析出来的数据重新编码,并插入至memtable。对于SSTable来说,InternalKey中的SequenceNumber和Type被隐藏,但存储数据的形式未发生改变。
  58. ```c++
  59. void MemTable::Add(SequenceNumber s, ValueType type, const Slice& key,
  60. const Slice& value) {
  61. // Format of an entry is concatenation of:
  62. // key_size : varint32 of internal_key.size()
  63. // key bytes : char[internal_key.size()]
  64. // tag : uint64((sequence << 8) | type)
  65. // value_size : varint32 of value.size()
  66. // value bytes : char[value.size()]
  67. size_t key_size = key.size();
  68. size_t val_size = value.size();
  69. size_t internal_key_size = key_size + 8;
  70. const size_t encoded_len = VarintLength(internal_key_size) +
  71. internal_key_size + VarintLength(val_size) +
  72. val_size;
  73. char* buf = arena_.Allocate(encoded_len);
  74. char* p = EncodeVarint32(buf, internal_key_size);
  75. std::memcpy(p, key.data(), key_size);
  76. p += key_size;
  77. EncodeFixed64(p, (s << 8) | type);
  78. p += 8;
  79. p = EncodeVarint32(p, val_size);
  80. std::memcpy(p, value.data(), val_size);
  81. assert(p + val_size == buf + encoded_len);
  82. table_.Insert(buf);
  83. }
  84. ```
  85. 这是原本leveldb在memtable和sstable中存储单个键值对时使用的数据结构:
  86. ![old_record](ttl.assets/old_record.png)
  87. 其中,key和value对应着用户输入的键值对,而sequenceNumber是该键值对在leveldb内部的序列号(越大越新),type则标识该记录的类型。
  88. #### 第一版设计:增加在中
  89. 我们的第一版TTL存储设计如下:
  90. ![old_record-17307966031002](ttl.assets/old_record-17307966031002.png)
  91. 在我们最初的思考中,TTL与SequenceNumber、Type一样,属于和主要的存储内容(key,value)分割开的存储数据,因此我们选择将TTL加在SequenceNumber和Type后面(不加在中间和前面,以免破坏原先InternalKey的结构)。
  92. 但在实现过程中,我们发现在中间增加TTL的形式会导致许多代码变动,因为对数据编码形式的修改会导致InternalKey和LookupKey等基础的编码类及函数需要进行改动,它们在一些与TTL无关的地方似乎也被复用(比如versionEdit和大量测试函数),因此我们认为对其进行修改会非常复杂,可能导致leveldb其他模块受到牵连,不符合我们对TTL轻量级改动的构想。因此我们进行了讨论,并产生了如下的新版设计
  93. #### 第二版设计:放入Value
  94. 我们的第二版TTL存储设计如下:
  95. ![old_record-17308259248204](ttl.assets/old_record-17308259248204.png)
  96. 由于 leveldb 本身对于 key、value、sequencenumber 的解析已经非常完善并且有些耦合,因此我们想要在不破坏 leveldb 原先的解析方式下完成 TTL 功能,我们想到的最好最轻量级的方式,就是将 TTL 数据作为 Value的一部分(在原 Value 后追加 8byte 作为 TTL )进行存储,仅当要解析 Value 时(即读取数据时或合并时),才会在同时对 TTL 进行解析。由此一来,我们无需改变原本的存储结构。事实证明,我们在很短时间内就完成了这一版 TTL的全部所需功能。
  97. ### 实现流程
  98. #### 第一版设计
  99. 首先,我们需要修改在 writebatch 中数据的存取格式(主要是对 Value 的读取需要向后偏移8位)。我们在 WriteBatch 中新增了 Put_for_ttl 函数,将 ttl 放置在 key,value 之前。
  100. ```c++
  101. void WriteBatch::Put_for_ttl(const Slice& key, const Slice& value,uint64_t ttl){
  102. WriteBatchInternal::SetCount(this, WriteBatchInternal::Count(this) + 1);
  103. rep_.push_back(static_cast<char>(kTypeValue));
  104. PutVarint64(&rep_,ttl);
  105. PutLengthPrefixedSlice(&rep_, key);
  106. PutLengthPrefixedSlice(&rep_, value);
  107. }
  108. ```
  109. 同时,对 WriteBatch 的迭代器也要进行修改。
  110. ![image-20241106141245806](ttl.assets/image-20241106141245806.png)
  111. 使用 Add 将键值对加入 memtable 时,也要在 key 和 value 之间插入参数 ttl。具体位置在 key 末尾的 tag 之后,使用 EncodeFixed64 放入大小为 8 的 ttl 。
  112. ```c++
  113. void MemTable::Add(SequenceNumber s, ValueType type, const Slice& key,
  114. const Slice& value,uint64_t ttl) {
  115. // Format of an entry is concatenation of:
  116. // key_size : varint32 of internal_key.size()
  117. // key bytes : char[internal_key.size()]
  118. // tag : uint64((sequence << 8) | type)
  119. // ttl : uint64(now)
  120. // value_size : varint32 of value.size()
  121. // value bytes : char[value.size()]
  122. size_t key_size = key.size();
  123. size_t val_size = value.size();
  124. size_t internal_key_size = key_size + 8;
  125. const size_t encoded_len = VarintLength(internal_key_size) +
  126. internal_key_size + 8 + VarintLength(val_size) +
  127. val_size;
  128. char* buf = arena_.Allocate(encoded_len);
  129. //internal_key_size(sizeof(key)+sizeof(sequence+type)+sizeof(ttl))
  130. //--key--(sequence+type)--ttl--sizeof(val)--val
  131. char* p = EncodeVarint32(buf, internal_key_size);
  132. std::memcpy(p, key.data(), key_size);
  133. p += key_size;
  134. EncodeFixed64(p, (s << 8) | type);
  135. p += 8;
  136. EncodeFixed64(p,ttl);
  137. p += 8;
  138. p = EncodeVarint32(p, val_size);
  139. std::memcpy(p, value.data(), val_size);
  140. assert(p + val_size == buf + encoded_len);
  141. table_.Insert(buf);
  142. }
  143. ```
  144. 从 memtable 获取插入的数据时,需要读出 key, value 之间的 ttl 进行检查( line15 )
  145. ```c++
  146. bool MemTable::Get(const LookupKey& key, std::string* value, Status* s) {
  147. Slice memkey = key.memtable_key();
  148. Table::Iterator iter(&table_);
  149. iter.Seek(memkey.data());
  150. while(iter.Valid()) {
  151. const char* entry = iter.key();
  152. uint32_t key_length;//internal_key_size
  153. const char* key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length);
  154. if (comparator_.comparator.user_comparator()->Compare(
  155. Slice(key_ptr, key_length - 8), key.user_key()) == 0) {
  156. // Correct user key
  157. const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
  158. switch (static_cast<ValueType>(tag & 0xff)) {
  159. case kTypeValue: {
  160. const uint64_t ttl = DecodeFixed64(key_ptr+key_length);
  161. if(ttl<key.ttl()){ // data的ttl需要大于等于查找开始时的时间否则查找直到下一个ttl有效的键值对
  162. iter.Next();// drop dead data
  163. continue;
  164. }
  165. Slice v = GetLengthPrefixedSlice(key_ptr + key_length+8);// 指针再偏移ttl的长度
  166. value->assign(v.data(), v.size());
  167. return true;
  168. }
  169. case kTypeDeletion:
  170. *s = Status::NotFound(Slice());
  171. return true;
  172. }
  173. }
  174. else{
  175. break;// 当 key 不一致的时候返回 false
  176. }
  177. }
  178. return false;
  179. }
  180. ```
  181. 在开始查找时,将当前的时间写入 LookupKey,如果查找到的数据 ttl 超过当前时间,那么就会抛弃该条数据。
  182. ![image-20241106152151687](ttl.assets/image-20241106152151687.png)
  183. 通过 blockbuilder 向 sstable 插入数据时(新建 level0/1/2)时使用 add 函数,需要将 ttl 通过PutVarint64 编码成 `Varint64` 形式,放入 key 与 value 之间
  184. ```c++
  185. void BlockBuilder::Add(const Slice& key, uint64_t ttl, const Slice& value) {
  186. ···
  187. ···
  188. // Add "<shared><non_shared><value_size>" to buffer_
  189. PutVarint32(&buffer_, shared);
  190. PutVarint32(&buffer_, non_shared);
  191. PutVarint32(&buffer_, value.size());
  192. // Add string delta to buffer_ followed by value
  193. buffer_.append(key.data() + shared, non_shared);
  194. PutVarint64(&buffer_, ttl);//加入ttl
  195. buffer_.append(value.data(), value.size());
  196. // Update state
  197. last_key_.resize(shared);
  198. last_key_.append(key.data() + shared, non_shared);
  199. assert(Slice(last_key_) == key);
  200. counter_++;
  201. }
  202. ```
  203. 通过 TableBuilder 将合并后的数据插入新的 sstable,使用 add 函数
  204. ![image-20241106154658482](ttl.assets/image-20241106154658482.png)
  205. ```c++
  206. void TableBuilder::Add(const Slice& key, uint64_t ttl, const Slice& value) {
  207. time_t now = time(nullptr);
  208. if(ttl < static_cast<uint64_t>(now))return; // 再次检查,是否超时(理论上在上层DoCompactionWork时已经检查过一次,都是为了保障正确性,冗余的测试并不会造成很大的性能瓶颈)
  209. ···
  210. ···
  211. if (r->filter_block != nullptr) {
  212. r->filter_block->AddKey(key);
  213. }
  214. r->last_key.assign(key.data(), key.size());
  215. r->num_entries++;
  216. r->data_block.Add(key, ttl, value); //加入block时仍旧需要使用 BlockBuilder::Add
  217. const size_t estimated_block_size = r->data_block.CurrentSizeEstimate();
  218. if (estimated_block_size >= r->options.block_size) {
  219. Flush();
  220. }
  221. }
  222. ```
  223. 在 DoCompactionWork 中,合并是会检查数据 ttl 是否到期,如果到期则会丢弃。
  224. ![image-20241106173117857](ttl.assets/image-20241106173117857.png)
  225. ##### 然而,在处理Block中数据的存取时,我们遇到了问题:
  226. ![image-20241106203303238](ttl.assets/image-20241106203303238.png)
  227. Leveldb使用Block::Iter对于IndexBlock和dataBlock进行统一的数据读取,在处理dataBlock时,我们期望Block::Iter对数据的处理能够适配ttl,即在key与value中间存有一份TTL。然而在debug过程中我们发现,在改动Block::Iter获取下一个键值对和获取当前键值对的Value的逻辑后(因为要多向后移8字节),使得IndexBlock获取数据出错。在经过一段时间的debug后,我们认为要完成解决这一bug,需要连带改动IndexBlock存取数据的逻辑,而这已经偏离了TTL本身的范围,使得项目的紧耦合度增强,既不轻量也不优雅,因此我们放弃解决这一bug,转而构思出了第二版设计。
  228. #### 第二版设计
  229. ##### 原先的Put
  230. ```c++
  231. // Convenience methods
  232. Status DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val) {
  233. return DB::Put(o, key, val);
  234. }
  235. // 加入ttl功能后
  236. Status DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val,uint64_t ttl) {
  237. return DB::Put(o, key, val,ttl);
  238. }
  239. ```
  240. ##### 老版本的 `DB::Put` 方法仅接受键和值,并将值直接存入数据库。
  241. ```c++
  242. Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) {
  243. WriteBatch batch;
  244. batch.Put(key, value);
  245. return Write(opt, &batch);
  246. }
  247. ```
  248. ##### 新版本增加了一个可选参数 `ttl`(过期时间),允许用户指定存储的值的生存时间。
  249. ```c++
  250. Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) {
  251. WriteBatch batch;
  252. int len = value.size() + sizeof(uint64_t);
  253. char* new_data = new char[len];
  254. time_t now = time(nullptr); // 获取当前时间,单位为秒
  255. uint64_t ttl = INT64_MAX; // 设置默认的 TTL 为最大值(即永不过期)
  256. memcpy(new_data, value.data(), value.size()); // 将实际的值复制到 new_data 中
  257. memcpy(new_data + len - sizeof(uint64_t), (char*)(&ttl), sizeof(uint64_t)); // 将 TTL 复制到 new_data 的末尾
  258. Slice newValue = Slice(new_data, len); // 创建一个新的 Slice 对象
  259. batch.Put(key, newValue); // 将键值对放入 WriteBatch 中
  260. return Write(opt, &batch); // 写入数据库
  261. }
  262. Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value, uint64_t ttl) {
  263. WriteBatch batch;
  264. int len = value.size() + sizeof(uint64_t);
  265. char* new_data = new char[len];
  266. time_t now = time(nullptr); // 获取当前时间,单位为秒
  267. ttl += static_cast<uint64_t>(now); // 将当前时间加上 TTL,计算过期时间,以过期时间戳作为真正存储的TTL
  268. memcpy(new_data, value.data(), value.size()); // 将实际的值复制到 new_data 中
  269. memcpy(new_data + len - sizeof(uint64_t), (char*)(&ttl), sizeof(uint64_t)); // 将 TTL 复制到 new_data 的末尾
  270. Slice newValue = Slice(new_data, len); // 创建一个新的 Slice 对象
  271. batch.Put(key, newValue); // 将键值对放入 WriteBatch 中
  272. delete []new_data; // 释放分配的内存,防止内存泄漏
  273. return Write(opt, &batch); // 写入数据库
  274. }
  275. ```
  276. ##### 合并sstable内的数据
  277. ```c++
  278. Status DBImpl::DoCompactionWork(CompactionState* compact) {
  279. ···
  280. ···
  281. Iterator* input = versions_->MakeInputIterator(compact->compaction);
  282. // Release mutex while we're actually doing the compaction work
  283. mutex_.Unlock();
  284. input->SeekToFirst();
  285. Status status;
  286. ParsedInternalKey ikey;
  287. std::string current_user_key;
  288. bool has_current_user_key = false;
  289. SequenceNumber last_sequence_for_key = kMaxSequenceNumber;
  290. while (input->Valid() && !shutting_down_.load(std::memory_order_acquire)) {
  291. auto x=input->value(); // 获取键值对 value
  292. uint64_t ttl=*(uint64_t*)(x.data()+x.size()-sizeof(uint64_t));// 将 TTL 从 new_data 的末尾取出
  293. time_t now = time(nullptr); // 获得当前时间
  294. // 如果 TTL 超过当前时间,说明数据已经过期
  295. if(ttl < static_cast<uint64_t>(now)){
  296. Log(options_.info_log, "delete record for ttl");
  297. input->Next(); // 将 input 指向下一个键值对
  298. continue;
  299. }
  300. // Prioritize immutable compaction work
  301. if (has_imm_.load(std::memory_order_relaxed)) {
  302. const uint64_t imm_start = env_->NowMicros();
  303. mutex_.Lock();
  304. if (imm_ != nullptr) {
  305. CompactMemTable();
  306. // Wake up MakeRoomForWrite() if necessary.
  307. background_work_finished_signal_.SignalAll();
  308. }
  309. mutex_.Unlock();
  310. imm_micros += (env_->NowMicros() - imm_start);
  311. }
  312. ···
  313. ···
  314. }
  315. ```
  316. ##### 用于处理从sstable中读取的键值对
  317. ```c++
  318. static void SaveValue(void* arg, const Slice& ikey, const Slice& v) {
  319. Saver* s = reinterpret_cast<Saver*>(arg);
  320. ParsedInternalKey parsed_key;
  321. if (!ParseInternalKey(ikey, &parsed_key)) {
  322. s->state = kCorrupt;
  323. } else {
  324. if (s->ucmp->Compare(parsed_key.user_key, s->user_key) == 0) {
  325. // kTypeValue 对应被插入的数据
  326. if(parsed_key.type == kTypeValue){
  327. time_t now = time(nullptr);
  328. uint64_t ttl=*(uint64_t*)(v.data()+v.size()-sizeof(uint64_t)); // 将 TTL 从 new_data 的末尾取出
  329. if(ttl < static_cast<uint64_t>(now))return; // 如果 TTL 超过当前时间,说明数据已经过期,直接返回
  330. }
  331. s->state = (parsed_key.type == kTypeValue) ? kFound : kDeleted;
  332. if (s->state == kFound) {
  333. s->value->assign(v.data(), v.size()-sizeof(uint64_t));
  334. }
  335. }
  336. }
  337. }
  338. ```
  339. ##### 读取memtable内的数据
  340. ```c++
  341. bool MemTable::Get(const LookupKey& key, std::string* value, Status* s) {
  342. Slice memkey = key.memtable_key();
  343. Table::Iterator iter(&table_);
  344. iter.Seek(memkey.data());
  345. while (iter.Valid()) {
  346. const char* entry = iter.key();
  347. uint32_t key_length;
  348. const char* key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length);
  349. if (comparator_.comparator.user_comparator()->Compare(
  350. Slice(key_ptr, key_length - 8), key.user_key()) == 0) {
  351. // Correct user key
  352. const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
  353. switch (static_cast<ValueType>(tag & 0xff)) {
  354. // kTypeValue 对应被插入的数据
  355. case kTypeValue: {
  356. Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
  357. uint64_t ttl=*(uint64_t*)(v.data()+v.size()-sizeof(uint64_t)); // 将 TTL 从 new_data 的末尾取出
  358. time_t now = time(nullptr);
  359. // 如果 TTL 超过当前时间,说明数据已经过期
  360. if(ttl < static_cast<uint64_t>(now)){
  361. iter.Next(); // 将 iter 指向下一个键值对
  362. continue;
  363. }
  364. value->assign(v.data(), v.size()-sizeof(uint64_t));
  365. return true;
  366. }
  367. case kTypeDeletion:
  368. *s = Status::NotFound(Slice());
  369. return true;
  370. }
  371. }
  372. else break;
  373. }
  374. return false;
  375. }
  376. ```
  377. ##### 测试
  378. 1.将原有的两个测试的严格sleep ttl秒,改成sleep(ttl+1)秒,因为我们的设计是以秒为存储单位,可能存在一秒以内的读取误差,所以进行修改。(我们认为ttl无需毫秒/微妙级的数据准确性,一秒之内的误差可以接受,因此采取此种设计)
  379. 2.在原有设计上增加了第三个测试,其目的在于:①测试新的数据过期后,是否能够重新查找到旧但未过期的数据。②测试无TTL的接口是否可以正确运行,并且数据不过期。
  380. ```c++
  381. #include "gtest/gtest.h"
  382. #include "leveldb/env.h"
  383. #include "leveldb/db.h"
  384. using namespace leveldb;
  385. constexpr int value_size = 2048;
  386. constexpr int data_size = 128 << 20;
  387. Status OpenDB(std::string dbName, DB **db) {
  388. Options options;
  389. options.create_if_missing = true;
  390. return DB::Open(options, dbName, db);
  391. }
  392. void InsertData(DB *db, uint64_t ttl/* second */) {
  393. WriteOptions writeOptions;
  394. int key_num = data_size / value_size;
  395. srand(0);
  396. for (int i = 0; i < key_num; i++) {
  397. int key_ = rand() % key_num+1;
  398. std::string key = std::to_string(key_);
  399. std::string value(value_size, 'a');
  400. db->Put(writeOptions, key, value, ttl);
  401. }
  402. }
  403. void GetData(DB *db, int size = (1 << 30)) {
  404. ReadOptions readOptions;
  405. int key_num = data_size / value_size;
  406. // 点查
  407. srand(0);
  408. for (int i = 0; i < 100; i++) {
  409. int key_ = rand() % key_num+1;
  410. std::string key = std::to_string(key_);
  411. std::string value;
  412. db->Get(readOptions, key, &value);
  413. }
  414. }
  415. TEST(TestTTL, ReadTTL) {
  416. DB *db;
  417. if(OpenDB("testdb", &db).ok() == false) {
  418. std::cerr << "open db failed" << std::endl;
  419. abort();
  420. }
  421. uint64_t ttl = 20;
  422. InsertData(db, ttl);
  423. ReadOptions readOptions;
  424. Status status;
  425. int key_num = data_size / value_size;
  426. srand(0);
  427. for (int i = 0; i < 100; i++) {
  428. int key_ = rand() % key_num+1;
  429. std::string key = std::to_string(key_);
  430. std::string value;
  431. status = db->Get(readOptions, key, &value);
  432. ASSERT_TRUE(status.ok());
  433. }
  434. Env::Default()->SleepForMicroseconds((ttl+1) * 1000000);
  435. for (int i = 0; i < 100; i++) {
  436. int key_ = rand() % key_num+1;
  437. std::string key = std::to_string(key_);
  438. std::string value;
  439. status = db->Get(readOptions, key, &value);
  440. ASSERT_FALSE(status.ok());
  441. }
  442. delete db;
  443. }
  444. TEST(TestTTL, CompactionTTL) {
  445. DB *db;
  446. if(OpenDB("testdb", &db).ok() == false) {
  447. std::cerr << "open db failed" << std::endl;
  448. abort();
  449. }
  450. uint64_t ttl = 20;
  451. InsertData(db, ttl);
  452. leveldb::Range ranges[1];
  453. ranges[0] = leveldb::Range("-", "A");
  454. uint64_t sizes[1];
  455. db->GetApproximateSizes(ranges, 1, sizes);
  456. ASSERT_GT(sizes[0], 0);
  457. Env::Default()->SleepForMicroseconds((ttl+1) * 1000000);
  458. db->CompactRange(nullptr, nullptr);
  459. leveldb::Range ranges_1[1];
  460. ranges[0] = leveldb::Range("-", "A");
  461. uint64_t sizes_1[1];
  462. db->GetApproximateSizes(ranges_1, 1, sizes_1);
  463. ASSERT_EQ(sizes_1[0], 0);
  464. delete db;
  465. }
  466. TEST(TestTTL, OurTTL) {
  467. DB *db;
  468. WriteOptions writeOptions;
  469. ReadOptions readOptions;
  470. if(OpenDB("testdb_for_XOY", &db).ok() == false) {
  471. std::cerr << "open db failed" << std::endl;
  472. abort();
  473. }
  474. for (int i = 0; i < 10000; i++) {
  475. std::string key = std::to_string(i);
  476. std::string value = std::to_string(i);
  477. db->Put(writeOptions, key, value);//插入永不过期的key value(value=key)
  478. }
  479. for (int i = 0; i < 10000; i++) {
  480. std::string key = std::to_string(i);
  481. std::string value = std::to_string(i*2);
  482. db->Put(writeOptions, key, value, 30);//插入30秒后过期的key value(value=key*2)
  483. }
  484. for (int i = 0; i < 10000; i++) {
  485. std::string key = std::to_string(i);
  486. std::string value = std::to_string(i*3);
  487. db->Put(writeOptions, key, value, 15);//插入15秒后过期的key value(value=key*3)
  488. }
  489. for (int i = 0; i < 10000; i++) {
  490. std::string key = std::to_string(i);
  491. std::string value;
  492. Status status = db->Get(readOptions, key, &value);
  493. ASSERT_TRUE(status.ok());
  494. ASSERT_TRUE(value==std::to_string(i*3));//立刻读取,期望得到value=key*3
  495. }
  496. Env::Default()->SleepForMicroseconds((15+1) * 1000000);
  497. for (int i = 0; i < 10000; i++) {
  498. std::string key = std::to_string(i);
  499. std::string value;
  500. Status status = db->Get(readOptions, key, &value);
  501. ASSERT_TRUE(status.ok());
  502. ASSERT_TRUE(value==std::to_string(i*2));//16秒后读取,期望得到value=key*2
  503. }
  504. Env::Default()->SleepForMicroseconds((15+1) * 1000000);
  505. for (int i = 0; i < 10000; i++) {
  506. std::string key = std::to_string(i);
  507. std::string value;
  508. Status status = db->Get(readOptions, key, &value);
  509. ASSERT_TRUE(status.ok());
  510. ASSERT_TRUE(value==std::to_string(i));//32秒后读取,期望得到value=key
  511. }
  512. delete db;
  513. }
  514. int main(int argc, char** argv) {
  515. // All tests currently run with the same read-only file limits.
  516. testing::InitGoogleTest(&argc, argv);
  517. return RUN_ALL_TESTS();
  518. }
  519. ```
  520. 测试结果
  521. ![image-20241106220858020](ttl.assets/image-20241106220858020.png)