ElixirでACID対応の軽量ファイルベースデータベース: CubDB v2.0

## はじめに

[Nerves]でファームウェアを構築していると、時々「データを保存しておきたい」と思う場面が出てきます。そんなときに役立つのが、ファイルベースのシンプルなデータベース **[CubDB v2.0](https://hexdocs.pm/cubdb/CubDB.html)** です。

## CubDB v2.0 の特徴

- **簡単で柔軟**: ファイルベースのデータベースで、初心者でも理解しやすい API を提供。
- **ACID トランザクション対応**: データの整合性を保証し、安全な操作が可能。
- **MVCC (マルチバージョン同時実行制御)**: 複数のプロセスが同時にデータを操作しても、衝突なく処理可能。
- **新しい機能**:
  - [トランザクション](https://hexdocs.pm/cubdb/CubDB.html#transaction/2): データ操作をまとめて行うことで、操作の整合性を高める。
  - [スナップショット](https://hexdocs.pm/cubdb/CubDB.Snapshot.html): 読み取り専用のデータビューを作成可能。
  - [`put_multi`](https://hexdocs.pm/cubdb/CubDB.html#put_multi/2): 一度に複数のデータを操作可能。
- **堅牢性**: 不意のクラッシュや障害時でもデータを保護。
- **軽量設計**: 外部のデータベースサーバーが不要で、小規模プロジェクトや[Nerves](https://www.nerves-project.org/)デバイスにも最適。

## CubDB v2.0 と DETS の比較

以下の表では、CubDB v2.0 と Erlang 標準の DETS の違いを比較しています。それぞれの特徴を知ることで、どちらが特定の用途に適しているか判断しやすくなります。CubDB は Elixir で書かれたファイルベースのデータベースで、ACID トランザクションやスナップショット機能がサポートされています。一方、DETS は Erlang 標準のデータベースで、CubDB に比べるとシンプルですが制限も多いです。

| 特徴             | CubDB v2.0                       | DETS                 |
| ---------------- | -------------------------------- | -------------------- |
| 言語             | Elixir で実装                    | Erlang で実装        |
| ACID 対応        | されている                       | されていない         |
| データ要害防止   | 中断の地点でも安全               | 安全性に制限あり     |
| スナップショット | 便利な API を提供                | サポートされていない |
| メンテナンス性   | 活発なメンテナンスとコミュニティ | メンテナンス減少中   |

公式 FAQ では [CubDB と他のデータベースとの比較](https://hexdocs.pm/cubdb/2.0.2/faq.html#how-does-it-compare-with-x) も紹介されています。

## IEx で CubDB を使ってみる

#### インストールして開始

```elixir
# cubdbをインストール
Mix.install([:cubdb])

# データの保存先とプロセス名を指定してcubdbサーバーを起動
data_dir = Path.join(System.tmp_dir!(), "database")
{:ok, db} = CubDB.start_link(data_dir: data_dir)
```

#### データの操作

```elixir
CubDB.put(db, :feeling, "awesome")
#=> :ok

CubDB.get(db, :feeling)
#=> "awesome"

CubDB.delete(db, :feeling)
#=> :ok

CubDB.get(db, :feeling)
#=> nil
```

#### トランザクション

```elixir
CubDB.put(db, :number, 123)
#=> :ok

CubDB.put(db, :word, "元氣")
#=> :ok

CubDB.transaction(db, fn tx ->
  number = CubDB.Tx.get(tx, :number)
  word = CubDB.Tx.get(tx, :word)

  tx = CubDB.Tx.put(tx, :number, word)
  tx = CubDB.Tx.put(tx, :word, number)

  {:commit, tx}
end)
#=> :ok

CubDB.get(db, :number)
#=> "元氣"

CubDB.get(db, :word)
#=> 123
```

## Nerves ファームウエアに搭載

`cubdb`を依存関係リストに追加して、`mix deps.get`コマンドでインストール。

```elixir:mix.exs
  defp deps do
    [
      {:cubdb, "~> 2.0"},
    ]
  end
```

アプリ起動時に[cubdb]サーバーを起動する。

```elixir:lib/hello_nerves/application.ex
defmodule HelloNerves.Application do
  @moduledoc false

  use Application

  def start(_type, _args) do
    opts = [strategy: :one_for_one, name: HelloNerves.Supervisor]

    children = [
      {CubDB, [
        # Nervesで書き込める場所`/data`配下の任意のディレクトリーを指定
        data_dir: "/data/database",
        # CubDBプロセスに名前をつける
        name: HelloNerves.Database]}
    ]

    Supervisor.start_link(children, opts)
  end
end
```

https://qiita.com/torifukukaiou/items/9dd5cfa81109a2e0a5eb

:::note
同じデータディレクトリで複数の CubDB プロセスを起動しないようにしてください。1 つのデータディレクトリには、必ず 1 つの CubDB プロセスだけを使用してください。詳細については、[公式ドキュメント](https://hexdocs.pm/cubdb/2.0.2/readme.html#usage)をご覧ください。
:::

後は IEx での試運転同様に Nerves ファームウエアで自由に読み書きできます。

:tada::tada::tada:

## おわりに

CubDB v2.0 は、Elixir 開発者にとって便利な選択肢だと思います。
まだ試していない機能も多いので、ぜひ皆さんにも試していただき、フィードバックをいただければと思います。

![toukon-qiita-macbook_20230912_091808.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82804/fd5c55ec-4fe0-8af6-59bc-bab1ef3d182b.jpeg)

<!-- Links -->

[Elixir]: https://elixir-lang.org/
[Nerves]: https://www.nerves-project.org/
[ecto_sqlite3]: https://hex.pm/packages/ecto_sqlite3
[dets]: https://www.erlang.org/doc/man/dets.html
[cubdb]: https://hex.pm/packages/cubdb
[IEx]: https://hexdocs.pm/iex/IEx.html