ElixirにはETSという便利なインメモリデータベースが備えついています。ETSのデータをバックアップする方法を調べたのでメモします。
ETSとDETS
- ETS(Erlang Term Storage)
- Erlang備え付けのメモリー内のデータ保管庫
- DETS(Disk-based ETS)
- Erlang備え付けのファイルに保存するデータ保管庫
依存関係
ETSもDETSもErlangの備え付けなので、Erlang/Elixir以外何もいりません。
テーブル名をきめる
インメモリーのETSとファイル保存のDETSの両方に対して同じ名前を使います。別々の名前にしても良いのですが、共通の名前にすることにより後にコードが簡素化できる利点があります。
table_name = :my_data
DETSファイル保存先をきめる
ドキュメントには書かれてませんが、相対パスじゃないとダメなようです。絶対パスだとDETSテーブル生成時に変なエラーがでました。あと、パスがディレクトリ配下の場合はディレクトリが存在していないとエラーになります。
dets_dir = "tmp"
File.mkdir(dets_dir)
dets_path = Path.join([dets_dir, "#{table_name}.dets"])
ETSテーブルをつくる
テーブル名と各種オプションを指定してETSテーブルを作ります。
- 名前をつけるか
- named_table
- データ構造
- set
- ordered_set
- bag
- duplicate_bag
- アクセス権限
- public
- protected
- private
- 並行性
- read_concurrency
- write_concurrency
named_table
オプションにより名前付きテーブルにするとテーブルへの参照が楽になります。本記事のコードは名前付きテーブルを前提としています。
詳しくは原典をご参照ください。
init_my_ets = fn table_name ->
:ets.new(table_name, [
:named_table,
:bag,
:public
])
end
init_my_ets.(table_name)
DETSテーブルをつくる
ファイルは相対パスで、ディレクトリを含むパスの場合はディレクトリが存在している必要があります。
同期させるためには、DETSテーブルのデータ構造(ここでは:bag
)をETSと合わせる必要があります。
初期設定では3分に一度ファイル保存されるようです。これは:auto_save
オプションにより変更可能です。
詳しくは原典をご参照ください。
:dets.open_file(table_name,
type: :bag,
file: dets_path |> String.to_charlist(),
)
DETSファイルに保存されたデータをETSに注入する
現時点ではDETSファイルは空だと思いますが、DETSにより保存されたデータが入っている場合は、:dets.to_ets/2を用いてETSに注入するすることができます。
ETSテーブルの中身は:ets.tab2list/1で確認できます。
:dets.to_ets(table_name, table_name)
:ets.tab2list(table_name)
ETSのデータを消してみる
後にデータを消したりしたくなると思います。ETSのデータを消す方法はいくつかあります。簡単な方法を挙げます。
キーで検索して消去
:ets.delete(table_name, :toukon)
:ets.tab2list(table_name)
全データ消去
:ets.delete_all_objects(table_name)
[] = :ets.tab2list(table_name)
ETSにデータを挿入してみる
余談ですが、今回はたまたまデータ構造を:bag
にしているのでキーの重複が認められます。キーを名前空間として利用できます。
entries = [
{:toukon, %{x: 1}},
{:toukon, %{x: 2}},
{:autorace, %{x: 3}}
]
for entry <- entries do
true = :ets.insert(table_name, entry)
end
:ets.tab2list(table_name)
ETSテーブルをDETSファイルに保存する
- :dets.from_ets/2でETSテーブルの内容をDETSテーブルに保存します
- :dets.sync/1でDETSテーブルに対して行われたすべての更新が確実にファイルに書き込まれるようにします
persist_data = fn table_name ->
with :ok <- :dets.from_ets(table_name, table_name),
:ok <- :dets.sync(table_name) do
:ok
else
{:error, reason} ->
Logger.error("Unable to sync DETS #{table_name}, #{inspect(reason)}")
end
end
persist_data.(table_name)
ETSテーブルを消去してみる
:ets.delete(table_name)
init_my_ets.(table_name)
[] = :ets.tab2list(table_name)
ETSを復元してみる
:dets.to_ets(table_name, table_name)
:ets.tab2list(table_name)
このように使い方を整理すれば、意外と簡単にできてしまいます。
ベテランの方々は簡素なパターンの場合には第三者パッケージに依存せずに自分で直にETSを使う人が多い印象をもっています。
参考文献
Elixirコミュニティ
本記事は以下のモクモク会での成果です。みなさんから刺激と元氣をいただき、ありがとうございました。
もしご興味のある方はお気軽にご参加ください。
生産者の皆様いつも美味しい食材をありがとうございます。おかげで健康に元氣にもくもく取り組むことができます。