3个稳定版本

使用旧Rust 2015

1.2.0 2020年8月11日
1.0.1 2018年10月17日

#1085 in 命令行实用工具

MIT 许可证

49KB
735

KaNiLS🦀

友好的Lump存储工具

这是一个使用Rust编写的工具,用于从命令行使用CannyLS(一个Key-Value存储)的基本功能。

CannyLS的基本机制

  • CannyLS是一个Key-Value存储,其Key是无符号128位长整数,Value是字节数组
  • CannyLS具有日志功能以保持存储的完整性
    (类似于日志文件系统中的日志)
  • CannyLS执行块大小写入
    • KaniLS中,一个块的大小是512字节
    • 示例1. 写入10字节数据时,会发生512字节(1块)的向上取整,并执行512字节的写入
    • 示例2. 写入513字节数据时,会发生1024字节(2块)的向上取整,并执行1024字节的写入
    • 从技术上讲,这是由于Linux实现中使用了O_DIRECT进行读写操作引起的
      (更多详情请参考 https://github.com/frugalos/cannyls/wiki/Terminology#ブロック

使用KaNiLS

KaNiLS是用Rust编写的。
如果没有安装Rust的开发环境,请使用 rustup 等工具进行安装。
(参考: https://rust-lang.net.cn/ja-JP/install.html)

从源代码构建

$ git clone https://github.com/frugalos/kanils
$ cd kanils
kanils$ cargo build # この場合は target/debug に kanilsバイナリができます
kanils$ cargo build --release # この場合は target/release に kanilsバイナリができます

使用Cargo进行安装

$ cargo install kanils

KaNiLS的功能

  • 创建 -- 创建存储文件
    • kanils Create--storage=storage_path--capacity=num
    • storage_path下创建一个包含num字节数据区域的cannyls存储文件(称为lusf文件)
  • 添加 -- 添加Key-Value对(覆盖)
    • kanils Put--storage=storage_path--key=num(128bit) --value=string
    • storage_path的lusf文件中添加key-value对<num, string>
    • 如果key num已存在,则会进行覆盖
  • 获取 -- 通过Key获取Key-Value对
    • kanils Get--storage=storage_path--key=num(128bit)
    • 使用key numstorage_path的lusf文件中读取数据
  • 删除 -- 通过Key删除Key-Value对
    • kanils Delete--storage=storage_path--key=num(128bit)
    • 使用key numstorage_path的lusf文件中删除数据
  • 头部 -- 获取lusf文件的头部信息(可以了解存储的各种信息)
    • kanils Header--storage=storage_path
  • 转储 -- 获取lusf文件的数据区域
    • kanils Dump--storage=storage_path
  • 日志 -- 获取lusf文件的日志区域
    • kanils Journal--storage=storage_path
  • 日志GC -- 对lusf文件的日志区域执行GC
    • kanils JournalGC--storage=storage_path
  • 打开 -- 文件打开
    • kanils Open--storage=storage_path
    • 打开存在的lusf文件storage_path并进入对话模式
    • 在对话模式中可以使用的命令有 put key valueget keydelete keydumpheaderjournaljournal_gc

使用KaNiLS操作CannyLS存储

# 2048バイトをデータ領域に割り当てるようなストレージファイルを作成
$ ./kanils Create --storage demo.lusf --capacity 2048
passed data region size = 2048
---------------
actual data region size = 2048
actual journal size = 1536
actual journal size ratio = 0.42857142857142855

# ストレージの様々な情報を確認
$ ./kanils Header --storage demo.lusf
header =>
  major version = 1
  minor version = 1
  block size = 512
  uuid = 731d2970-b03f-4f1b-9da8-8b4617ace5fc
  journal region size = 1536 // ジャーナル領域全体のサイズは以下2つからなる
    journal header size = 512 // ジャーナル領域のメタ情報を格納するヘッダ部分
    journal record size = 1024 // ジャーナルエントリを実際に書き込む部分
  data region size = 2048
  storage header size => 512
  storage total size = 4096

# (key=42, value="test_string")の組をストレージにput
$ ./kanils Put --storage demo.lusf --key 42 --value test_string
put key=42, value=test_string

# (key=7, value="🦀")の組をストレージにput
$ ./kanils Put --storage demo.lusf --key 7 --value 🦀         
put key=7, value=🦀

# 現在のストレージ中のデータ領域をダンプ
$ ./kanils Dump --storage demo.lusf
<lump list>
(LumpId("00000000000000000000000000000007"), "🦀")
(LumpId("0000000000000000000000000000002a"), "test_string")
</lump list>

# 現在のストレージ中のジャーナル領域をダンプ
$ ./kanils Journal --storage demo.lusf
journal [unreleased head] position = 0
journal [head] position = 0
journal [tail] position = 56
<journal entries>
JournalEntry { start: Address(0), record: Put(LumpId("0000000000000000000000000000002a"), DataPortion { start: Address(0), len: 1 }) }
JournalEntry { start: Address(28), record: Put(LumpId("00000000000000000000000000000007"), DataPortion { start: Address(1), len: 1 }) }
</journal entries>

# key=42を持つデータを削除
$ ./kanils Delete --storage demo.lusf --key 42
delete result => true

# 削除されたかどうかを確認
$ ./kanils Dump --storage demo.lusf 
<lump list>
(LumpId("00000000000000000000000000000007"), "🦀")
</lump list>

# ジャーナル領域を確認
## ジャーナルファイルシステムと同じ様に、ジャーナル領域には操作を記録していくので、
## PUTに対するDELETEの場合でも、データ領域のように打ち消し合って消えることはない
$ ./kanils Journal --storage demo.lusf
journal [unreleased head] position = 0
journal [head] position = 0
journal [tail] position = 77
<journal entries>
JournalEntry { start: Address(0), record: Put(LumpId("0000000000000000000000000000002a"), DataPortion { start: Address(0), len: 1 }) }
JournalEntry { start: Address(28), record: Put(LumpId("00000000000000000000000000000007"), DataPortion { start: Address(1), len: 1 }) }
JournalEntry { start: Address(56), record: Delete(LumpId("0000000000000000000000000000002a")) }
</journal entries>

# ジャーナル領域へのGC
## ただし、ジャーナル領域へのGCを実行することで、ジャーナル側の情報のうち、削除しても問題がないことが分かっているものを消すことができる。
## 削除しても問題がないジャーナルエントリ(もしくはGC対象になるジャーナルエントリ)についての詳細は
## https://github.com/frugalos/cannyls/wiki/Journal-Region-GC を参照
$ ./kanils JournalGC --storage demo.lusf
run journal full GC ...
journal full GC succeeded!

$ ./kanils Journal --storage demo.lusf  
journal [unreleased head] position = 77
journal [head] position = 77
journal [tail] position = 105
<journal entries>
JournalEntry { start: Address(77), record: Put(LumpId("00000000000000000000000000000007"), DataPortion { start: Address(1), len: 1 }) }
</journal entries>

$ ./kanils Put --storage demo.lusf --key 100 --value x
put key=100, value=x

$ ./kanils Put --storage demo.lusf --key 200 --value y
put key=200, value=y

$ ./kanils Put --storage demo.lusf --key 300 --value z
put key=300, value=z

# 5件目のデータを書き込もうとするとエラーになる。
# これは2048バイトをデータ領域に確保しており、かつ512バイトを1書き込みに使っているからである。
$ ./kanils Put --storage demo.lusf --key 400 --value o
thread 'main' panicked at '
EXPRESSION: self.storage.put(&lump_id, &lump_data)
ERROR: StorageFull (cause; assertion failed: `self.allocator.allocate(block_size).is_some()`)
HISTORY:
  [0] at /Users/ferris/.cargo/git/checkouts/cannyls-3a0f9a30cf1773f1/281ae5b/src/storage/data_region.rs:58
  [1] at /Users/ferris/.cargo/git/checkouts/cannyls-3a0f9a30cf1773f1/281ae5b/src/storage/mod.rs:350
  [2] at /Users/ferris/.cargo/git/checkouts/cannyls-3a0f9a30cf1773f1/281ae5b/src/storage/mod.rs:206
  [3] at src/main.rs:165

', src/main.rs:165:22
note: Run with `RUST_BACKTRACE=1` for a backtrace.

对话模式

如果逐一指定--storage storage_path很麻烦,那么可以在调用Create之后使用Open

上述操作在对话模式下的表现如下

$ ./kanils Create --storage demo.lusf --capacity 2048
passed data region size = 2048
---------------
actual data region size = 2048
actual journal size = 1536
actual journal size ratio = 0.42857142857142855

$ ./kanils Open --storage demo.lusf                  
>> put 42 test_string
put key=42, value=test_string
>> put 7 🦀
put key=7, value=🦀
>> dump
<lump list>
(LumpId("00000000000000000000000000000007"), "🦀")
(LumpId("0000000000000000000000000000002a"), "test_string")
</lump list>
>> journal
journal [unreleased head] position = 0
journal [head] position = 0
journal [tail] position = 56
<journal entries>
JournalEntry { start: Address(0), record: Put(LumpId("0000000000000000000000000000002a"), DataPortion { start: Address(0), len: 1 }) }
JournalEntry { start: Address(28), record: Put(LumpId("00000000000000000000000000000007"), DataPortion { start: Address(1), len: 1 }) }
</journal entries>
>> delete 42
delete result => true
>> dump
<lump list>
(LumpId("00000000000000000000000000000007"), "🦀")
</lump list>
>> journal
journal [unreleased head] position = 0
journal [head] position = 0
journal [tail] position = 77
<journal entries>
JournalEntry { start: Address(0), record: Put(LumpId("0000000000000000000000000000002a"), DataPortion { start: Address(0), len: 1 }) }
JournalEntry { start: Address(28), record: Put(LumpId("00000000000000000000000000000007"), DataPortion { start: Address(1), len: 1 }) }
JournalEntry { start: Address(56), record: Delete(LumpId("0000000000000000000000000000002a")) }
</journal entries>
>> journal_gc
run journal full GC ...
journal full GC succeeded!
>> journal
journal [unreleased head] position = 77
journal [head] position = 77
journal [tail] position = 105
<journal entries>
JournalEntry { start: Address(77), record: Put(LumpId("00000000000000000000000000000007"), DataPortion { start: Address(1), len: 1 }) }
</journal entries>
>> put 100 x
put key=100, value=x
>> put 200 y
put key=200, value=y
>> put 300 z
put key=300, value=z
>> put 400 o
thread 'main' panicked at '
EXPRESSION: self.storage.put(&lump_id, &lump_data)
ERROR: StorageFull (cause; assertion failed: `self.allocator.allocate(block_size).is_some()`)
HISTORY:
  [0] at /Users/ferris/.cargo/git/checkouts/cannyls-3a0f9a30cf1773f1/281ae5b/src/storage/data_region.rs:58
  [1] at /Users/ferris/.cargo/git/checkouts/cannyls-3a0f9a30cf1773f1/281ae5b/src/storage/mod.rs:350
  [2] at /Users/ferris/.cargo/git/checkouts/cannyls-3a0f9a30cf1773f1/281ae5b/src/storage/mod.rs:206
  [3] at src/main.rs:165

', src/main.rs:165:22
note: Run with `RUST_BACKTRACE=1` for a backtrace.

基准测试

顺序PUT & 随机GET

# test.lusfというファイルを作り
# 1件3MBのデータを1000件シーケンシャルに書き込み、
# その後に1000件をランダムにGETする。
kanils RandomGetBench --storage test.lusf --count 1000 --size 3MB

以下为输出示例

[src/bench.rs:91] path.clone() = "test.lusf"
[src/bench.rs:91] count = 1000
[src/bench.rs:91] size = 3145728
[Putting Data] start
  [00:00:03] [########################################] 1000/1000 (0s, done)
[Putting Data] finish @ 3s 507ms
[Getting Data] start
  [00:00:03] [########################################] 1000/1000 (0s, done)
[Getting Data] finish @ 3s 539ms

依赖项

~11-19MB
~260K SLoC