Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Persistent Embedded Key-Value Store with CubDB

cubdb.livemd

Persistent Embedded Key-Value Store with CubDB

Mix.install([
  {:kino, "~> 0.12.3"},
  {:cubdb, "~> 2.0"}
])

Introduction

This notebook illustrates the use of CubDB, an embedded key-value database with ACID properties and multi version concurrency control.

Note: Operations on a CubDB store are asynchronous, and thus racy in nature. For instance, a read operation may succeed even though it is preceded by a delete operation on the same key.

Setup

cwd = File.cwd!()
datadir_kino = Kino.Input.text("Directory for persisting database:", default: "#{cwd}/cubdb_demo")

Start database process:

datadir = Kino.Input.read(datadir_kino)
{:ok, db} = CubDB.start_link(data_dir: datadir)

We will be working with two keys in particular:

{xander, willow} = {
  {"Harris", "Xander"},
  {"Rosenberg", "Willow"}
}

Note: Any term can be used as key (or value, for that matter).

Basic Operations

Insert money into some accounts:

{
  CubDB.put(db, xander, 100),
  CubDB.put(db, willow, 200)
}

Verify that it worked:

{
  CubDB.get(db, xander),
  CubDB.get(db, willow)
}

Transactions

Lets transfer 20 gold from Willow to Xander:

src = willow
dst = xander
amount = 20

CubDB.transaction(db, fn tx ->
  src_balance = CubDB.Tx.get(tx, src)
  dst_balance = CubDB.Tx.get(tx, dst)

  if src_balance >= amount do
    tx =
      tx
      |> CubDB.Tx.put(src, src_balance - amount)
      |> CubDB.Tx.put(dst, dst_balance + amount)

    {:commit, tx, :ok}
  else
    {:cancel, :insufficient_funds}
  end
end)

Verify that it worked:

{
  CubDB.get(db, xander),
  CubDB.get(db, willow)
}

Snapshotted Operations

When operating with transactions, snapshots allows us to get at concistent view of the state across multiple keys:

CubDB.with_snapshot(db, fn snap ->
  xander_balance = CubDB.Snapshot.get(snap, xander)
  willow_balance = CubDB.Snapshot.get(snap, willow)

  {xander_balance, willow_balance}
end)

Selections

The simplest selection is to go through every key-value pair:

CubDB.select(db)
|> Stream.map(fn {{fname, gname}, value} -> "**#{gname} #{fname}:** #{value}\n" end)
|> Enum.join("\n")
|> Kino.Markdown.new()

Note: The select function has options for narrowing down the scope of the selection.

Removing Entries

Remove used keys:

{
  CubDB.delete(db, xander),
  CubDB.delete(db, willow)
}

Verify that it worked:

{
  CubDB.get(db, xander),
  CubDB.get(db, willow)
}

Note: The database is still present on the disk.