Detsをイテレートする

Erlangにはdetsというモジュールがある。 ディスクを使うetsという位置付け。

単なる 埋め込みKVS だと思っていたけど :type オプションを使えば追記可能なリストになったりと便利だった。 利用の流れと Enum, Stream の練習としてテーブル全体のイテレーションを書いておく。

注意点

Detsの注意点を並べておく。ディスクを使うKVSだが特に因果順序が保たれたりしない。

  • 同一プロセス内で共有される
  • 同一パスに対するテーブルは並行制御されない
  • syncするまでファイルに書き込まれているとは限らない
  • openの仕方によってリファレンスかパスか異なる
  • select,matchが線形探索
  • テーブル全体のサイズが2GBまで
  • erlangの文字列なのでElixirとしてはcharlistを使う

基本的な利用の流れ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
:dets.open_file('year:2020', type: :bag, file: 'tmp/smaple.2020.dets')
:dets.all() |> Enum.map(& :dets.info(&1))
for num <- 0..5 do
    :dets.insert('year:2020', {num, "value #{num}"})
    :dets.insert('year:2020', {num, "value #{num + Enum.random(1..20)}"})
end
0..5 |> Enum.map(& :dets.lookup('year:2020', &1))
:dets.sync('year:2020')
:dets.stop()
:dets.all()

:dets.open_file('year:2020', type: :bag, file: 'tmp/smaple.2020.dets')

0..5 |> Enum.map(& :dets.lookup('year:2020', &1))

:dets.close('year:2020')
:dets.all()

:dets.open_file/n がDetsファイルを作成する。この際に file: キーワードでパスを指定しない場合、最初のデータベース名がファイルパスになる。 ファイルパスにファイルが存在しなければ自動的に作成されるが、すでに存在する場合にフォーマットが違うとエラーとなる。

全てのidを返す Streamの実装

こんな感じで書く。

1
2
3
4
5
6
defmodule Example do
  def id_stream(name) do
    Stream.iterate(:dets.first(name), & :dets.next(name, &1))
    |> Stream.take_while(& &1 != :"$end_of_table")
  end
end

使い方。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
:dets.insert(:foo, {"hoge", "apple"})
:ok
:dets.insert(:foo, {"hoge", "orange"})
:ok
:dets.insert(:foo, {"foo", 100})
:ok

Example.id_stream(:foo) |> Enum.map(& &1)
["hoge", "foo"]
Example.id_stream(:foo) |> Enum.map(& :dets.lookup(:foo, &1))
[[{"hoge", "apple"}, {"hoge", "orange"}], [{"foo", 100}]]
comments powered by Disqus