CLOVER🍀

That was when it all began.

PythonでRocksDBを使う

これは、なにをしたくて書いたもの?

ふと、RocksDBのPythonバインディングを使ってみようかなと思いまして。

python-rocksdb

RocksDB自体はC++で作られていますが、pytyon-rocksdbを使うことでPythonからも利用することができます。

Welcome to python-rocksdb’s documentation! — python-rocksdb 0.6.7 documentation

GitHubリポジトリは、こちら。

GitHub - twmht/python-rocksdb: Python bindings for RocksDB

なのですが、よくよく見るとオリジナルはこちらのリポジトリ(pyrocksdb)で、メンテナンスされたなくなったものが
forkされたようです。

GitHub - stephan-hof/pyrocksdb: Python bindings for RocksDB

APIドキュメントは、こちら。

Python driver for RocksDB — python-rocksdb 0.6.7 documentation

環境

今回の環境は、こちらです。

$ python3 -V
Python 3.6.9

$ pip3 -V
pip 9.0.1 from /path/to/venv/lib/python3.6/site-packages (python 3.6)

OSは、Ubuntu Linux 18.04 LTSです。

$ uname -srvmpio
Linux 4.15.0-99-generic #100-Ubuntu SMP Wed Apr 22 20:32:56 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux


$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:   bionic

python-rocksdbをインストールする

インストールは、こちらを参考にしながら。OSのパッケージインストールも行う必要があるようです。

Installing — python-rocksdb 0.6.7 documentation

なのですが、けっこうハマりました。

Python 3は入っているので、ドキュメントを見て「python3-dev」、「librocksdb-dev」をインストール。

$ sudo apt install python3-dev librocksdb-dev

自分の環境では、こちらも足りなかったのでインストールすることに。

$ sudo apt install libsnappy-dev libbz2-dev liblz4-dev

そしてインストールですが、ドキュメントを見ると「python-rocksdb」だけがあればよさそうですが、実は「Cython」も必要になります。

$ pip3 install Cython python-rocksdb

Cythonが入っていないと、python-rocksdbのインストール時のビルドで失敗します。

    rocksdb/_rocksdb.cpp:1:2: error: #error Do not use this file, it is the result of a failed Cython compilation.
     #error Do not use this file, it is the result of a failed Cython compilation.
      ^~~~~

あと、ライブラリが足りていなかったので、このあたりでもエラーになりました。

    /usr/bin/ld: -lsnappy が見つかりません
    /usr/bin/ld: -lbz2 が見つかりません
    /usr/bin/ld: -llz4 が見つかりません

動作確認には、テストコードを使うことにするので、pytestをインスールします。

$ pip3 install pytest

バージョン。

$ pip3 freeze | grep -E 'Cython|rocksdb|pytest'
Cython==0.29.17
pytest==5.4.2
python-rocksdb==0.7.0

なんとか、インストールまで完了です。

テストコードの雛形

pytyon-rocksdbを使った確認は、テストコードを使って行います。

雛形として意したのは、こちら。
tests/rocsdb_test.py

import rocksdb

## ここに、テストを書く!

import文が1行あるだけですが…。

では、Tutorialを見ながら使っていってみましょう。

Basic Usage of python-rocksdb — python-rocksdb 0.6.7 documentation

基本的な使い方

最初は、簡単なput/get/deleteから。

作成したコードは、こちら。

def test_rocksdb_getteing_started():
    options = rocksdb.Options()
    options.create_if_missing = True
    # more options...

    db = rocksdb.DB("example-db", options)

    db.put(b"key1", b"value1")
    db.put(b"key2", b"value2")

    assert db.get(b"key1") == b"value1"
    assert db.get(b"key2") == b"value2"

    db.delete(b"key1")
    db.delete(b"key2")

    assert db.get(b"key1") is None
    assert db.get(b"key2") is None

最初に、オプションを指定してDBインスタンスを作成します。

    options = rocksdb.Options()
    options.create_if_missing = True
    # more options...

    db = rocksdb.DB("example-db", options)

DBのインスタンスを作成する際に与えるパスは、RocksDBのファイルを作成するディレクトリになります。この中に、データを
保存するファイルが置かれていくのですが、チュートリアルを見ているとファイル名っぽく見えるように書かれている気がします…。

作成されるのは、実際はディレクトリです。

あとはほぼ見たままの操作ですが、DBとやり取りするデータはバイト表現であることに注意しましょう。

RocksDB stores all data as uninterpreted byte strings.
it is strongly recommended to use an explicit b prefix for all byte string literals in both python2 and python3 code.

About Bytes And Unicode

バッチ操作

更新系の操作は、バッチ処理でまとめることができます。

def test_batch_operation():
    db = rocksdb.DB("example-db", rocksdb.Options(create_if_missing=True))

    batch = rocksdb.WriteBatch()
    batch.put(b"batch-key1", b"batch-value1")
    batch.put(b"batch-key2", b"batch-value2")
    batch.put(b"batch-key3", b"batch-value3")

    db.write(batch)

    values = db.multi_get([b"batch-key1", b"batch-key2", b"batch-key3"])

    assert values[b"batch-key1"] == b"batch-value1"
    assert values[b"batch-key2"] == b"batch-value2"
    assert values[b"batch-key3"] == b"batch-value3"

    batch = rocksdb.WriteBatch()
    batch.delete(b"batch-key1" )
    batch.delete(b"batch-key2")
    batch.delete(b"batch-key3")

    db.write(batch)

    values = db.multi_get([b"batch-key1", b"batch-key2", b"batch-key3"])

    assert values[b"batch-key1"] is None
    assert values[b"batch-key2"] is None
    assert values[b"batch-key3"] is None

データベースの作成については、今回はOptionsを使わずに、キーワード引数で簡単に済ませました。こういった書き方も
可能です。

    db = rocksdb.DB("example-db", rocksdb.Options(create_if_missing=True))

Iterator

キーと値のペア、キー、値のそれぞれのパターンで、Iteratorを利用することができます。

def test_iteration():
    db = rocksdb.DB("example-db", rocksdb.Options(create_if_missing=True))

    db.put(b"key1", b"value1")
    db.put(b"key2", b"value2")
    db.put(b"key3", b"value3")

    iterator = db.iteritems()
    iterator.seek_to_first()

    assert list(iterator) == [(b"key1", b"value1"), (b"key2", b"value2"), (b"key3", b"value3")]

    iterator = db.iterkeys()
    iterator.seek_to_first()

    assert list(iterator) == [b"key1", b"key2", b"key3"]

    iterator = db.itervalues()
    iterator.seek_to_first()

    assert list(iterator) == [b"value1", b"value2", b"value3"]

    db.delete(b"key1")
    db.delete(b"key2")
    db.delete(b"key3")

DB#iteritemsの場合はキーと値のタプルのIteratorが、DB#iterkeysではキーのIterator、DB#itervaluesでは値のIteratorが
返ってきます。

Iteratorは、最初にseekする必要がありseek_to_firstで最初のエントリ、seek_to_lastで最後のエントリ、seek(key)で指定の
位置からイテレーションを行うことができます。

とりあえず、基本的な使い方はこんなところでしょうか。

他にもSnapshot、Merge、PrefixEtractorなどのトピックもあるようですが、そのあたりは機会があれば。

地味に、インストールが1番大変だったように思います…。

今回はIteratorをリストに変換してAssertionしていますが、