leveldb学习记录-文件

leveldb的文件命名

当运行一次leveldb来写入数据时,leveldb可能会生成很多个log文件和SSTable文件,而这些文件的命名都是类似的,都是固定前缀+文件编号+固定后缀的。这些文件的名称是调用函数MakeFileName()来生成的。

image-20201215195153429

如上图,运行./db_bench后,输出很多由数字编号的文件。

一般可能会存在以下文件内容:

  • LOCK database文件锁,leveldb通过文件锁来避免同一个db被多次打开操作。调用DB::Open()时会先获取该文件锁。
  • MANIFEST-XXXXXX,描述文件。XXXXX => [1, n),以LOG的形式写入VersionEdit数据。
  • CURRENT 表明当前正在使用哪个MANIFEST文件
  • LOGLOG.old 运行日志文件,当未指定options.info_log时,默认会将错误日志输出到该文件,每一次DB::Open()时,会将LOG重命名为LOG.old
  • .log 日志文件
  • .ldb db ssttable 持久化文件,新版本的后缀为.ldb,老版本后缀为.sst
  • .dbtmp 变更文件内容,中间过程中的临时文件。

1.文件命名

leveldb中与文件名创建相关的文件放在:

filename :leveldb/db/filename.cc

leveldb/db/filename.h

database中的文件用文件名区分类型,有以下几种类型

1
2
3
4
5
6
7
8
9
enum FileType {
kLogFile,
kDBLockFile,
kTableFile,
kDescriptorFile,
kCurrentFile,
kTempFile,
kInfoLogFile // Either the current one, or an old one
};

1) kLogFile 日志文件: [0-9]+.log

leveldb 的写流程是先记 binlog,然后写 sstable,该日志文件即是 binlog。前缀数字为FileNumber。
2) kDBLockFile, lock 文件: LOCK
​ 一个 db 同时只能有一个 db 实例操作,通过对 LOCK 文件加文件锁(flock) 实现主动保护。
3) kTableFile, sstable 文件: [0-9]+.sst
​ 保存数据的 sstable 文件。前缀为 FileNumber。
4) kDescriptorFile, db 元信息文件: MANIFEST-[0-9]+
​ 每当 db 中的状态改变(VersionSet),会将这次改变(VersionEdit) 追加到 descriptor 文件中。后缀数字为 FileNumber。
5) kCurrentFile,: CURRENT
​ CURRENT 文件中保存当前使用的 descriptor 文件的文件名。
6) kTempFile,临时文件: [0-9]+.dbtmp
​ 对 db 做修复(Repairer)时,会产生临时文件。 前缀为 FileNumber。
7) kInfoLogFile, db 运行时打印日志的文件: LOG
​ db 运行时,打印的 info 日志保存在 LOG 中。 每次重新运行,如果已经存在 LOG 文件,会先将 LOG文件重名成 LOG.old

文件命名的核心函数为MakeFileName

1
2
3
4
5
6
7
8
9
//返回形如"$dbname/$number.$suffix"的文件名,其中number使用6位数字输出,不足则左边补0,例如 db/000006.log
static std::string MakeFileName(const std::string& dbname, uint64_t number,
const char* suffix) {
char buf[100];
snprintf(buf, sizeof(buf), "/%06llu.%s",
static_cast<unsigned long long>(number),
suffix);
return dbname + buf;
}

由上述代码得知文件名是由dbname+6位number+后缀名组成的。比如db/000010.ldb(leveldb1.14版本之前后缀名是.sst,1.14版本之后采用.ldb)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
std::string LogFileName(const std::string& dbname, uint64_t number) {
assert(number > 0);
return MakeFileName(dbname, number, "log");
}

std::string TableFileName(const std::string& dbname, uint64_t number) {
assert(number > 0);
return MakeFileName(dbname, number, "ldb");
}

std::string SSTTableFileName(const std::string& dbname, uint64_t number) {
assert(number > 0);
return MakeFileName(dbname, number, "sst");
}

值得注意的是MAINIFEST文件没有像 TableFileNameSSTTableFileNameLogFileName调用MakeFileName函数

1
2
3
4
5
6
7
std::string DescriptorFileName(const std::string& dbname, uint64_t number) {
assert(number > 0);
char buf[100];
std::snprintf(buf, sizeof(buf), "/MANIFEST-%06llu",
static_cast<unsigned long long>(number));
return dbname + buf;
}

2.生成文件编号

db/version_set.h中保存了多个编号:

1
2
3
4
5
uint64_t next_file_number_;//用于生成系统下一个文件的编号
uint64_t manifest_file_number_;//Manifest文件的编号,主要在Recover()时用到
uint64_t last_sequence_;
uint64_t log_number_;//log文件编号
uint64_t prev_log_number_;

1
2
3
4
5
6
 file_number = versions_->NewFileNumber();//sstable文件的number

uint64_t new_log_number = versions_->NewFileNumber(); //log文件的number

// Allocate and return a new file number
uint64_t NewFileNumber() { return next_file_number_++; }//两者都调用的NewFileNumber函数

所以由以上代码可以发现sstable文件和log文件的number都是由version_set中的函数NewFileNumber()获得的,变化的值是next_file_number_,leveldb中的文件编号都是逐渐递增的。不存在相同编号的.log或.ldb。也就是说不可能出现000003.ldb和000003.log文件的情况。

在versionset的构造函数中,next_file_number_被初始化为2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
VersionSet::VersionSet(const std::string& dbname,
const Options* options,
TableCache* table_cache,
const InternalKeyComparator* cmp)
: env_(options->env),
dbname_(dbname),
options_(options),
table_cache_(table_cache),
icmp_(*cmp),
next_file_number_(2),//初始化为2
manifest_file_number_(0), // Filled by Recover()
last_sequence_(0),
log_number_(0),
prev_log_number_(0),
descriptor_file_(nullptr),
descriptor_log_(nullptr),
dummy_versions_(this),
current_(nullptr) {
AppendVersion(new Version(this));
}

文件编号1留给Mainifest使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Status DBImpl::NewDB() {
VersionEdit new_db;
new_db.SetComparatorName(user_comparator()->Name());
new_db.SetLogNumber(0);
new_db.SetNextFile(2);
new_db.SetLastSequence(0);

const std::string manifest = DescriptorFileName(dbname_, 1);//编号设置为1
WritableFile* file;
Status s = env_->NewWritableFile(manifest, &file);
if (!s.ok()) {
return s;
}
{
log::Writer log(file);
std::string record;
new_db.EncodeTo(&record);
s = log.AddRecord(record);
if (s.ok()) {
s = file->Close();
}
}
delete file;
if (s.ok()) {
// Make "CURRENT" file that points to the new manifest file.
s = SetCurrentFile(env_, dbname_, 1);
} else {
env_->DeleteFile(manifest);
}
return s;
}

Manifest文件记录了leveldb的元信息,包括数据库使用的Comparator名,以及各SSTable文件的管理信息:如Level层数、文件名、最小key和最大key等等。

总结

sstable, log, manifest 的编号都是由 versoinset 中NewFileNumber函数的 next_file_number_变量来指定。