XUtils

nebulex

A fast, flexible and extensible distributed and local caching library for Elixir.


Quickstart example

Assuming you are using Ecto and you want to use declarative caching:

# In the config/config.exs file
config :my_app, MyApp.PartitionedCache,
  primary: [
    gc_interval: :timer.hours(12),
    backend: :shards,
    partitions: 2
  ]

# Defining a Cache with a partitioned topology
defmodule MyApp.PartitionedCache do
  use Nebulex.Cache,
    otp_app: :my_app,
    adapter: Nebulex.Adapters.Partitioned,
    primary_storage_adapter: Nebulex.Adapters.Local
end

# Some Ecto schema
defmodule MyApp.Accounts.User do
  use Ecto.Schema

  schema "users" do
    field(:username, :string)
    field(:password, :string)
    field(:role, :string)
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:username, :password, :role])
    |> validate_required([:username, :password, :role])
  end
end

# The Accounts context
defmodule MyApp.Accounts do
  use Nebulex.Caching

  alias MyApp.Accounts.User
  alias MyApp.PartitionedCache, as: Cache
  alias MyApp.Repo

  @ttl :timer.hours(1)

  @decorate cacheable(cache: Cache, key: {User, id}, opts: [ttl: @ttl])
  def get_user!(id) do
    Repo.get!(User, id)
  end

  @decorate cacheable(cache: Cache, key: {User, username}, opts: [ttl: @ttl])
  def get_user_by_username(username) do
    Repo.get_by(User, [username: username])
  end

  @decorate cache_put(
              cache: Cache,
              keys: [{User, user.id}, {User, user.username}],
              match: &match_update/1,
              opts: [ttl: @ttl]
            )
  def update_user(%User{} = user, attrs) do
    user
    |> User.changeset(attrs)
    |> Repo.update()
  end

  @decorate cache_evict(
              cache: Cache,
              keys: [{User, user.id}, {User, user.username}]
            )
  def delete_user(%User{} = user) do
    Repo.delete(user)
  end

  def create_user(attrs \\ %{}) do
    %User{}
    |> User.changeset(attrs)
    |> Repo.insert()
  end

  defp match_update({:ok, value}), do: {true, value}
  defp match_update({:error, _}), do: false
end

See more Nebulex examples.

Testing

Testing by default spawns nodes internally for distributed tests. To run tests that do not require clustering, exclude the clustered tag:

$ mix test --exclude clustered

If you have issues running the clustered tests try running:

$ epmd -daemon

before running the tests.

Benchmarks

Nebulex provides a set of basic benchmark tests using the library benchee, and they are located within the directory benchmarks.

To run a benchmark test you have to run:

$ MIX_ENV=test mix run benchmarks/{BENCH_TEST_FILE}

Where BENCH_TEST_FILE can be any of:

  • local_with_ets_bench.exs: benchmark for the local adapter using :ets backend.
  • local_with_shards_bench.exs: benchmark for the local adapter using :shards backend.
  • partitioned_bench.exs: benchmark for the partitioned adapter.

For example, for running the benchmark for the local adapter using :shards backend:

$ MIX_ENV=test mix run benchmarks/local_with_shards_bench.exs

Additionally, you can also run performance tests using :basho_bench. See nebulex_bench example for more information.


Articles

  • coming soon...