- ETSにデータを入れていたけど、全部Agent(GenServer)にして保持するとどの程度コストがかかるか気になった
結論
- データ投入では約10倍
- とはいえ、許容できる時間と思われる
- データ参照では約6倍程度
- こちらはデータ数によりそう
- といってもほとんど許容できる時間の様子
- メモリ使用量が約10倍
- これがネックになりそう
- (その後)DETSも試したところ、ETSに対し、投入で100倍、参照で10倍くらいの様子
実験で使った処理
- 構造体をKey-Valueで参照するケースで考える
- 簡単のため並列化はせずに行う
# 対象構造体
defmodule User do
defstruct [:uuid, :code1, :code2, :code3]
def sample do
%User{
uuid: Ecto.UUID.generate(),
code1: random_str(10),
code2: random_str(10),
code3: random_str(10),
}
end
defp random_str(num) do
:crypto.strong_rand_bytes(num)
|> Base.encode64()
|> binary_part(0, num)
end
end
# 構造体データ作成
# - 件数が多い場合には `export ELIXIR_ERL_OPTIONS="+P 1000000"` が必要
size = 500_000
users = (1..size) |> Enum.map(fn _ -> User.sample() end)
# ETS テーブル
table = :ets.new(:lookup_users, [:set, :protected])
# エージェント(サーバ)
defmodule MyAgent do
use Agent
def start_link(user) do
Agent.start_link(fn -> user end, name: :"#{user.uuid}")
end
def get(uuid) do
Agent.get(:"#{uuid}", & &1)
end
end
# 計測処理
measure = (fn func ->
total_before = :erlang.memory(:total)
{t, :done} = :timer.tc(fn ->
func.()
:done
end)
total_after = :erlang.memory(:total)
sec = t / 1000000
IO.inspect({sec, total_after - total_before})
end)
# (1-1) ETS 構築
measure.(fn ->
users
|> Enum.each(& :ets.insert(table, {&1.uuid, &1}))
end)
# (1-2) エージェント 構築
measure.(fn ->
users
|> Enum.each(& MyAgent.start_link/1)
end)
# (2-1) ETS 参照
measure.(fn ->
users
|> Enum.each(& :ets.lookup(table, &1.uuid))
end)
# (2-2) エージェント 参照
measure.(fn ->
users
|> Enum.each(& MyAgent.get(&1.uuid))
end)