2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Elixir標準インメモリストレージ(Elixir 1.18 / OTP 28)

Last updated at Posted at 2025-08-13
Page 1 of 18

自己紹介

こんにちは、@mnishiguchi です。
仕事や趣味でWeb アプリケーションや組み込みシステムの開発に取り組んでいます。
まだまだ知らないことばかりで、日々學びの連続です。

Qiita では、Elixir や Nerves を中心に、ちょっとした氣づきや學びを記録しています。


はじめに

Elixir でプログラミングをしていると、異なるプロセス間や、同一プロセス内でも異なるタイミングで取得する必要がある状態を共有・保存したい場面があります。

そのような場合、Elixir には外部サービスや追加ライブラリを使わずに利用できるインメモリストレージが標準で用意されています。


ここでは代表的なものをご紹介します。

モジュール 主な用途
Agent 単純な共有状態やキャッシュの保持
GenServer 状態管理とカスタムロジックの実装
:ets 大規模データや共有テーブルの高速アクセス
:persistent_term 読み取り中心でほぼ固定のグローバル設定値
:counters 高頻度で更新する数値カウンター
Process プロセス内だけで参照できるメタ情報

今回使用した Erlang/Elixir のバージョンは以下のとおりです。

Erlang/OTP 28
Elixir 1.18.4

Agent

Agent は 単純な状態の取得・更新に特化したプロセスです。


Map のように値の追加・更新・削除が行える最小限のモジュールを用意します。

defmodule MyKvAgent do
  use Agent
  @name __MODULE__

  def start_link(_opts \\ []) do
    initial_state = %{}
    Agent.start_link(fn -> initial_state end, name: @name)
  end

  def get(key, default \\ nil) do
    Agent.get(@name, &Map.get(&1, key, default))
  end

  def put(key, value) do
    Agent.update(@name, &Map.put(&1, key, value))
  end

  def delete(key) do
    Agent.update(@name, &Map.delete(&1, key))
  end
end
# プロセスを開始
MyKvAgent.start_link()

# 値を追加
MyKvAgent.put(:hogehoge, "元氣")

# 値を取得
MyKvAgent.get(:hogehoge)

:sys.get_state/1で現在の内容を確認することができますが、これはデバッグ専用です。

:sys.get_state(MyKvAgent)

GenServer

GenServer は、状態と処理ロジックを一つのプロセスで扱える OTP の基本機能です。状態の読み書きだけでなく、任意のメッセージ処理や非同期処理にも対応できます。


Map のように値の追加・更新・削除が行える最小限のモジュールを用意します。

defmodule MyKvServer do
  use GenServer
  @name __MODULE__

  ## クライアントAPI

  def start_link(_opts \\ []) do
    init_args = nil
    GenServer.start_link(__MODULE__, init_args, name: @name)
  end

  def get(key, default \\ nil) do
    GenServer.call(@name, {:get, key, default})
  end

  def put(key, value) do
    GenServer.call(@name, {:put, key, value})
  end

  def delete(key) do
    GenServer.call(@name, {:delete, key})
  end

  ## サーバコールバック

  @impl true
  def init(_args) do 
    initial_state = %{}
    {:ok, initial_state}
  end

  @impl true
  def handle_call({:get, key, default}, _from, state) do
    {:reply, Map.get(state, key, default), state}
  end

  @impl true
  def handle_call({:put, key, value}, _from, state) do
    {:reply, :ok, Map.put(state, key, value)}
  end

  @impl true
  def handle_call({:delete, key}, _from, state) do
    {:reply, :ok, Map.delete(state, key)}
  end
end
# プロセスを起動
MyKvServer.start_link()

# 値を追加
MyKvServer.put(:hogehoge, "元氣")

# 値を取得
MyKvServer.get(:hogehoge)

:sys.get_state/1で現在の内容を確認することができますが、これはデバッグ専用です。

:sys.get_state(MyKvServer)

:ets (Erlang Term Storage)

:ets は 高速で並列アクセス可能なインメモリテーブルです。大きなデータセットの共有や頻繁な読み書きに適しています。


Map のように値の追加・更新・削除が行える最小限のモジュールを用意します。

defmodule MyKvEts do
  @table __MODULE__

  def init do
    case :ets.whereis(@table) do
      :undefined ->
        ref = :ets.new(@table, [:set, :named_table, :public])
        {:ok, ref}
      ref when is_reference(ref) ->
        {:ok, ref}
    end
  end

  def get(key, default \\ nil) do
    case :ets.lookup(@table, key) do
      [] -> default
      [{^key, value}] -> value
    end
  end

  def put(key, value) do
    :ets.insert(@table, {key, value})
    :ok
  end

  def delete(key) do
    :ets.delete(@table, key)
    :ok
  end
end
# テーブルを初期化
MyKvEts.init()

# 値を追加
MyKvEts.put(:hogehoge, "元氣")

# 値を取得
MyKvEts.get(:hogehoge)

:ets.tab2list/1でetsテーブルの中身を見ることができます。

:ets.tab2list(MyKvEts)

:ets.info/1でetsテーブルの設定や状態を確認できます。

:ets.info(MyKvEts)

:ets.all/0で現在のノードのすべてのテーブルを列挙できます。色んな所でetsが利用されているのが見えてたいへん興味深いです。

:ets.all()

:persistent_term

:persistent_term は、アプリケーション全体から参照できる読み取り特化型のストレージです。
読み込みは非常に高速ですが、更新や削除を行うと全プロセスでGC(ガーベジコレクション)が発生するため高コストです。
そのため、ほぼ固定の設定値やルックアップテーブルなど、頻繁に参照するが、ほとんど更新しないデータの保存に適しています。


:persistent_term は、特に初期化は不要です。

# 値を設定
:persistent_term.put(:hogehoge, "元氣")

# 値を取得
:persistent_term.get(:hogehoge)

:counters

:counters は、複数プロセスから効率的に同時更新できる数値カウンターです。
加算・減算・取得が高速で、整数インデックス(1始まり)で管理します。
高頻度更新が必要なメトリクスやレート制限、集計処理に適しています。


初期化の際にカウンターの数を指定します。
インデックス番号で複数のカウンターを管理します。

# 初期化
counters_ref = :counters.new(_how_many = 2, _options = [])

# カウンター1に1を足す
index = 1
:counters.add(counters_ref, index, 1)
:counters.get(counters_ref, index) 

# カウンター2から5引く
index = 2
:counters.sub(counters_ref, index, 5)
:counters.get(counters_ref, index)

プロセス辞書

実はプロセス自体にもプロセス辞書(Process Dictionary) という内部データを保持する仕組みがあります。
プロセス内でProcess.put/2Process.get/1 で操作ができますが、他のプロセスからは見えません。プロセスが終了するとデータも消えます。


# 値を保存
Process.put(:hogehoge, "元氣")

# 値を取得
Process.get(:hogehoge)

Process.info/1で現在の内容を確認することができますが、これはデバッグ専用です。

for {k, v} <- Process.info(self())[:dictionary], do: {k, inspect(v)}

おわりに

Elixir の標準機能だけで、これだけ多彩なインメモリストレージが揃っています。これらを適材適所で、どんどん活用していきましょう。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?