Compare commits

...

17 Commits
main ... main

Author SHA1 Message Date
  VirgilZhu 3a8791f173 add Commit ID 1 month ago
  VirgilZhu 258a4f2dac migrate to shuishan 1 month ago
  VirgilZhu 5b8eaab8e9 finish lab 1 month ago
  VirgilZhu 098cde3769 finish testCompactionTTL 1 month ago
  GUJIEJASON 604470f2c4 add some comments 1 month ago
  GUJIEJASON 59a258c3fd fix some bugs about ts 1 month ago
  GUJIEJASON 04fccda06f merge 1 month ago
  GUJIEJASON 614cf96bc7 fix some bugs in ttl_test 1 month ago
  VirgilZhu 2530c897ec finish ReadTTL 1 month ago
  GUJIEJASON 1c1970d241 Merge branch 'virgil' into jie 1 month ago
  VirgilZhu bc872072d3 fix db_test1/2 bug 1 month ago
  GUJIEJASON 1bf2665d03 Merge branch 'virgil' into jie 1 month ago
  GUJIEJASON c97d7cee71 init 1 month ago
  VirgilZhu 24828dd069 add TTL version Get() and Put() 1 month ago
  GUJIEJASON facd2ab6ff init repo 2 months ago
  root 2d26e4ab6d init repo 2 months ago
  GUJIEJASON 681aed6df6 init 2 months ago
13 changed files with 444 additions and 28 deletions
Split View
  1. +18
    -1
      CMakeLists.txt
  2. +0
    -7
      README.md
  3. +202
    -0
      TTL report.md
  4. +116
    -2
      db/db_impl.cc
  5. +7
    -0
      db/db_impl.h
  6. BIN
      image/passtest.png
  7. +3
    -0
      include/leveldb/db.h
  8. BIN
      passtest.png
  9. +26
    -0
      test/db_test1.cc
  10. +4
    -6
      test/db_test2.cc
  11. +26
    -0
      test/test_a.cpp
  12. +13
    -0
      test/time.cpp
  13. +29
    -12
      test/ttl_test.cc

+ 18
- 1
CMakeLists.txt View File

@ -112,6 +112,7 @@ include_directories(
if(BUILD_SHARED_LIBS)
# Only export LEVELDB_EXPORT symbols from the shared library.
add_compile_options(-fvisibility=hidden)
endif(BUILD_SHARED_LIBS)
# Must be included before CMAKE_INSTALL_INCLUDEDIR is used.
@ -518,9 +519,15 @@ if(LEVELDB_INSTALL)
)
endif(LEVELDB_INSTALL)
add_executable(db_test1
"${PROJECT_SOURCE_DIR}/test/db_test1.cc"
)
target_link_libraries(db_test1 leveldb)
add_executable(db_test2
"${PROJECT_SOURCE_DIR}/test/db_test2.cc"
test/time.cpp
)
target_link_libraries(db_test2 PRIVATE leveldb)
@ -528,4 +535,14 @@ target_link_libraries(db_test2 PRIVATE leveldb)
add_executable(ttl_test
"${PROJECT_SOURCE_DIR}/test/ttl_test.cc"
)
target_link_libraries(ttl_test PRIVATE leveldb gtest)
target_link_libraries(ttl_test PRIVATE leveldb gtest)
add_executable(test_a
"${PROJECT_SOURCE_DIR}/test/test_a.cpp"
)
target_link_libraries(test_a leveldb)
add_executable(time
"${PROJECT_SOURCE_DIR}/test/time.cpp"
)
target_link_libraries(time leveldb)

+ 0
- 7
README.md View File

@ -1,9 +1,2 @@
**本仓库提供TTL基本的测试用例**
克隆代码:
```bash
git clone --recurse-submodules https://gitea.shuishan.net.cn/building_data_management_systems.Xuanzhou.2024Fall.DaSE/leveldb_base.git
```

+ 202
- 0
TTL report.md View File

@ -0,0 +1,202 @@
<h1>
<center>
LevelDB TTL Lab Report
</center>
</h1>
> <center>
> 朱维清 10215300402 ### commitID——VirgilZhu
> </center>
> <center>
> 谷杰 10222140408 ### commitID——GUJIEJASON
> </center>
> <center>
> Github 仓库地址:https://github.com/VirgilZhu/LevelDB-course-Lab
> </center>
## 一、实验背景
- **TTL**(Time To Live,生存时间):
> ​ LevelDB 中,根据 TTL 设置了死亡时间的 KV 数据对应在被读取、合并过程中,判断其是否存活(数据对有效),若超过死亡时间,则进行丢弃处理。
> ​ 对所有 KV 数据对加入 TTL 属性后,能使 LevelDB 的 SStable 所占空间有效降低,存储数据回收机制进一步合理化。
+ **实验要求:**
1. 在 LevelDB 中实现键值对的 TTL 功能,使得过期的数据在**读取时自动失效**,并在适当的时候**被合并清理**:
- **Put()**:键值对插入函数,使得调用它时每个插入的键值对应包含 TTL 属性,便于读取、合并过程中被访问时,进行 TTL 相关处理;
- **Get()**:获取键值对的 Key、Value,使得调用它时能触发相应 TTL 判断流程,若数据对已过期,在相应函数进行键值对丢弃处理;
- **Compaction 流程**相关函数:厘清 Minor Compaction、Major Compaction 的具体实现流程,在相关函数对数据对粒度的处理部分的适当位置加入判断数据对是否过期的代码,并触发可能的相应回收处理。
2. 编写/修改测试用例,验证 TTL 功能的正确性和稳定性
## 二、具体实现
### Put() 函数实现
**Q**:测试用例在传入 TTL 参数后,Put() 函数应将死亡时间`deadtime`信息存入键值对的`key`还是`value`部分?
**A**:我们选择**将`deadtime`存在`value`中**,好处是存在`value`中我们可以更容易地实现不同的过期策略,也可以更方便地修改过期时间,同时也不需要更新更加复杂的`key`匹配和查找机制,更重要的是在`value`中添加会有更好的扩展性,未来也可以在`value`中添加更多其他的信息。
```c++
/* TODO: Add TTL Version Put() */
Status DBImpl::Put(const WriteOptions& opt, const Slice& key, const Slice& value, uint64_t ttl){
WriteBatch batch;
auto now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
auto end = now + ttl;
Slice value_timestamp = Slice(value.ToString() + "_ts_" + std::to_string(end));
batch.Put(key, value_timestamp);
return Write(opt, &batch);
}
/* --------------------------- */
```
1. 对于`Put`操作,我们选择在保留原有的`Put`接口的基础上,增加一个新的`Put`接口,其包含`TTL`参数,单位为**秒**。如果我们调用原本的`Put`接口,则该键值对不会有`TTL`,如果调用新的`Put`接口,则会**在`value`部分的末段加上`_ts_`字段和该键值对的`deadtime`**。
2. 同时,对于如何获取时间,我们选择的是调用 C++ 标准库中的**`std::chrono::steady_clock`**这一时钟类型,它的特点是单调递增,不会受到系统时间调整的影响。我们先是使用`now()`这一静态成员函数返回当前时间点的`time_point`对象,再调用`time_point`中的一个成员函数`time_since_epoch()`返回从纪元开始到当前时间点的时间间隔,这是一个`duration`对象,接着通过`std::chrono::duration_cast`将时间间隔的单位转换为秒,最后调用`duration的count()`函数返回一个表示秒数的整数。
3. 这样,我们就成功地得到了当前时间的时间戳,单位为秒,接着只需要加上我们传入的`ttl`,便是该键值对的`deadtime`,最后将带有`deadtime`的`value`和`key`封装成`Slice`,使用 `WriteBatch` 对象的 `Put` 方法将带有时间戳的新键值对加入到批处理操作中,再调用 `Write` 方法将 `WriteBatch` 中的所有操作应用到数据库中,并返回操作的状态。这样便完成了带有`ttl`的写入操作。
4. 同时,由于我们是重新定义了一个`Put`接口,别忘了在头文件中加入新`Put`接口的声明。
```c++
/* TODO: Add TTL Version Put() */
Status Put(const WriteOptions&, const Slice& key,
const Slice& value, uint64_t ttl) override;
/* --------------------------- */
```
---
### Get() 函数实现
```c++
/* TODO: Add TTL Version Get() */
if (mem->Get(lkey, value, &s)) {
isLive(key, value, s, ttl);
// Done
} else if (imm != nullptr && imm->Get(lkey, value, &s)) {
isLive(key, value, s, ttl);
// Done
} else {
s = current->Get(options, lkey, value, &stats);
if (s.ok()) isLive(key, value, s, ttl);
have_stat_update = true;
}
/* --------------------------- */
```
+ 对于`Get`即读取操作,我们在原本的基础上只是增加了一个判断是否过期的功能,即对读取的键值对加一个判断,如果存在`ttl`且已经过期则不再读取。代码上在原本的基础上只是增加了一个`isLive`的判断函数。
```c++
/* TODO: Add TTL Version isLive() */
Status isLive(const Slice& key, std::string* value, Status& s) {
if (value->empty()) {
s = Status::NotFound(key);
return s;
}
uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
size_t pos = value->rfind("_ts_");
if (pos != std::string::npos){
std::string timestampStr = value->substr(pos + 4);
if (isAllDigits(timestampStr)) {
uint64_t deadtime = std::stoull(timestampStr);
if (now >= deadtime) {
s = Status::NotFound(key);
}
}
}
return s;
}
/* --------------------------- */
/* TODO: Check if a string consists entirely of digits */
bool isAllDigits(const std::string& str) {
for (char c : str) {
if (!isdigit(c)) {
return false;
}
}
return true;
}
/* --------------------------- */
```
1. 首先是先判断`value`是否为空,若为空则表示该键不存在或已被删除,返回`NotFound`。接着便是同`Put`操作中一样通过调用`std::chrono::steady_clock`来获取当前时间的时间戳。
2. 通过`rfind`函数从后向前找到第一个`_ts_`字段的`pos`。若存在`_ts_`字段,则通过`pos`和`substr`函数来获取`_ts_`字段后面的内容,类型为字符串。接着这里有一个`isAllDigits`函数来判断一个字符串的内容是否全为数字,用到的是`isdigit()`函数。在确认全为数字后便通过`std::stoull`函数转换为`unsigned long long`整数,再与当前时间戳比较,若已过期,则返回`NotFound`。
3. ⭐`std::stoull` 只能解析字符串中有效的数字部分,直到遇到第一个无法解析为数字的字符为止。例如如果我们调用原本的`Put`接口,设置`value`为`“aaaaaa_ts_5678aaaaaaa”`,对于获取的字符串`timestampStr` `"5678aaaaaaa"`,`std::stoull` 会解析出前缀的数字部分 `"5678"`,然后停止解析,忽略后续的非数字字符,这样本来一个无`ttl`的键值对被当成了有`ttl`,会导致数据丢失(在有无 TTL 参数与有 TTL 参数的 `Put()` 函数同时随机调用来插入数据对的情况下可能发生的情况)。
---
### Compaction 流程相关函数实现
> **LevelDB 触发一次 Compaction 流程的函数调用关系:**
>
> 1. 参阅源码可知,每次 Compaction 流程的调用源头均为 `DBImpl::MaybeScheduleCompaction()` 函数,该函数判断当前是否可以调度一次 Compaction 流程后,若符合条件,会在“background”调度`DBImpl::BGWork`,触发`DBImpl::BackgroundCall()`函数,再通过加锁与判断,触发`BackgroundCompaction()`函数,正式进行一次 Compaction 流程;
> 2. `BackgroundCompaction()`函数详细解释了一次完整的 Compaction 流程,包括:
> + 当前 LevelDB 并不存在 immutable memtable 的情况下会进行一次`CompactMemtable()` ,使 memtable 转变为一个 immutable table;
> + 判断是否为手动合并(manual compaction),即传入的键值对合并范围 `begin`、`end` 是否均为 `nullptr`,若是,则调用确定合并范围的函数`CompactionRange()`;
> + 实现一些 Compaction 流程的 Log 工作与回收工作;
> 3. 在记录 Log 的同时,调用`DBImpl::DoCompactionWork()`函数,该函数是对 KV 键值对粒度进行实际读取与处理的函数。在经过详细的判断流程后,通过`drop`这一变量,实时记录当前访问的键值对是否需要丢弃,而本次 Lab 需要实现的 Compaction 流程中丢弃过期数据对这一工作正是在此处实现——在原本的判断是否丢弃的条件(如已经遍历到最后有数据对存储的 SStable 层;上一层存有写入数据库更晚的相同 key 数据对;上一层有正要合并到此层的更新的相同 key 数据对)之后,加入判断该键值对是否过期,若过期则将`drop`置为`True`。
+ TTL 在 Compaction 流程的具体处理是在`DBImpl::DoCompactionWork`函数中多加一个`if`判断:
```c++
/* TODO: Add TTL Version Compaction Drop Condition */
else {
std::string user_value = input->value().ToString();
uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
size_t pos = user_value.rfind("_ts_");
if (pos != std::string::npos){
std::string timestampStr = user_value.substr(pos + 4);
if (isAllDigits(timestampStr)) {
uint64_t deadtime = std::stoull(timestampStr);
if (now >= deadtime) {
drop = true;
}
}
}
}
/* ----------------------------------------------- */
```
​ 首先先将`value`转换为一个字符串类型,再用之前同样的方法,调用`std::chrono::steady_clock`来获取当前时间的时间戳,后面便是与`isLive`函数类似,若存在`_ts_`字段并且成功获取`deadtime`来与当前时间戳进行比较从而判断是否过期,若过期则调用`drop`将其删除。
## 实验中遇到的问题和解决方案
1. TestTTL.ReadTTL 测试样例始终报错的情况:
+ 两次测试调用 OpenDB() 函数时,修改`dbName`为不同名字,避免出现访问持有锁的 LevelDB 的情况;
+ 在测试过程中打印所有 `Put()` 函数插入的数据对,以及 `Get()` 函数在读取到数据对后,打印该数据对是否过期,并打印该数据对的死亡时间与当前时钟信息:
![image-20241104234444773](./../AppData/Roaming/Typora/typora-user-images/image-20241104234444773.png)
+ 修改`srand()`函数传入的随机种子为固定(`srand(42)`),保证查找读取的 key 为先前插入的相同的 key。
2. TestTTL.CompactionTTL 测试样例,打印信息`ApproximateSizes after TTL`始终不为0,且数值仅比`ApproximateSizes before TTL`小一部分:
+ 查看测试样例可知,测试样例调用的 `CompactRange(nullptr, nullptr)` 函数仅触发了手动合并,而 LevelDB 内部的自动合并可能在手动合并前就已经把部分数据合并为有序并存入更深层的 SStable 中,对此的解决方案采取了强迫每次手动合并都要遍历、合并数据对直到存有数据对的最深层Level(或直接合并所有SStable);
+ 经查阅源码,确定每次合并的最大范围(即`end`参数)的设置在`DBImpl::CompactRange()`函数中,由`max_level_with_files`参数决定。该参数意为存有数据对的 SStable 所在的最深`level`,而该`level`参数将传入`Test_CompactRange()`函数中,被设置为`ManualCompaction manual`变量的字段,而该变量`manual`将被赋值到`manual_compaction_`中,决定了该次手动合并的最深层和最大`key`值——只需要修改`max_level_with_files`为测试样例插入的数据经自动合并后存储了 SStable 的最深层,或是 LevelDB 的 SStable 存储最深层`config::kNumLevels - 1`(需要 pass `assert(level + 1 < config::kNumLevels)` 判断条件,即每次合并的最深层和更深一层是最后一次 compaction 涉及的 SStables 所在的两层):
```c++
void DBImpl::CompactRange(const Slice* begin, const Slice* end) {
int max_level_with_files = 1;
{
MutexLock l(&mutex_);
Version* base = versions_->current();
for (int level = 1; level < config::kNumLevels; level++) {
if (base->OverlapInLevel(level, begin, end)) {
max_level_with_files = level;
}
}
}
TEST_CompactMemTable(); // TODO(sanjay): Skip if memtable does not overlap
for (int level = 0; level <= max_level_with_files; level++) {
// for (int level = 0; level < config::kNumLevels - 1; level++) {
//Original: for (int level = 0; level < max_level_with_files; level++) {
TEST_CompactRange(level, begin, end);
}
}
```
## 测试样例 Pass 截图
![passtest.png](image/passtest.png)

+ 116
- 2
db/db_impl.cc View File

@ -2,12 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.
#include <chrono>
#include <iostream>
#include "db/db_impl.h"
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <cstdio>
#include <cctype>
#include <set>
#include <string>
#include <vector>
@ -591,7 +595,8 @@ void DBImpl::CompactRange(const Slice* begin, const Slice* end) {
}
}
TEST_CompactMemTable(); // TODO(sanjay): Skip if memtable does not overlap
for (int level = 0; level < max_level_with_files; level++) {
for (int level = 0; level <= max_level_with_files; level++) {
// for (int level = 0; level < max_level_with_files; level++) {
TEST_CompactRange(level, begin, end);
}
}
@ -894,6 +899,17 @@ Status DBImpl::InstallCompactionResults(CompactionState* compact) {
return versions_->LogAndApply(compact->compaction->edit(), &mutex_);
}
/* TODO: Check if a string consists entirely of digits */
bool isAllDigits(const std::string& str) {
for (char c : str) {
if (!isdigit(c)) {
return false;
}
}
return true;
}
/* --------------------------- */
Status DBImpl::DoCompactionWork(CompactionState* compact) {
const uint64_t start_micros = env_->NowMicros();
int64_t imm_micros = 0; // Micros spent doing imm_ compactions
@ -978,7 +994,31 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) {
// Therefore this deletion marker is obsolete and can be dropped.
drop = true;
}
/* TODO: Add TTL Version Compaction Drop Condition */
else {
std::string user_value = input->value().ToString();
uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
// if (user_value.find("_ts_") == std::string::npos) {
// input->value() = user_value + "_ts_" + std::to_string(now + ttl);
// }
// else {
// uint64_t deadtime = std::stoi(user_value.substr(user_value.find("_ts_") + 4));
// if (now >= deadtime) {
// drop = true;
// }
// }
size_t pos = user_value.rfind("_ts_");
if (pos != std::string::npos){
std::string timestampStr = user_value.substr(pos + 4);
if (isAllDigits(timestampStr)) {
uint64_t deadtime = std::stoull(timestampStr);
if (now >= deadtime) {
drop = true;
}
}
}
}
/* ----------------------------------------------- */
last_sequence_for_key = ikey.sequence;
}
#if 0
@ -1117,6 +1157,44 @@ int64_t DBImpl::TEST_MaxNextLevelOverlappingBytes() {
return versions_->MaxNextLevelOverlappingBytes();
}
/* TODO: Add TTL Version isLive() */
// Status isLive(const Slice& key, std::string* value, Status& s, uint64_t ttl) {
// if (value->empty()) {
// s = Status::NotFound(key);
// return s;
// }
// uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
// if (value->find("_ts_") == std::string::npos) {
// *value = *value + "_ts_" + std::to_string(now + ttl);
// }
// else {
// uint64_t deadtime = std::stoi(value->substr(value->find("_ts_") + 4));
// if (now >= deadtime) {
// s = Status::NotFound(key);
// }
// }
// return s;
// }
/* --------------------------- */
Status isLive(const Slice& key, std::string* value, Status& s) {
if (value->empty()) {
s = Status::NotFound(key);
return s;
}
uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
size_t pos = value->rfind("_ts_");
if (pos != std::string::npos){
std::string timestampStr = value->substr(pos + 4);
if (isAllDigits(timestampStr)) {
uint64_t deadtime = std::stoull(timestampStr);
if (now >= deadtime) {
s = Status::NotFound(key);
}
}
}
return s;
}
Status DBImpl::Get(const ReadOptions& options, const Slice& key,
std::string* value) {
Status s;
@ -1144,14 +1222,24 @@ Status DBImpl::Get(const ReadOptions& options, const Slice& key,
mutex_.Unlock();
// First look in the memtable, then in the immutable memtable (if any).
LookupKey lkey(key, snapshot);
/* TODO: Add TTL Version Get() */
if (mem->Get(lkey, value, &s)) {
// isLive(key, value, s, ttl);
isLive(key, value, s);
// Done
} else if (imm != nullptr && imm->Get(lkey, value, &s)) {
// isLive(key, value, s, ttl);
isLive(key, value, s);
// Done
} else {
s = current->Get(options, lkey, value, &stats);
// if (s.ok()) isLive(key, value, s, ttl);
if (s.ok()) isLive(key, value, s);
have_stat_update = true;
}
/* --------------------------- */
mutex_.Lock();
}
@ -1491,6 +1579,17 @@ Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) {
return Write(opt, &batch);
}
/* TODO: Add TTL Version Put() */
Status DBImpl::Put(const WriteOptions& opt, const Slice& key, const Slice& value, uint64_t ttl){
WriteBatch batch;
auto now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
auto end = now + ttl;
Slice value_timestamp = Slice(value.ToString() + "_ts_" + std::to_string(end));
batch.Put(key, value_timestamp);
return Write(opt, &batch);
}
/* --------------------------- */
Status DB::Delete(const WriteOptions& opt, const Slice& key) {
WriteBatch batch;
batch.Delete(key);
@ -1508,12 +1607,22 @@ Status DB::Open(const Options& options, const std::string& dbname, DB** dbptr) {
// Recover handles create_if_missing, error_if_exists
bool save_manifest = false;
Status s = impl->Recover(&edit, &save_manifest);
if (!s.ok()) {
std::cerr << "Recover failed: " << s.ToString() << std::endl;
}
if (s.ok() && impl->mem_ == nullptr) {
// Create new log and a corresponding memtable.
uint64_t new_log_number = impl->versions_->NewFileNumber();
WritableFile* lfile;
s = options.env->NewWritableFile(LogFileName(dbname, new_log_number),
&lfile);
if (!s.ok()) {
std::cerr << "NewWritableFile failed: " << s.ToString() << std::endl;
}
if (s.ok()) {
edit.SetLogNumber(new_log_number);
impl->logfile_ = lfile;
@ -1527,6 +1636,11 @@ Status DB::Open(const Options& options, const std::string& dbname, DB** dbptr) {
edit.SetPrevLogNumber(0); // No older logs needed after recovery.
edit.SetLogNumber(impl->logfile_number_);
s = impl->versions_->LogAndApply(&edit, &impl->mutex_);
if (!s.ok()) {
std::cerr << "LogAndApply failed: " << s.ToString() << std::endl;
}
}
if (s.ok()) {
impl->RemoveObsoleteFiles();

+ 7
- 0
db/db_impl.h View File

@ -28,6 +28,7 @@ class VersionSet;
class DBImpl : public DB {
public:
uint64_t ttl;
DBImpl(const Options& options, const std::string& dbname);
DBImpl(const DBImpl&) = delete;
@ -42,6 +43,12 @@ class DBImpl : public DB {
Status Write(const WriteOptions& options, WriteBatch* updates) override;
Status Get(const ReadOptions& options, const Slice& key,
std::string* value) override;
/* TODO: Add TTL Version Put() */
Status Put(const WriteOptions&, const Slice& key,
const Slice& value, uint64_t ttl) override;
/* --------------------------- */
Iterator* NewIterator(const ReadOptions&) override;
const Snapshot* GetSnapshot() override;
void ReleaseSnapshot(const Snapshot* snapshot) override;

BIN
image/passtest.png View File

Before After
Width: 1014  |  Height: 533  |  Size: 59 KiB

+ 3
- 0
include/leveldb/db.h View File

@ -54,6 +54,7 @@ class LEVELDB_EXPORT DB {
DB** dbptr);
DB() = default;
uint64_t ttl;
DB(const DB&) = delete;
DB& operator=(const DB&) = delete;
@ -148,8 +149,10 @@ class LEVELDB_EXPORT DB {
// ----------------------------For TTL-----------------------------
// key设置ttl
/* TODO: Add TTL Version Put() */
virtual Status Put(const WriteOptions& options, const Slice& key,
const Slice& value, uint64_t ttl) = 0;
/* --------------------------- */
};
// Destroy the contents of the specified database.

BIN
passtest.png View File

Before After
Width: 1014  |  Height: 533  |  Size: 59 KiB

+ 26
- 0
test/db_test1.cc View File

@ -0,0 +1,26 @@
#include "leveldb/db.h"
#include <iostream>
using namespace std;
using namespace leveldb;
int main() {
DB* db = nullptr;
Options op;
op.create_if_missing = true;
Status status = DB::Open(op, "testdb", &db);
assert(status.ok());
db->Put(WriteOptions(), "001", "leveldb");
string s;
db->Get(ReadOptions(), "001", &s);
cout<<s<<endl;
db->Put(WriteOptions(), "002", "world");
string s1;
db->Delete(WriteOptions(), "002");
db->Get(ReadOptions(), "002", &s1);
cout<<s1<<endl;
delete db;
return 0;
}

+ 4
- 6
test/db_test2.cc View File

@ -1,10 +1,8 @@
#include "leveldb/db.h"
#include "leveldb/filter_policy.h"
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace leveldb;
constexpr int value_size = 2048;
@ -37,7 +35,7 @@ void InsertData(DB *db) {
void GetData(DB *db, int size = (1 << 30)) {
ReadOptions readOptions;
int key_num = data_size / value_size;
// 点查
srand(0);
for (int i = 0; i < 100; i++) {
@ -45,6 +43,7 @@ void GetData(DB *db, int size = (1 << 30)) {
std::string key = std::to_string(key_);
std::string value;
db->Get(readOptions, key, &value);
std::cout << value << std::endl;
}
// 范围查询
@ -68,7 +67,6 @@ int main() {
GetData(db);
delete db;
}
return 0;
}
return 0;
}

+ 26
- 0
test/test_a.cpp View File

@ -0,0 +1,26 @@
#include "leveldb/db.h"
#include <iostream>
using namespace std;
using namespace leveldb;
int main() {
DB* db = nullptr;
Options op;
op.create_if_missing = true;
Status status = DB::Open(op, "testdb", &db);
assert(status.ok());
db->Put(WriteOptions(), "001", "leveldb");
string s;
db->Get(ReadOptions(), "001", &s);
cout<<s<<endl;
db->Put(WriteOptions(), "002", "world");
string s1;
db->Delete(WriteOptions(), "002");
db->Get(ReadOptions(), "002", &s1);
cout<<s1<<endl;
delete db;
return 0;
}

+ 13
- 0
test/time.cpp View File

@ -0,0 +1,13 @@
#include <iostream>
#include <chrono>
//int main() {
// auto now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
// auto end = now + 5;
//
// // 输出当前时间点和未来时间点
// std::cout << "Current time point: " << now << std::endl;
// std::cout << "Future time point (+5 seconds): " << end << std::endl;
//
// return 0;
//}

+ 29
- 12
test/ttl_test.cc View File

@ -1,17 +1,18 @@
#include <chrono>
#include "gtest/gtest.h"
#include "leveldb/env.h"
#include "leveldb/db.h"
using namespace leveldb;
constexpr int value_size = 2048;
constexpr int data_size = 128 << 20;
Status OpenDB(std::string dbName, DB **db) {
std::string rm_command = "rm -rf " + dbName;
system(rm_command.c_str());
Options options;
options.create_if_missing = true;
return DB::Open(options, dbName, db);
@ -20,14 +21,24 @@ Status OpenDB(std::string dbName, DB **db) {
void InsertData(DB *db, uint64_t ttl/* second */) {
WriteOptions writeOptions;
int key_num = data_size / value_size;
srand(0);
srand(42);
for (int i = 0; i < key_num; i++) {
int key_ = rand() % key_num+1;
std::string key = std::to_string(key_);
std::string value(value_size, 'a');
db->ttl = ttl;
db->Put(writeOptions, key, value, ttl);
}
// for (int i = 0; i < key_num; i++) {
// int key_ = rand() % key_num+1;
// std::string key = std::to_string(key_ );
// std::string value = "aaaaaa_ts_5678aaaaaa";
// db->Put(writeOptions, key, value);
// }
}
void GetData(DB *db, int size = (1 << 30)) {
@ -35,7 +46,7 @@ void GetData(DB *db, int size = (1 << 30)) {
int key_num = data_size / value_size;
// 点查
srand(0);
srand(42);
for (int i = 0; i < 100; i++) {
int key_ = rand() % key_num+1;
std::string key = std::to_string(key_);
@ -46,7 +57,7 @@ void GetData(DB *db, int size = (1 << 30)) {
TEST(TestTTL, ReadTTL) {
DB *db;
if(OpenDB("testdb", &db).ok() == false) {
if(OpenDB("testdb_ReadTTL", &db).ok() == false) {
std::cerr << "open db failed" << std::endl;
abort();
}
@ -58,12 +69,14 @@ TEST(TestTTL, ReadTTL) {
ReadOptions readOptions;
Status status;
int key_num = data_size / value_size;
srand(0);
srand(42);
for (int i = 0; i < 100; i++) {
int key_ = rand() % key_num+1;
std::string key = std::to_string(key_);
std::string value;
status = db->Get(readOptions, key, &value);
uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
std::cout << status.ToString() << " key: " << key << " value: ******" << value.substr(value.find("_ts_")) << " now: " << now << std::endl;
ASSERT_TRUE(status.ok());
}
@ -74,6 +87,8 @@ TEST(TestTTL, ReadTTL) {
std::string key = std::to_string(key_);
std::string value;
status = db->Get(readOptions, key, &value);
uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
std::cout << status.ToString() << " key: " << key << " value: ******" << value.substr(value.find("_ts_")) << " now: " << now << std::endl;
ASSERT_FALSE(status.ok());
}
}
@ -81,8 +96,8 @@ TEST(TestTTL, ReadTTL) {
TEST(TestTTL, CompactionTTL) {
DB *db;
if(OpenDB("testdb", &db).ok() == false) {
std::cerr << "open db failed" << std::endl;
if(OpenDB("testdb_CompactionTTL", &db).ok() == false) {
std::cerr << "open db failed" << OpenDB("testdb_CompactionTTL", &db).ToString() << std::endl;
abort();
}
@ -93,16 +108,18 @@ TEST(TestTTL, CompactionTTL) {
ranges[0] = leveldb::Range("-", "A");
uint64_t sizes[1];
db->GetApproximateSizes(ranges, 1, sizes);
std::cout << "ApproximateSizes before TTL: " << sizes[0] << std::endl;
ASSERT_GT(sizes[0], 0);
Env::Default()->SleepForMicroseconds(ttl * 1000000);
db->CompactRange(nullptr, nullptr);
leveldb::Range ranges[1];
// leveldb::Range ranges[1];
ranges[0] = leveldb::Range("-", "A");
uint64_t sizes[1];
// uint64_t sizes[1];
db->GetApproximateSizes(ranges, 1, sizes);
std::cout << "ApproximateSizes after TTL: " << sizes[0] << std::endl;
ASSERT_EQ(sizes[0], 0);
}
@ -111,4 +128,4 @@ int main(int argc, char** argv) {
// All tests currently run with the same read-only file limits.
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
}

Loading…
Cancel
Save