Riak is an opensource distributed non-relational database.
In Riak, you do not have tables with fixed schema (‘columns’) to store data. Instead you have buckets. And buckets hold keys and their values. The values may be of different types.
If you are on a Mac with Homebrew, then installing Riak is one command away:
brew install riak
For everything else, checkout the Riak documentation
Start Riak first (refer the documentation on how to do it). On my mac, running riak start
will start Riak on the default port.
$ mix new riak_example --sup
The --sup
switch tells mix
to creates a supervisor also.
Basho provides an Erlang client library to connect to Riak - riak-erlang-client. We’ll use that to connect to Riak from Elixir. After all, Elixir runs on the Erlang VM.
Add the Riak erlang client to the list of dependencies in the deps
function in the mix.exs
file of your project.
defp deps do
[ {:riakc, github: "basho/riak-erlang-client"} ]
end
Why did I use
:riakc
?If you are from any other language/ecosystem, you might be used to repositories having the same name as the library name. When you include an erlang library, check the name of the
*.app
or*.app.src
file in thesrc
directory of the Erlang library. In the Riak erlang client’s case, the file within thesrc
dir isriakc.app.src
. That is the OTP appication specification file.
Let’s play with Riak from the project’s console. Start the console with iex -S mix
.
You’ll be using the following two Erlang modules:
riakc_pb_socket
- Used to perform operations on the database.
riakc_obj
- Used to wrap and unwrap Riak objects.
Start the riak client. (Note the use of single quotes)
{ok, pid} = :riakc_pb_socket.start_link('127.0.0.1', 8087)
If Riak and the Riak erlang client have started, it should respond to pings
:riakc_pb_socket.ping(pid) # => pong
To store details of a student, in the students
bucket, you need to first wrap your data in a Riak object
{:ok, student} = :riakc_obj.new("students", :undefined, [name: "akash manohar"])
:undefined
, Riak will assign a key.Note: If you pass a key yourself instead of
:undefined
, then Riak client will only return:ok
Next, store the student object in the database
{:ok, stored_obj} :riakc_pb_socket.put(pid, student)
When you store the value pair in the database, the Riak client returns an object, which has the key of the value stored.
:riakc_obj.key(stored_obj) # => "LHBFxjIC7lh4hvPubrSYTgixKYM"
It is also possibe to get the bucket name from the key
:riakc_obj.bucket(stored_obj) # => "students"
(Thanks to @josevalim for helping out with this stuff)
Fetch the value of the key stored in the database.
{:ok, student_obj} = :riakc_pb_socket.get(pid, "students", "LHBFxjIC7lh4hvPubrSYTgixKYM")
Whatever is stored in student_obj might look like gibberish on first sight. What is returned is Riak’s representation of the value you stored and not the value of the key straight away. Try inspecting the
student_obj
and you’ll see for yourself.
Now lets get the value stored for the key from the Riak object using the :riakc_obj
Erlang module.
values = :riakc_obj.get_values(student_obj)
# Oh yes this is what you'll get
[<<131, 108, 0, 0, 0, 1, 104, 2, 100, 0, 4, 110, 97, 109, 101, 109, 0, 0, 0, 13, 97, 107, 97, 115, 104, 32, 109, 97, 110, 111, 104, 97, 114, 106>>]
That is a list of binaries. The first and only element in the list is the value you stored.
# Take the head of the list
# and convert it to an erlang term
values |> hd |> binary_to_term
# And this is what you'll get
[name: "akash manohar"]
Awesomeness ~! Your first Riak database fetch from Elixir
We are using
binary_to_term
because we are storing Erlang binaries. Riak also supports storing and indexing JSON and a few other formats. Ideally, I would choose to store JSON, because that way any programs written in other languages, accessing the database, might find it easier.
First get the Riak object of the value you want to update.
{:ok, student_obj} = :riakc_pb_socket.get(pid, "students", "LHBFxjIC7lh4hvPubrSYTgixKYM")
Update the student object and store it
updated_student = :riakc_obj.update_value(student_obj, [name: "kumar"])
:riakc_pb_socket.put(pid, updated_student, [:return_body])
Now try getting the student again and check it’s value. You’ll have the new value.
Easy peasy ~! You’ll need the bucket name and the key
:riakc_pb_socket.delete(pid, "students", "LHBFxjIC7lh4hvPubrSYTgixKYM")
We’ll write a simple app to list, create, get, update and delete students. You might get an idea on how to use Erlang/Elixir libraries in your project and add them to the supervision tree.
We are using the same app, generated at the beginning of this post. It already has generated a supervisor for us. We’ll have to write some code to tell the supervisor to start and monitor the Riak client.
Now in our Eixir app, the following will be the contents of lib/riak_example/supervisor.ex
.
defmodule RiakExample.Supervisor do
use Supervisor.Behaviour
def start_link do
# We'll name this supervisor as "riak_client_sup"
:supervisor.start_link({:local, :riak_client_sup}, __MODULE__, [])
end
def init([]) do
# We'll ask OTP to set the ID of the riak client as :riak_client
# We won't be using the ID,
# but if the supervisor is supervising
# multiple processes, then it'll come handy
children = [
worker(:riakc_pb_socket, ['127.0.0.1', 8087], id: :riak_client)
]
supervise(children, strategy: :one_for_one)
end
end
Add the following to lib/riak_example.ex
.
defmodule RiakExample do
use Application.Behaviour
# Start the supervisor
def start(_type, _args) do
RiakExample.Supervisor.start_link
end
def ping do
:riakc_pb_socket.ping(riak_client)
end
def list do
# Get a list of keys stored
:riakc_pb_socket.list_keys(riak_client, "students")
end
def create(details) do
obj = :riakc_obj.new("students", :undefined, details)
:riakc_pb_socket.put(riak_client, obj)
end
def get(key) do
{:ok, obj} = :riakc_pb_socket.get(riak_client, "students", key)
:riakc_obj.get_values(obj) |> hd |> binary_to_term
end
def update(key, value) do
{:ok, obj} = :riakc_pb_socket.get(riak_client, "students", key)
updated_obj = :riakc_obj.update_value(obj, value)
:riakc_pb_socket.put(riak_client, updated_obj, [:return_body])
end
def delete(key) do
:riakc_pb_socket.delete(riak_client, "students", key)
end
# This is a helper method to get the Riak client's pid
# from the supervisor
defp riak_client do
# :supervisor.which_children() will return
# a list of children the supervisor is supervising
# We are supervising only one process,
# So we'll pass it to the hd() function and get the first element
{_, client_pid, _, _} = :supervisor.which_children(:riak_client_sup)
|> hd
client_pid
end
end
Start the Elixir console with iex -S mix
and you’ll be able to play with the app.
$ iex -S mix
iex(1)> RiakExample.list
{:ok,
["LHBFxjIC7lh4hvPubrSYTgixKYM", "TuEfE2DOOfz8AkPpdzLmdPYUuVK",
"PPQuKZsyHWVPSbs3rQQVWW9nyTe"]}
iex(2)> RiakExample.get("LHBFxjIC7lh4hvPubrSYTgixKYM")
[name: "kumar"]
iex(3)> RiakExample.update("LHBFxjIC7lh4hvPubrSYTgixKYM", name: "P Kumar")
# -- will dispay riak object representation --
iex(4)> RiakExample.get("LHBFxjIC7lh4hvPubrSYTgixKYM")
[name: "P Kumar"]
iex(5)> RiakExample.delete("LHBFxjIC7lh4hvPubrSYTgixKYM")
:ok
Source code for this blog post is on Github.
The readme of the Riak erlang client is very detailed. Check that out for more info.
Drew Kerrigan has an Elixir wrapper for the Riak client library https://github.com/drewkerrigan/riak-elixir-client. He also wrote Elixiak, which has an OO-style API.
I’m loving Elixir. I’ve been playing with it for a couple weeks now. If you are interested in such stuff, I’m @HashNuke on twitter.