In recent years, many applications, recommender, fraud detection, are based on a graph structure. And these applications have got more real-time, and dynamic. There are so many graph-based databases, but almost all of graph DB seems too heavy, or too huge.
We've just needed a simple graph structure, but not highly theorized algorithms such as ontologies or optimization techniques.
LanternDB is In-memory key-vertex-store
(KVS). It behaves like key-value-store
but can explore neighbor vertices
based on graph structure.
LanternDB is a streaming database. All vertices or edges will be expired as time passes, just like real relationships.
LanternDB just illuminates the moment, just focuses on neighbors, not global structures.
git clone https://github.com/lantern-db/lantern.git
cd lantern
docker-compose up
Then you can see followings.
- grpc-server
- localhost:6380
- grpc-gateway
- viewer
- prometheus-server
We can use DumpVertex(ctx, key, value, ttl)
method to register vertices like below.
_ := c.DumpVertex(ctx, "a", "value of a", 60*time.Second)
_ := c.DumpVertex(ctx, "b", "value of b", 60*time.Second)
_ := c.DumpVertex(ctx, "c", "value of c", 60*time.Second)
_ := c.DumpVertex(ctx, "d", "value of d", 60*time.Second)
Loading vertices behaves like standard key-value-store.
resA, err := c.LoadVertex(ctx, "a")
The value of resA
is an instance of model.Vertex
, and a field Vertex.Value
is just interface{}
. To get the
typed-value, we can use Vertex.XXXValue()
method such like IntValue()
. And if a value is not valid as int, this
method will return error.
str, err := resA.StringValue() // => valid case, returns "value of a"
i, err := resA.IntValue() // => invalid case, returns error
Edges can also be created with Dump(ctx, keyOfTail, keyOfHead, weight, ttl)
.
_ = c.DumpEdge(ctx, "a", "b", 1.0, 60*time.Second)
_ = c.DumpEdge(ctx, "b", "c", 1.0, 60*time.Second)
_ = c.DumpEdge(ctx, "c", "d", 1.0, 60*time.Second)
_ = c.DumpEdge(ctx, "b", "e", 1.0, 60*time.Second)
If the vertex which has key a
is missing in a graph, then empty valued vertices will be created with same expirations.
Once you set multiple duplicate edges, this weight of the edge will be incremented.
_ = c.DumpEdge(ctx, "a", "b", 1.0, 3*time.Second) // weight of e(a, b) -> 1.0
_ = c.DumpEdge(ctx, "a", "b", 1.0, 3*time.Second) // weight of e(a, b) -> 2.0
But each TTLs of transactions will be expired independently.
_ = c.DumpEdge(ctx, "a", "b", 1.0, 2*time.Second) // weight of e(a, b) -> 1.0
time.Sleep(1*time.Second) // weight of e(a, b) -> 1.0
_ = c.DumpEdge(ctx, "a", "b", 1.0, 2*time.Second) // weight of e(a, b) -> 2.0
time.Sleep(1*time.Second) // weight of e(a, b) -> 1.0, first transaction is expired
time.Sleep(1*time.Second) // weight of e(a, b) -> 0.0, second transaction is expired
All vertices can be loaded with a graph structure linking with edges, And we call this transaction Illuminate
. An
client has method Illuminate(ctx, key, step)
.
For example, client.Illuminate(ctx, "a", 2)
returns all vertices within 2-steps from a vertex "a". type of returning
value is an instance of model.Graph
and it can be rendered to json-parsable struct with a method Render()
.
{
"vertices": {
"a": "test",
"b": 42,
"c": 3.14
},
"edges": {
"a": {
"b": 1
},
"b": {
"c": 1
}
},
"stats": {
"vertex": {
"a": {
"degree": {
"out": 1
}
},
"b": {
"degree": {
"in": 1,
"out": 2
}
},
"c": {
"degree": {
"in": 1,
"out": 1
}
}
}
}
}
Example usage of Lantern-client
for Golang.
example/client/simple/simple.go
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/lantern-db/lantern/client"
"log"
"os"
"time"
)
func main() {
c, err := client.NewLanternClient("localhost", 6380)
if err != nil {
fmt.Printf("hoge %v", err)
panic(err)
}
defer func() {
err := c.Close()
if err != nil {
panic(err)
}
}()
ctx := context.Background()
_ = c.DumpVertex(ctx, "a", "test", 60*time.Second)
_ = c.DumpVertex(ctx, "b", 42, 60*time.Second)
_ = c.DumpVertex(ctx, "c", 3.14, 60*time.Second)
if resA, err := c.LoadVertex(ctx, "a"); err == nil {
log.Println(resA.StringValue())
}
if resB, err := c.LoadVertex(ctx, "b"); err == nil {
log.Println(resB.IntValue())
}
if resC, err := c.LoadVertex(ctx, "c"); err == nil {
log.Println(resC.Float64Value())
}
if _, err := c.LoadVertex(ctx, "d"); err == nil {
log.Println(err)
}
_ = c.DumpEdge(ctx, "a", "b", 1.0, 60*time.Second)
_ = c.DumpEdge(ctx, "b", "c", 1.0, 60*time.Second)
_ = c.DumpEdge(ctx, "c", "d", 1.0, 60*time.Second)
_ = c.DumpEdge(ctx, "b", "e", 1.0, 60*time.Second)
result, err := c.Illuminate(ctx, "a", 2)
if err != nil {
log.Fatalf("error: %v", err)
}
jsonBytes, _ := json.Marshal(result.Render())
log.Println(string(jsonBytes))
}
Then we will get
{
"vertices": {
"a": "test",
"b": 42,
"c": 3.14,
"e": null
},
"edges": {
"a": {
"b": 1
},
"b": {
"c": 1,
"e": 1
}
},
"stats": {
"vertex": {
"a": {
"degree": {
"out": 1
}
},
"b": {
"degree": {
"in": 1,
"out": 2
}
},
"c": {
"degree": {
"in": 2,
"out": 1
}
},
"e": {
"degree": {
"in": 1
}
}
}
}
}
And it shows simple graph structure.