golangでLevelDBを使用する

まえがき

DBの作成からデータの登録、取得、範囲取得、削除など基本的な使い方を紹介します。

プログラム全体は最後に載せてあります。
コピーしてGo Playground に貼り付ければ実行できますので、実際の動作を確認してみてください。

解説

LevelDBを作成

    //DBを開く(なければ作成される)
    db, err := leveldb.OpenFile(dbName, nil)
    if err != nil {
        return err
    }
    defer db.Close()

DBの作成はOpenFile関数を使用します。

コメントに記載のある通り、ディレクトリがなければ新しく作成してくれます。
今回の場合はtestdbというディレクトリが作成されます。

defer db.Close()のdefer関数を抜ける前にコードを実行してくれるステートメントです。
この例ではleveldbTest関数を抜ける前にdb.Close()が実行されることになります。
※このコードはleveldbTest関数から呼び出されています

毎回return文の前に記述しなくてよくなるのでとても便利ですね。

第1引数はDB名を指定します。
第2引数はオプションを指定します。
今回は指定しないのでnilを渡しています。

Close関数は、データのファイルへの保存やメモリの解放など重要な後始末を実行していますので、忘れずに呼び出すようにしましょう。

キーの登録

    //key1~key5を登録
    dataNum := 5
    for i := 1; i <= dataNum; i++ {
        key := fmt.Sprintf("key%d", i)
        value := fmt.Sprintf("value%d", i)
        err = db.Put([]byte(key), []byte(value), nil)
        if err != nil {
            return err
        }
    }

    //mofumofu1~mofumofu5を登録
    dataNum = 5
    for i := 1; i <= dataNum; i++ {
        key := fmt.Sprintf("mofumofu%d", i)
        value := fmt.Sprintf("mofumofu-value%d", i)
        err = db.Put([]byte(key), []byte(value), nil)
        if err != nil {
            return err
        }
    }

キーの登録はPut関数を使用します。

第1引数に設定したいキーをbyte配列で指定します。
第2引数にキーの値をbyte配列で指定します。
keyとvalue共にbyte配列で渡す仕様になっています。
渡す前のデータ変換がめんどくさいですが、その分内部はシンプルな作りになっています。

第3引数はオプションを指定します。
今回は指定しないのでnilを渡しています。

指定キーのデータを取得

    //指定したキー(mofumofu2)を取得
    fmt.Println("指定したキー(mofumofu2)を取得")
    valueBytes, err := db.Get([]byte("mofumofu2"), nil)
    if err != nil {
        return err
    }
    fmt.Println(string(valueBytes))

    //指定したキー(hogehoge2)を取得
    fmt.Println("指定したキー(hogehoge2)を取得")
    valueBytes, err := db.Get([]byte("hogehoge2"), nil)
    if err == leveldb.ErrNotFound {
        fmt.Println("hogehoge2は見つかりません")
    } else if err != nil {
        return err
    }

指定キーのデータの取得はGet関数を使用します。

第1引数に取得したいデータのキーをbyte配列で渡します。

第2引数はオプションを指定します。
今回は指定しないのでnilを渡しています。

もし指定したキーが見つからなかった場合は、leveldb.ErrNotFoundが返却されます。

キーを全て取得

    //全て取得
    fmt.Println("\n全て取得")
    iter := db.NewIterator(nil, nil)
    for iter.Next() {
        key := iter.Key()
        value := iter.Value()
        fmt.Println(string(key), string(value))
    }
    iter.Release()
    err = iter.Error()
    if err != nil {
        return err
    }

全てのキーを一つずつ取得したい場合はNewIterator関数を使用します。

1番目の引数はキーを取得する範囲を指定します。
今回は全体を取得するのでnilを渡しています。

2番目の引数はオプションを指定します。
こちらはGet関数で指定するオプションと同じものです。
今回は指定しないのでnilを渡しています。

イテレーターのNext関数は呼び出すたびに値がセットされ、keyとvalueが取得できるようになります。
Next関数は最終キーの位置で使用するとfalseを返却します。

使い終わったらRelease関数を使用して後始末を行います。
Error関数も使用して問題が発生していないかも確認しておきましょう。

キーを一部取得

最初から指定キーまで

    //最初~mofumofu2までを取得
    fmt.Println("\n最初~mofumofu2までを取得")
    iter = db.NewIterator(&util.Range{
        Start: nil, //[]byte{}でも処理されるが、未指定はnil想定なので[]byte{}は指定しない方がいい
        Limit: []byte("mofumofu3"),
    }, nil)
    for iter.Next() {
        key := iter.Key()
        value := iter.Value()
        fmt.Println(string(key), string(value))
    }
    iter.Release()
    err = iter.Error()
    if err != nil {
        return err
    }

ある範囲のキーを取得する場合もNewIterator関数を使用します。

第1引数にutil.Rangeを指定して範囲を設定します。
util.RangeStartLimitのメンバ変数があります。
最初のデータから取得したい場合はStartnilを設定します。
Limitmofumofu3を指定して終端を設定します。

この場合は最初からmofumofu2までのキーを取得できます。

コメントに書いてますがStartの未指定はnilです。
[]byte{}でも動きますが指定しない方がいいです。

指定キーから最後のキーまで

    //key3以降を取得
    fmt.Println("\nkey3以降を取得")
    iter = db.NewIterator(&util.Range{
        Start: []byte("key3"),
        Limit: nil, //[]byte{}を指定するとパニックになるので注意、未指定はnil
    }, nil)
    for iter.Next() {
        key := iter.Key()
        value := iter.Value()
        fmt.Println(string(key), string(value))
    }
    iter.Release()
    err = iter.Error()
    if err != nil {
        return err
    }

指定キーから最後のキーまで取得する例です。
Startkey3を設定して、Limitnilを設定します。

この場合はkey3から最後のキーまで取得できます。

こちらもコメントに書いてますが、Limitの未指定はnilです。
Startと違ってLimit[]byte{}を指定するとパニックになりますのでご注意ください。

指定キーから指定キーまで

    //key2~key3を取得
    fmt.Println("\nkey2~key3を取得")
    iter = db.NewIterator(&util.Range{
        Start: []byte("key2"),
        Limit: []byte("key4"),
    }, nil)
    for iter.Next() {
        key := iter.Key()
        value := iter.Value()
        fmt.Println(string(key), string(value))
    }
    iter.Release()
    err = iter.Error()
    if err != nil {
        return err
    }

指定キーから指定キーまで取得する例です。
Startkey2を設定して、LimitKey4を設定します。

この場合はkey2からKey3まで取得できます。

前方一致のキー

    //mofumofu*を取得
    fmt.Println("\nmofumofu*を取得")
    iter = db.NewIterator(util.BytesPrefix([]byte("mofumofu")), nil)
    for iter.Next() {
        key := iter.Key()
        value := iter.Value()
        fmt.Println(string(key), string(value))
    }
    iter.Release()
    err = iter.Error()
    if err != nil {
        return err
    }

前方一致のキーを取得する例です。

前方一致はutil.BytesPrefix関数を使用してutil.Rangeを作成してもらいます。
第1引数にmofumofuを設定します。

この場合はmofumofu1~mofumofu5のキーが取得できます。

キーの削除

    //指定したキー(key2)を削除
    fmt.Println("\n指定したキー(key2)を削除")
    err = db.Delete([]byte("key2"), nil)
    if err != nil {
        return err
    }
    fmt.Println("\n存在しないキー(hogehoge2)を削除")
    err = db.Delete([]byte("hogehoge2"), nil)
    if err != nil {
        //ここにはこない(エラーにならない)
        return err
    }

キーの削除はDelete関数を使用します。

第1引数に削除したいキーを設定します。
第2引数はオプションを指定します。
今回は指定しないのでnilを渡しています。

存在しないキーを削除してもエラーにはなりません

まとめ

leveldbは他のgolang製のkvsに比べて高速でメモリ使用量も少なくとても使いやすいです。
MySQLのように複数のプロセスからはアクセスできないですが、バッチ処理で作成したデータの保存先として使用するなど特定の用途で効果を発揮します。
ぜひ一度使ってみて下さい。

今回オプションは全てnil設定でしたが、オプションをうまく使うことでメモリ消費を抑えられるメリットがありますので、別記事でまた書きたいと思います。

プログラム全体

package main

import (
    "fmt"
    "runtime"

    "github.com/syndtr/goleveldb/leveldb"
    "github.com/syndtr/goleveldb/leveldb/util"
)

func main() {
    err := leveldbTest("testdb")
    if err != nil {
        panic(err)
    }
    fmt.Printf("\nGo version: %s\n", runtime.Version())
}

func leveldbTest(dbName string) error {
    //DBを開く(なければ作成される)
    db, err := leveldb.OpenFile(dbName, nil)
    if err != nil {
        return err
    }
    defer db.Close()

    //key1~key5を登録
    dataNum := 5
    for i := 1; i <= dataNum; i++ {
        key := fmt.Sprintf("key%d", i)
        value := fmt.Sprintf("value%d", i)
        err = db.Put([]byte(key), []byte(value), nil)
        if err != nil {
            return err
        }
    }

    //mofumofu1~mofumofu5を登録
    dataNum = 5
    for i := 1; i <= dataNum; i++ {
        key := fmt.Sprintf("mofumofu%d", i)
        value := fmt.Sprintf("mofumofu-value%d", i)
        err = db.Put([]byte(key), []byte(value), nil)
        if err != nil {
            return err
        }
    }

    //指定したキー(mofumofu2)を取得
    fmt.Println("指定したキー(mofumofu2)を取得")
    valueBytes, err := db.Get([]byte("mofumofu2"), nil)
    if err != nil {
        return err
    }
    fmt.Println(string(valueBytes))

    //指定したキー(hogehoge2)を取得
    fmt.Println("指定したキー(hogehoge2)を取得")
    valueBytes, err = db.Get([]byte("hogehoge2"), nil)
    if err == leveldb.ErrNotFound {
        fmt.Println("hogehoge2は見つかりません")
    } else if err != nil {
        return err
    }

    //全て取得
    fmt.Println("\n全て取得")
    iter := db.NewIterator(nil, nil)
    for iter.Next() {
        key := iter.Key()
        value := iter.Value()
        fmt.Println(string(key), string(value))
    }
    iter.Release()
    err = iter.Error()
    if err != nil {
        return err
    }

    //最初~mofumofu2までを取得
    fmt.Println("\n最初~mofumofu2までを取得")
    iter = db.NewIterator(&util.Range{
        Start: nil, //[]byte{}でも処理されるが、未指定はnil想定なので[]byte{}は指定しない方がいい
        Limit: []byte("mofumofu3"),
    }, nil)
    for iter.Next() {
        key := iter.Key()
        value := iter.Value()
        fmt.Println(string(key), string(value))
    }
    iter.Release()
    err = iter.Error()
    if err != nil {
        return err
    }

    //key3以降を取得
    fmt.Println("\nkey3以降を取得")
    iter = db.NewIterator(&util.Range{
        Start: []byte("key3"),
        Limit: nil, //[]byte{}を指定するとパニックになるので注意、未指定はnil
    }, nil)
    for iter.Next() {
        key := iter.Key()
        value := iter.Value()
        fmt.Println(string(key), string(value))
    }
    iter.Release()
    err = iter.Error()
    if err != nil {
        return err
    }

    //key2~key3を取得
    fmt.Println("\nkey2~key3を取得")
    iter = db.NewIterator(&util.Range{
        Start: []byte("key2"),
        Limit: []byte("key4"),
    }, nil)
    for iter.Next() {
        key := iter.Key()
        value := iter.Value()
        fmt.Println(string(key), string(value))
    }
    iter.Release()
    err = iter.Error()
    if err != nil {
        return err
    }

    //mofumofu*を取得
    fmt.Println("\nmofumofu*を取得")
    iter = db.NewIterator(util.BytesPrefix([]byte("mofumofu")), nil)
    for iter.Next() {
        key := iter.Key()
        value := iter.Value()
        fmt.Println(string(key), string(value))
    }
    iter.Release()
    err = iter.Error()
    if err != nil {
        return err
    }

    //指定したキー(key2)を削除
    fmt.Println("\n指定したキー(key2)を削除")
    err = db.Delete([]byte("key2"), nil)
    if err != nil {
        return err
    }
    fmt.Println("\n存在しないキー(hogehoge2)を削除")
    err = db.Delete([]byte("hogehoge2"), nil)
    if err != nil {
        //ここにはこない(エラーにならない)
        return err
    }
    //全て取得
    fmt.Println("\n全て取得")
    iter = db.NewIterator(nil, nil)
    for iter.Next() {
        key := iter.Key()
        value := iter.Value()
        fmt.Println(string(key), string(value))
    }
    iter.Release()
    err = iter.Error()
    if err != nil {
        return err
    }
    return nil
}
指定したキー(mofumofu2)を取得
mofumofu-value2
指定したキー(hogehoge2)を取得
hogehoge2は見つかりません

全て取得
key1 value1
key2 value2
key3 value3
key4 value4
key5 value5
mofumofu1 mofumofu-value1
mofumofu2 mofumofu-value2
mofumofu3 mofumofu-value3
mofumofu4 mofumofu-value4
mofumofu5 mofumofu-value5

最初~mofumofu2までを取得
key1 value1
key2 value2
key3 value3
key4 value4
key5 value5
mofumofu1 mofumofu-value1
mofumofu2 mofumofu-value2

key3以降を取得
key3 value3
key4 value4
key5 value5
mofumofu1 mofumofu-value1
mofumofu2 mofumofu-value2
mofumofu3 mofumofu-value3
mofumofu4 mofumofu-value4
mofumofu5 mofumofu-value5

key2~key3を取得
key2 value2
key3 value3

mofumofu*を取得
mofumofu1 mofumofu-value1
mofumofu2 mofumofu-value2
mofumofu3 mofumofu-value3
mofumofu4 mofumofu-value4
mofumofu5 mofumofu-value5

指定したキー(key2)を削除

存在しないキー(hogehoge2)を削除

全て取得
key1 value1
key3 value3
key4 value4
key5 value5
mofumofu1 mofumofu-value1
mofumofu2 mofumofu-value2
mofumofu3 mofumofu-value3
mofumofu4 mofumofu-value4
mofumofu5 mofumofu-value5

Go version: go1.17.7

Program exited.

実行環境

Go Playgroundで実行
Go version go1.17.7

スポンサーリンク

もふもふ

プロフィール

著者
もふもふ
プログラマ。汎用系→ゲームエンジニア→Webエンジニア→QAエンジニア。開発からテストまで一通り経験し、実際に詰まった点や検証結果を技術ブログとしてまとめています。