Skip to content

Commit

Permalink
Initial draft
Browse files Browse the repository at this point in the history
  • Loading branch information
cabol committed Sep 6, 2016
1 parent f1f320d commit f624be7
Show file tree
Hide file tree
Showing 13 changed files with 637 additions and 2 deletions.
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# The directory Mix will write compiled artifacts to.
/_build

# If you run "mix test --cover", coverage assets end up here.
/cover

# The directory Mix downloads your dependencies sources to.
/deps

# Where 3rd-party dependencies like ExDoc output generated docs.
/doc

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Others
*.o
*.beam
*.plt
erl_crash.dump
.DS_Store
._*
88 changes: 86 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,86 @@
# kvx
Simple in-memory KV store written in Elixir using `cabol/shards`
# KVX

This is a simple/basic [Elixir](http://elixir-lang.org/) in-memory Key/Value Store
using [Shards](https://github.com/cabol/shards) – which is the default adapter.

## Usage

Add `kvx` to your Mix dependencies:

```elixir
defp deps do
[{:kvx, "~> 0.1.0"}]
end
```

In an existing or new module:

```elixir
defmodule MyTestMod do
use KVX.Bucket
end
```

Now let's play with `kvx`:

```elixir
> MyTestMod.new(:mybucket)
:mybucket

> MyTestMod.set(:mybucket, :k1, 1)
:mybucket

> MyTestMod.mset(:mybucket, k2: 2, k3: "3")
:mybucket

> MyTestMod.get(:mybucket, :k1)
1

> MyTestMod.mget(:mybucket, [:k2, :k3])
[2, "3"]

> MyTestMod.find_all(:mybucket)
[k3: "3", k2: 2, k1: 1]

> MyTestMod.delete(:mybucket, :k1)
:mybucket

> MyTestMod.get(:mybucket, :k1)
nil

> MyTestMod.flush!(:mybucket)
:mybucket

> MyTestMod.find_all(:mybucket)
[]
```

## Configuration

Most of the configuration that goes into the `config` is specific to the adapter.
But there are some common/shared options such as: `:adapter` and `:ttl`. E.g.:

```elixir
config :kvx,
adapter: KVX.Bucket.Shards,
ttl: 1
```

Now, in case of Shards adapter `KVX.Bucket.Shards`, it has some extra options
like `:shards_mod`. E.g.:

```elixir
config :kvx,
adapter: KVX.Bucket.Shards,
ttl: 1,
shards_mod: :shards
```

In case of Shards adapter, run-time options when calling `new/2` function, are
the same as `shards:new/2`. E.g.:

```elixir
MyModule.new(:mybucket, [n_shards: 4])
```

> **NOTE:** For more information check [KVX.Bucket.Shards](./lib/kvx/adapters/shards/bucket_shards.ex).
10 changes: 10 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# KVX config
config :kvx,
adapter: KVX.Bucket.Shards

# Import environment specific config.
import_config "#{Mix.env}.exs"
6 changes: 6 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use Mix.Config

# KVX config
config :kvx,
adapter: KVX.Bucket.Shards,
ttl: 300
6 changes: 6 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use Mix.Config

# KVX config
config :kvx,
adapter: KVX.Bucket.Shards,
ttl: 1
2 changes: 2 additions & 0 deletions lib/kvx.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
defmodule KVX do
end
130 changes: 130 additions & 0 deletions lib/kvx/adapters/shards/bucket_shards.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
defmodule KVX.Bucket.Shards do
@moduledoc """
Shards adapter. This is the default adapter supported by `KVX`.
Shards adapter only works with `set` and `ordered_set` table types.
Shards extra config options:
* `:shards_mod` - internal Shards module to use. By default, `:shards`
module is used, which is a wrapper on top of `:shards_local` and
`:shards_dist`.
Run-time options when calling `new/2` function, are the same as
`shards:new/2`. For example:
MyModule.new(:mybucket, [n_shards: 4])
For more information about `shards`:
* [GitHub](https://github.com/cabol/shards)
* [Blog Post](http://cabol.github.io/posts/2016/04/14/sharding-support-for-ets.html)
"""

@behaviour KVX.Bucket

@shards (Application.get_env(:kvx, :shards_mod, :shards))
@default_ttl (Application.get_env(:kvx, :ttl, 3600))

require Ex2ms

## Setup Commands

def new(bucket, opts \\ []) do
case Process.whereis(bucket) do
nil -> new_bucket(bucket, opts)
_ -> bucket
end
end

defp new_bucket(bucket, opts) do
{^bucket, _} = @shards.new(bucket, opts)
bucket
end

## Storage Commands

def add(bucket, key, value, ttl \\ @default_ttl) do
case get(bucket, key) do
nil -> set(bucket, key, value, ttl)
_ -> raise KVX.ConflictError, key: key, value: value
end
end

def set(bucket, key, value, ttl \\ @default_ttl) do
ttl = if is_integer(ttl) do
seconds_since_epoch(ttl)
end
true = @shards.insert(bucket, {key, value, ttl})
bucket
end

def mset(bucket, kv_pairs, ttl \\ @default_ttl) when is_list(kv_pairs) do
kv_pairs |> Enum.each(fn({key, value}) ->
^bucket = set(bucket, key, value, ttl)
end)
bucket
end

## Retrieval Commands

def get(bucket, key) do
case @shards.lookup(bucket, key) do
[{^key, value, ttl}] ->
case ttl > seconds_since_epoch(0) do
true ->
value
_ ->
true = @shards.delete(bucket, key)
nil
end
_ ->
nil
end
end

def mget(bucket, keys) when is_list(keys) do
for key <- keys do
get(bucket, key)
end
end

def find_all(bucket, query \\ nil) do
do_find_all(bucket, query)
end

defp do_find_all(bucket, nil) do
find_all(bucket, Ex2ms.fun do object -> object end)
end
defp do_find_all(bucket, query) do
bucket
|> @shards.select(query)
|> Enum.reduce([], fn({k, v, ttl}, acc) ->
case ttl > seconds_since_epoch(0) do
true ->
[{k, v} | acc]
_ ->
true = @shards.delete(bucket, k)
acc
end
end)
end

## Cleanup functions

def delete(bucket, key) do
true = @shards.delete(bucket, key)
bucket
end

def flush!(bucket) do
true = @shards.delete_all_objects(bucket)
bucket
end

## Private functions

defp seconds_since_epoch(diff) do
{mega, secs, _} = :os.timestamp()
mega * 1000000 + secs + diff
end
end
Loading

0 comments on commit f624be7

Please sign in to comment.