Graviton
Graviton Database: ZFS for key-value stores.
Install / Use
/learn @deroproject/GravitonREADME
Graviton Database: ZFS for key-value stores.
Graviton Database is simple, fast, versioned, authenticated, embeddable key-value store database in pure GOLANG.
Graviton Database in short is like "ZFS for key-value stores" in which every write is tracked, versioned and authenticated with cryptographic proofs. Additionally it is possible to take snapshots of database. Also it is possible to use simple copy,rsync commands for database backup even during live updates without any possibilities of database corruption.

Project Status
Graviton is currently alpha software. Almost full unit test coverage and randomized black box testing are used to ensure database consistency and thread safety. The project already has 100% code coverage. A number of decisions such as change,rename APIs, handling errors, hashing algorithms etc. are being evaluated and open for improvements and suggestions.
Features
Graviton Database in short is "ZFS for key-value stores".
- Authenticated data store (All keys, values are backed by blake 256 bit checksum).
- Append only data store.
- Support of 2^64 trees (Theoretically) within a single data store. Trees can be named and thus used as buckets.
- Support of values version tracking. All committed changes are versioned with ability to visit them at any point in time.
- Snapshots (Multi tree commits in a single version causing multi bucket sync, each snapshot can be visited, appended and further modified, keys deleted, values modified etc., new keys, values stored.)
- Ability to iterate over all key-value pairs in a tree.
- Ability to diff between 2 trees in linear time and report all changes of Insertions, Deletions, Modifications.)
- Minimal and simplified API.
- Theoretically support Exabyte data store, Multi TeraByte tested internally.
- Decoupled storage layer, allowing use of object stores such as Ceph, AWS etc.
- Ability to generate cryptographic proofs which can prove key existance or non-existance (Cryptographic Proofs are around 1 KB.)
- Superfast proof generation time of around 1000 proofs per second per core.
- Support for disk based filesystem based persistant stores.
- Support for memory based non-persistant stores.
- 100% code coverage
Table of Contents
- Getting Started
- Installing
- Opening and Using the Database
- Graviton Tree
- Using key,value pairs
- Iterating over keys
- Snapshots
- Diffing (Diffing of 2 trees to detect changes between versions or compare 2 arbitrary trees in linear time.)
- GravitonDB Backups
- Stress testing
- Graviton Internals
- Lines of Code
- TODO
- Comparison with other databases (Mysql, Postgres, LevelDB, RocksDB, LMDB, Bolt etc.)
- License
GNU General Public License v3.0
Getting Started
Installing
To start using Graviton DB, install Go and run go get:
go get github.com/deroproject/graviton/...
This will retrieve the library and build the library
Opening and Using the Database
The top-level object in Graviton is a Store. It is represented as a directory with multiple files on server's disk and represents a consistent snapshot of your data at all times.
Example code to open database:
package main
import "fmt"
import "github.com/deroproject/graviton"
func main() {
//store, _ := graviton.NewDiskStore("/tmp/testdb") // create a new testdb in "/tmp/testdb"
store, _ := graviton.NewMemStore() // create a new DB in RAM
ss, _ := store.LoadSnapshot(0) // load most recent snapshot
tree, _ := ss.GetTree("root") // use or create tree named "root"
tree.Put([]byte("key"), []byte("value")) // insert a value
graviton.Commit(tree) // commit the tree
value, _ := tree.Get([]byte("key"))
fmt.Printf("value retrived from DB \"%s\"\n", string(value))
}
//NOTE: Linux (or other platforms) have open file limit for 1024.
// Default limits allows upto 2TB of Graviton databases.
Graviton Tree
A Tree in Graviton DB acts like a bucket in BoltDB or a ZFS dataset. It is named and can contain upto 128 byte names. Any store can contain infinite trees. Each tree can also contain infinite key-value pairs. However, practically being limited by the server or system storage space.
Each tree can be accessed with its merkle root hash using "GetTreeWithRootHash" API. Also each tree maintains its own separate version number and any specific version can be used GetTreeWithVersion. Note that each tree can also have arbitrary tags and any tagged tree can be accessed using the tag GetTreeWithTag. Also, 2 arbitrary trees can diffed in linear time and relevant changes detected.
NOTE: Tree tags or names cannot start with ':' .
Using key,value pairs
To save a key/value pair to a tree ( or bucket), use the tree.Put() function:
tree, _ := ss.GetTree("root")
tree.Put([]byte("answer"), []byte("44")) // insert a value
graviton.Commit(tree) // make the tree persistant by storing it in backend disk
This will set the value of the "answer" key to "44" in the root
tree. To retrieve this value, we can use the tree.Get() function:
tree, _ := ss.GetTree("root")
v,_ := tree.Get([]byte("answer"))
fmt.Printf("The answer is: %s\n", v)
The Get() function returns an error because its operation is guaranteed to work (unless there is some kind of system failure which we try to report). If the key exists then it will return its byte slice value. If it doesn't exist then it
will return an error.
Iterating over keys
Graviton stores its keys in hash byte-sorted order within a tree. This makes sequential
iteration over these keys extremely fast. To iterate over keys GravitonDB uses a
Cursor:
// Assume "root" tree exists and has keys
tree, _ := store.GetTree("root")
c := tree.Cursor()
for k, v, err := c.First(); err == nil; k, v, err = c.Next() {
fmt.Printf("key=%s, value=%s\n", k, v)
}
The cursor allows you to move to a specific point in the list of keys and move forward or backward through the keys one at a time.
The following functions are available on the cursor:
First() Move to the first key.
Last() Move to the last key.
Next() Move to the next key.
Prev() Move to the previous key.
Each of those functions has a return signature of (key []byte, value []byte, err error).
When you have iterated to the end of the cursor then Next() will return an error ErrNoMoreKeys. You must seek to a position using First(), Last()
before calling Next() or Prev(). If you do not seek to a position then these functions will return an error.
Snapshots
Snapshot refers to collective state of all buckets + data + history. Each commit( tree.Commit() or Commit(tree1, tree2 .....)) creates a new snapshot in the store.Each snapshot is represented by an incremental uint64 number, 0 represents most recent snapshot. Snapshots can be used to access any arbitrary state of entire database at any point in time.
Example code for snapshots:
package main
import "fmt"
import "github.com/deroproject/graviton"
func main() {
key := []byte("key1")
//store, _ := graviton.NewDiskStore("/tmp/testdb") // create a new testdb in "/tmp/testdb"
store, _ := graviton.NewMemStore() // create a new DB in RAM
ss, _ := store.LoadSnapshot(0) // load most recent snapshot
tree, _ := ss.GetTree("root") // use or create tree named "root"
tree.Put(key, []byte("commit_value1")) // insert a value
commit1, _ := graviton.Commit(tree) // commit the tree
tree.Put(key, []byte("commit_value2")) // overwrite existing value
commit2, _ := graviton.Commit(tree) // commit the tree again
// at this point, you have done 2 commits
// at first commit or snapshot, "root" tree contains "key1 : commit_value1"
// at second commit or snapshot, "root" tree contains "key1 : commit_value2"
// we will traverse now commit1 snapshot
ss, _ = store.LoadSnapshot(commit1)
tree, _ = ss.GetTree("root")
value, err := tree.Get(key)
fmt.Printf(" snapshot%d key %s value %s err %s\n", ss.GetVersion(), string(key), string(value), err)
// we will traverse now commit2 snapshot
ss, _ = store.LoadSnapshot(commit2)
tree, _ = ss.GetTree("root")
value, err = tree.Get(key)
fmt.Printf(" snapshot%d key %s value %s err %s\n", ss.GetVersion(), string(key), string(value), err)
}
Diffing
Diffing of 2 trees to detect changes between versions or compare 2 arbitrary trees in linear time.
Two arbitrary trees can be diffed in linear time to detect changes. Changes are of 3 types insertions, deletions and modifications (Same key but value changed). If the reported changes are applied to base tree, it will be equivalent to the head tree being compared.
func Diff(base_tree, head_tree *Tree, deleted, modified, inserted DiffHandler) (err error)
Diffhandler is a callback function of the following type having k,v as arguments
type DiffHandler func(k, v []byte)
The algorithm is linear time in the number of changes. Eg. a tree with billion KVs can be diffed with parent almost instantaneously.
GravitonDB Backups
Use simple commands like cp, copy or rsync to sync a Graviton database even while the database is being updated. However, as the database might be continuously appending, backup will always lag a bit. And note that the database or backups will NEVER get corrupted durin
Related Skills
feishu-drive
353.3k|
things-mac
353.3kManage Things 3 via the `things` CLI on macOS (add/update projects+todos via URL scheme; read/search/list from the local Things database)
clawhub
353.3kUse the ClawHub CLI to search, install, update, and publish agent skills from clawhub.com
postkit
PostgreSQL-native identity, configuration, metering, and job queues. SQL functions that work with any language or driver
