View Source ActiveMemory (ActiveMemory v0.8.1)
The typed, attribute-queryable in memory store for ETS and Mnesia.
In most applications, a huge share of database load is reads of data that barely changes: reference data, configuration, authorization, catalog data. ActiveMemory exists to take that load off the database — load those tables into memory once at boot, serve every read for those tables from RAM, and write back only when something actually changes. A cache with no cache to manage: no keys to design, no cold misses, no invalidation dance. The in-memory copy serves the reads, while the database remains the durable source of truth.
Records are found by their attributes, in any combination, with no ETS match specifications to hand write:
AdminStore.one(%{email: email, active?: true})
ProductStore.select(%{category: "electronics", in_stock?: true})
TokenStore.withdraw(%{value: submitted_token})The same interface runs on :ets or :mnesia, with boot-time seeding, record
expiry, crash resilience and atomic take-once reads built in.
the-pieces
The pieces
ActiveMemory.Table— define a table's fields and its ETS/Mnesia options. A table is either anattributesblock or an Ectoembedded_schema.ActiveMemory.Store— a supervised process owning one table, and the API you read and write through.ActiveMemory.ActiveRepo— the same API over several tables from one process.ActiveMemory.Query— thematch/1macro, for queries that need more than equality.
getting-started
Getting started
Define a table, define a store, add the store to your supervision tree.
defmodule MyApp.People.Person do
use ActiveMemory.Table, options: [index: [:last]]
attributes auto_generate_uuid: true do
field(:email, :string)
field(:first, :string)
field(:last, :string)
field(:age, :integer)
field(:admin?, :boolean)
end
end
defmodule MyApp.People.Store do
use ActiveMemory.Store, table: MyApp.People.Person
end# in MyApp.Application
children = [MyApp.People.Store]That is the whole setup. The table is created when the store starts, so there are no migrations to run.
{:ok, person} = MyApp.People.Store.write(%MyApp.People.Person{email: "kara@bsg.com"})
{:ok, person} = MyApp.People.Store.get(person.uuid)
people = MyApp.People.Store.all(order_by: :last, limit: 20)Tables default to :mnesia; pass type: :ets for an ETS table. See
ActiveMemory.Store for the full read and write API.
working-with-ecto
Working with Ecto
A table takes Ecto types, and can be an Ecto schema outright, so Ecto.Changeset
works on it and write/1 accepts a changeset the way Ecto.Repo.insert/2 does:
%MyApp.People.Person{}
|> MyApp.People.Person.changeset(params)
|> MyApp.People.Store.write()The Coming from Ecto guide covers what carries over, what is named differently, and where the two genuinely differ.
what-else-is-built-in
What else is built in
- Record expiry. A
ttlon a table gives every record a lifetime; reads never return an expired record and the owning process sweeps them to reclaim memory. SeeActiveMemory.Table. - Crash resilience.
ActiveMemory.TableHeirholds ETS tables when a store crashes, so the data survives the restart. No configuration needed. - Atomic take-once reads.
withdraw/1finds a record and removes it in one atomic operation, so exactly one concurrent caller wins — what you want for one time tokens and 2FA codes.
testing
Testing
A table's module name is its ETS/Mnesia table name, so isolation comes down to
whether tests share a table. A test module that defines its own table and store
runs async: true alongside every other test module with no configuration. Test
modules that share your application's store need async: false, since they write
to the same global table. See Testing for both patterns.
when-to-reach-for-it
When to reach for it
ActiveMemory suits a small-to-medium dataset that is read constantly but changes
rarely — reference data, configuration, admin users and permissions, products and
plans — plus short-lived records that benefit from ttl and withdraw/1, like
one time tokens and 2FA codes.
It is not a system of record. ETS lives and dies with the node, and Mnesia
persists only with disc_copies; keep durable data in a database. For caching
computed values by key with eviction policies, a cache such as
Cachex or
Nebulex is the better fit.
Running across several nodes means a replicated Mnesia table, which makes network
partitions a concern — see the majority option in ActiveMemory.Table.