にむかひて

ETSかAgent(サーバ)か

2022年6月 トップ > ひとこと > 調査したことの記録
#Elixir #Phoenix #ETS #Agent

  • 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)

サイト内検索