Giter VIP home page Giter VIP logo

buntdb's Introduction

BuntDB
Godoc LICENSE

BuntDB is a low-level, in-memory, key/value store in pure Go. It persists to disk, is ACID compliant, and uses locking for multiple readers and a single writer. It supports custom indexes and geospatial data. It's ideal for projects that need a dependable database and favor speed over data size.

Features

Getting Started

Installing

To start using BuntDB, install Go and run go get:

$ go get -u github.com/tidwall/buntdb

This will retrieve the library.

Opening a database

The primary object in BuntDB is a DB. To open or create your database, use the buntdb.Open() function:

package main

import (
	"log"

	"github.com/tidwall/buntdb"
)

func main() {
	// Open the data.db file. It will be created if it doesn't exist.
	db, err := buntdb.Open("data.db")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	...
}

It's also possible to open a database that does not persist to disk by using :memory: as the path of the file.

buntdb.Open(":memory:") // Open a file that does not persist to disk.

Transactions

All reads and writes must be performed from inside a transaction. BuntDB can have one write transaction opened at a time, but can have many concurrent read transactions. Each transaction maintains a stable view of the database. In other words, once a transaction has begun, the data for that transaction cannot be changed by other transactions.

Transactions run in a function that exposes a Tx object, which represents the transaction state. While inside a transaction, all database operations should be performed using this object. You should never access the origin DB object while inside a transaction. Doing so may have side-effects, such as blocking your application.

When a transaction fails, it will roll back, and revert all changes that occurred to the database during that transaction. There's a single return value that you can use to close the transaction. For read/write transactions, returning an error this way will force the transaction to roll back. When a read/write transaction succeeds all changes are persisted to disk.

Read-only Transactions

A read-only transaction should be used when you don't need to make changes to the data. The advantage of a read-only transaction is that there can be many running concurrently.

err := db.View(func(tx *buntdb.Tx) error {
	...
	return nil
})

Read/write Transactions

A read/write transaction is used when you need to make changes to your data. There can only be one read/write transaction running at a time. So make sure you close it as soon as you are done with it.

err := db.Update(func(tx *buntdb.Tx) error {
	...
	return nil
})

Setting and getting key/values

To set a value you must open a read/write transaction:

err := db.Update(func(tx *buntdb.Tx) error {
	_, _, err := tx.Set("mykey", "myvalue", nil)
	return err
})

To get the value:

err := db.View(func(tx *buntdb.Tx) error {
	val, err := tx.Get("mykey")
	if err != nil{
		return err
	}
	fmt.Printf("value is %s\n", val)
	return nil
})

Getting non-existent values will cause an ErrNotFound error.

Iterating

All keys/value pairs are ordered in the database by the key. To iterate over the keys:

err := db.View(func(tx *buntdb.Tx) error {
	err := tx.Ascend("", func(key, value string) bool {
		fmt.Printf("key: %s, value: %s\n", key, value)
		return true // continue iteration
	})
	return err
})

There is also AscendGreaterOrEqual, AscendLessThan, AscendRange, AscendEqual, Descend, DescendLessOrEqual, DescendGreaterThan, DescendRange, and DescendEqual. Please see the documentation for more information on these functions.

Custom Indexes

Initially all data is stored in a single B-tree with each item having one key and one value. All of these items are ordered by the key. This is great for quickly getting a value from a key or iterating over the keys. Feel free to peruse the B-tree implementation.

You can also create custom indexes that allow for ordering and iterating over values. A custom index also uses a B-tree, but it's more flexible because it allows for custom ordering.

For example, let's say you want to create an index for ordering names:

db.CreateIndex("names", "*", buntdb.IndexString)

This will create an index named names which stores and sorts all values. The second parameter is a pattern that is used to filter on keys. A * wildcard argument means that we want to accept all keys. IndexString is a built-in function that performs case-insensitive ordering on the values

Now you can add various names:

db.Update(func(tx *buntdb.Tx) error {
	tx.Set("user:0:name", "tom", nil)
	tx.Set("user:1:name", "Randi", nil)
	tx.Set("user:2:name", "jane", nil)
	tx.Set("user:4:name", "Janet", nil)
	tx.Set("user:5:name", "Paula", nil)
	tx.Set("user:6:name", "peter", nil)
	tx.Set("user:7:name", "Terri", nil)
	return nil
})

Finally you can iterate over the index:

db.View(func(tx *buntdb.Tx) error {
	tx.Ascend("names", func(key, val string) bool {
	fmt.Printf(buf, "%s %s\n", key, val)
		return true
	})
	return nil
})

The output should be:

user:2:name jane
user:4:name Janet
user:5:name Paula
user:6:name peter
user:1:name Randi
user:7:name Terri
user:0:name tom

The pattern parameter can be used to filter on keys like this:

db.CreateIndex("names", "user:*", buntdb.IndexString)

Now only items with keys that have the prefix user: will be added to the names index.

Built-in types

Along with IndexString, there is also IndexInt, IndexUint, and IndexFloat. These are built-in types for indexing. You can choose to use these or create your own.

So to create an index that is numerically ordered on an age key, we could use:

db.CreateIndex("ages", "user:*:age", buntdb.IndexInt)

And then add values:

db.Update(func(tx *buntdb.Tx) error {
	tx.Set("user:0:age", "35", nil)
	tx.Set("user:1:age", "49", nil)
	tx.Set("user:2:age", "13", nil)
	tx.Set("user:4:age", "63", nil)
	tx.Set("user:5:age", "8", nil)
	tx.Set("user:6:age", "3", nil)
	tx.Set("user:7:age", "16", nil)
	return nil
})
db.View(func(tx *buntdb.Tx) error {
	tx.Ascend("ages", func(key, val string) bool {
	fmt.Printf(buf, "%s %s\n", key, val)
		return true
	})
	return nil
})

The output should be:

user:6:age 3
user:5:age 8
user:2:age 13
user:7:age 16
user:0:age 35
user:1:age 49
user:4:age 63

Spatial Indexes

BuntDB has support for spatial indexes by storing rectangles in an R-tree. An R-tree is organized in a similar manner as a B-tree, and both are balanced trees. But, an R-tree is special because it can operate on data that is in multiple dimensions. This is super handy for Geospatial applications.

To create a spatial index use the CreateSpatialIndex function:

db.CreateSpatialIndex("fleet", "fleet:*:pos", buntdb.IndexRect)

Then IndexRect is a built-in function that converts rect strings to a format that the R-tree can use. It's easy to use this function out of the box, but you might find it better to create a custom one that renders from a different format, such as Well-known text or GeoJSON.

To add some lon,lat points to the fleet index:

db.Update(func(tx *buntdb.Tx) error {
	tx.Set("fleet:0:pos", "[-115.567 33.532]", nil)
	tx.Set("fleet:1:pos", "[-116.671 35.735]", nil)
	tx.Set("fleet:2:pos", "[-113.902 31.234]", nil)
	return nil
})

And then you can run the Intersects function on the index:

db.View(func(tx *buntdb.Tx) error {
	tx.Intersects("fleet", "[-117 30],[-112 36]", func(key, val string) bool {
		...
		return true
	})
	return nil
})

This will get all three positions.

k-Nearest Neighbors

Use the Nearby function to get all the positions in order of nearest to farthest :

db.View(func(tx *buntdb.Tx) error {
	tx.Nearby("fleet", "[-113 33]", func(key, val string, dist float64) bool {
		...
		return true
	})
	return nil
})

Spatial bracket syntax

The bracket syntax [-117 30],[-112 36] is unique to BuntDB, and it's how the built-in rectangles are processed. But, you are not limited to this syntax. Whatever Rect function you choose to use during CreateSpatialIndex will be used to process the parameter, in this case it's IndexRect.

  • 2D rectangle: [10 15],[20 25] Min XY: "10x15", Max XY: "20x25"

  • 3D rectangle: [10 15 12],[20 25 18] Min XYZ: "10x15x12", Max XYZ: "20x25x18"

  • 2D point: [10 15] XY: "10x15"

  • LonLat point: [-112.2693 33.5123] LatLon: "33.5123 -112.2693"

  • LonLat bounding box: [-112.26 33.51],[-112.18 33.67] Min LatLon: "33.51 -112.26", Max LatLon: "33.67 -112.18"

Notice: The longitude is the Y axis and is on the left, and latitude is the X axis and is on the right.

You can also represent Infinity by using -inf and +inf. For example, you might have the following points ([X Y M] where XY is a point and M is a timestamp):

[3 9 1]
[3 8 2]
[4 8 3]
[4 7 4]
[5 7 5]
[5 6 6]

You can then do a search for all points with M between 2-4 by calling Intersects.

tx.Intersects("points", "[-inf -inf 2],[+inf +inf 4]", func(key, val string) bool {
	println(val)
	return true
})

Which will return:

[3 8 2]
[4 8 3]
[4 7 4]

JSON Indexes

Indexes can be created on individual fields inside JSON documents. BuntDB uses GJSON under the hood.

For example:

package main

import (
	"fmt"

	"github.com/tidwall/buntdb"
)

func main() {
	db, _ := buntdb.Open(":memory:")
	db.CreateIndex("last_name", "*", buntdb.IndexJSON("name.last"))
	db.CreateIndex("age", "*", buntdb.IndexJSON("age"))
	db.Update(func(tx *buntdb.Tx) error {
		tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil)
		tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil)
		tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil)
		tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil)
		return nil
	})
	db.View(func(tx *buntdb.Tx) error {
		fmt.Println("Order by last name")
		tx.Ascend("last_name", func(key, value string) bool {
			fmt.Printf("%s: %s\n", key, value)
			return true
		})
		fmt.Println("Order by age")
		tx.Ascend("age", func(key, value string) bool {
			fmt.Printf("%s: %s\n", key, value)
			return true
		})
		fmt.Println("Order by age range 30-50")
		tx.AscendRange("age", `{"age":30}`, `{"age":50}`, func(key, value string) bool {
			fmt.Printf("%s: %s\n", key, value)
			return true
		})
		return nil
	})
}

Results:

Order by last name
3: {"name":{"first":"Carol","last":"Anderson"},"age":52}
4: {"name":{"first":"Alan","last":"Cooper"},"age":28}
1: {"name":{"first":"Tom","last":"Johnson"},"age":38}
2: {"name":{"first":"Janet","last":"Prichard"},"age":47}

Order by age
4: {"name":{"first":"Alan","last":"Cooper"},"age":28}
1: {"name":{"first":"Tom","last":"Johnson"},"age":38}
2: {"name":{"first":"Janet","last":"Prichard"},"age":47}
3: {"name":{"first":"Carol","last":"Anderson"},"age":52}

Order by age range 30-50
1: {"name":{"first":"Tom","last":"Johnson"},"age":38}
2: {"name":{"first":"Janet","last":"Prichard"},"age":47}

Multi Value Index

With BuntDB it's possible to join multiple values on a single index. This is similar to a multi column index in a traditional SQL database.

In this example we are creating a multi value index on "name.last" and "age":

db, _ := buntdb.Open(":memory:")
db.CreateIndex("last_name_age", "*", buntdb.IndexJSON("name.last"), buntdb.IndexJSON("age"))
db.Update(func(tx *buntdb.Tx) error {
	tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil)
	tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil)
	tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil)
	tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil)
	tx.Set("5", `{"name":{"first":"Sam","last":"Anderson"},"age":51}`, nil)
	tx.Set("6", `{"name":{"first":"Melinda","last":"Prichard"},"age":44}`, nil)
	return nil
})
db.View(func(tx *buntdb.Tx) error {
	tx.Ascend("last_name_age", func(key, value string) bool {
		fmt.Printf("%s: %s\n", key, value)
		return true
	})
	return nil
})

// Output:
// 5: {"name":{"first":"Sam","last":"Anderson"},"age":51}
// 3: {"name":{"first":"Carol","last":"Anderson"},"age":52}
// 4: {"name":{"first":"Alan","last":"Cooper"},"age":28}
// 1: {"name":{"first":"Tom","last":"Johnson"},"age":38}
// 6: {"name":{"first":"Melinda","last":"Prichard"},"age":44}
// 2: {"name":{"first":"Janet","last":"Prichard"},"age":47}

Descending Ordered Index

Any index can be put in descending order by wrapping it's less function with buntdb.Desc.

db.CreateIndex("last_name_age", "*",
    buntdb.IndexJSON("name.last"),
    buntdb.Desc(buntdb.IndexJSON("age")),
)

This will create a multi value index where the last name is ascending and the age is descending.

Collate i18n Indexes

Using the external collate package it's possible to create indexes that are sorted by the specified language. This is similar to the SQL COLLATE keyword found in traditional databases.

To install:

go get -u github.com/tidwall/collate

For example:

import "github.com/tidwall/collate"

// To sort case-insensitive in French.
db.CreateIndex("name", "*", collate.IndexString("FRENCH_CI"))

// To specify that numbers should sort numerically ("2" < "12")
// and use a comma to represent a decimal point.
db.CreateIndex("amount", "*", collate.IndexString("FRENCH_NUM"))

There's also support for Collation on JSON indexes:

db.CreateIndex("last_name", "*", collate.IndexJSON("CHINESE_CI", "name.last"))

Check out the collate project for more information.

Data Expiration

Items can be automatically evicted by using the SetOptions object in the Set function to set a TTL.

db.Update(func(tx *buntdb.Tx) error {
	tx.Set("mykey", "myval", &buntdb.SetOptions{Expires:true, TTL:time.Second})
	return nil
})

Now mykey will automatically be deleted after one second. You can remove the TTL by setting the value again with the same key/value, but with the options parameter set to nil.

Delete while iterating

BuntDB does not currently support deleting a key while in the process of iterating. As a workaround you'll need to delete keys following the completion of the iterator.

var delkeys []string
tx.AscendKeys("object:*", func(k, v string) bool {
	if someCondition(k) == true {
		delkeys = append(delkeys, k)
	}
	return true // continue
})
for _, k := range delkeys {
	if _, err = tx.Delete(k); err != nil {
		return err
	}
}

Append-only File

BuntDB uses an AOF (append-only file) which is a log of all database changes that occur from operations like Set() and Delete().

The format of this file looks like:

set key:1 value1
set key:2 value2
set key:1 value3
del key:2
...

When the database opens again, it will read back the aof file and process each command in exact order. This read process happens one time when the database opens. From there on the file is only appended.

As you may guess this log file can grow large over time. There's a background routine that automatically shrinks the log file when it gets too large. There is also a Shrink() function which will rewrite the aof file so that it contains only the items in the database. The shrink operation does not lock up the database so read and write transactions can continue while shrinking is in process.

Durability and fsync

By default BuntDB executes an fsync once every second on the aof file. Which simply means that there's a chance that up to one second of data might be lost. If you need higher durability then there's an optional database config setting Config.SyncPolicy which can be set to Always.

The Config.SyncPolicy has the following options:

  • Never - fsync is managed by the operating system, less safe
  • EverySecond - fsync every second, fast and safer, this is the default
  • Always - fsync after every write, very durable, slower

Config

Here are some configuration options that can be use to change various behaviors of the database.

  • SyncPolicy adjusts how often the data is synced to disk. This value can be Never, EverySecond, or Always. Default is EverySecond.
  • AutoShrinkPercentage is used by the background process to trigger a shrink of the aof file when the size of the file is larger than the percentage of the result of the previous shrunk file. For example, if this value is 100, and the last shrink process resulted in a 100mb file, then the new aof file must be 200mb before a shrink is triggered. Default is 100.
  • AutoShrinkMinSize defines the minimum size of the aof file before an automatic shrink can occur. Default is 32MB.
  • AutoShrinkDisabled turns off automatic background shrinking. Default is false.

To update the configuration you should call ReadConfig followed by SetConfig. For example:

var config buntdb.Config
if err := db.ReadConfig(&config); err != nil{
	log.Fatal(err)
}
if err := db.SetConfig(config); err != nil{
	log.Fatal(err)
}

Performance

How fast is BuntDB?

Here are some example benchmarks when using BuntDB in a Raft Store implementation.

You can also run the standard Go benchmark tool from the project root directory:

go test --bench=.

BuntDB-Benchmark

There's a custom utility that was created specifically for benchmarking BuntDB.

These are the results from running the benchmarks on a MacBook Pro 15" 2.8 GHz Intel Core i7:

$ buntdb-benchmark -q
GET: 4609604.74 operations per second
SET: 248500.33 operations per second
ASCEND_100: 2268998.79 operations per second
ASCEND_200: 1178388.14 operations per second
ASCEND_400: 679134.20 operations per second
ASCEND_800: 348445.55 operations per second
DESCEND_100: 2313821.69 operations per second
DESCEND_200: 1292738.38 operations per second
DESCEND_400: 675258.76 operations per second
DESCEND_800: 337481.67 operations per second
SPATIAL_SET: 134824.60 operations per second
SPATIAL_INTERSECTS_100: 939491.47 operations per second
SPATIAL_INTERSECTS_200: 561590.40 operations per second
SPATIAL_INTERSECTS_400: 306951.15 operations per second
SPATIAL_INTERSECTS_800: 159673.91 operations per second

To install this utility:

go get github.com/tidwall/buntdb-benchmark

Contact

Josh Baker @tidwall

License

BuntDB source code is available under the MIT License.

buntdb's People

Contributors

alesvaupotic avatar appleboy avatar dc0d avatar dependabot[bot] avatar funkyboy avatar hallerm avatar jacobh avatar prisoner avatar radarhere avatar sora233 avatar tidwall avatar tomrosystems avatar xmikus01 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

buntdb's Issues

Attempting to use AscendEqual

I'm trying to use the AscendEqual function to iterate through entries, but it's not returning the results I'm expecting. I've narrowed the issue down to the following large-ish (sorry) test case:

func TestNameEquals(t *testing.T) {
	iter := func(key, value string) bool {
		i, _ := strconv.Atoi(key)
		fmt.Printf("%2d - %v\n", i, value)
		return true
	}

	j := func(v string, a int) string {
		return fmt.Sprintf("{\"name\":\"%v\", \"age\":%v}", v, a)
	}

	name := func(v string) string {
		return fmt.Sprintf("{\"name\":\"%v\"}", v)
	}

	db, _ := buntdb.Open(":memory:")
	db.CreateIndex("name", "*",
		buntdb.IndexJSON("name"),
		buntdb.IndexJSON("age"),
	)
	db.Update(func(tx *buntdb.Tx) error {
		tx.Set("1", j("bob", 42), nil)
		tx.Set("2", j("james", 55), nil)
		tx.Set("4", j("logan", 14), nil)
		tx.Set("11", j("james", 11), nil)
		tx.Set("3", j("clark", 33), nil)
		tx.Set("5", j("tommy", 15), nil)
		tx.Set("6", j("zane", 19), nil)
		return nil
	})

	db.View(func(tx *buntdb.Tx) error {
		fmt.Println(" -- Greater than d")
		tx.AscendGreaterOrEqual("name", name("d"), iter)

		fmt.Println(" -- Equals James")
		tx.AscendEqual("name", name("james"), iter)

		return nil
	})
}

The output from running the test case is:

=== RUN   TestNameEquals
 -- Greater than d
11 - {"name":"james", "age":11}
 2 - {"name":"james", "age":55}
 4 - {"name":"logan", "age":14}
 5 - {"name":"tommy", "age":15}
 6 - {"name":"zane", "age":19}
 -- Equals James
--- PASS: TestNameEquals (0.00s)

AscendGreaterOrEqual works exactly as expected, but I was hoping that AscendEqual would allow me to iterate only the james entries sorted by the age field. Have I misunderstood the purpose of the function?

Sliding Expiration and Callback

This is a feature request - if it's proper to add them.

  1. Adding sliding TTL; on each get operation, if the entry has a TTL and a sliding flag is set, the absolute internal expiration time will be shifted into future.
  2. Setting a callback like func(key, value string) that will be called (in it's own new goroutine) when an entry gets expired.

Since these are optional, they would affect performance only if they are present.

Invalid descend range result

hello, tidwall
I hoped to get the ascend results is right when I was using tx.AscendRange or tx.DescendRange fuction.
But when i hoped to get the descend results, it's nil of return.
Can you help me? thanks.

db, _ := buntdb.Open(":memory:")
	db.Update(func(tx *buntdb.Tx) error {
		tx.CreateIndex("last_name", "*", buntdb.IndexJSON("name.last"))
		
		tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":"38"}`, nil)
		tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":"47"}`, nil)
		tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":"52"}`, nil)
		tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":"28"}`, nil)
		tx.Set("5", `{"name":{"first":"Jack","last":"Ma"},"age":"20"}`, nil)
		tx.Set("6", `{"name":{"first":"Blues","last":"Li"},"age":"31"}`, nil)
		tx.Set("7", `{"name":{"first":"Pony","last":"Ma"},"age":"3"}`, nil)
		tx.Set("8", `{"name":{"first":"Robin","last":"Li"},"age":"2"}`, nil)
		tx.Set("9", `{"name":{"first":"Larry","last":"Liu"},"age":""}`, nil)
		return nil
	})

	db.View(func(tx *buntdb.Tx) error {
		fmt.Println("---------------------")
		tx.Descend("last_name", func(key, value string) bool {
			fmt.Printf("%s: %s\n", key, value)
			return true
		})
		
		fmt.Println("---------------------")
		tx.DescendRange("last_name", `{"name":{"last":""}}`, `{"name":{"last":"Ma"}}`, func(key, value string) bool {
			fmt.Printf("%s: %s\n", key, value)
			return true
		})
		return nil
	})

result:

---------------------
2: {"name":{"first":"Janet","last":"Prichard"},"age":"47"}
7: {"name":{"first":"Pony","last":"Ma"},"age":"3"}
5: {"name":{"first":"Jack","last":"Ma"},"age":"20"}
9: {"name":{"first":"Larry","last":"Liu"},"age":""}
8: {"name":{"first":"Robin","last":"Li"},"age":"2"}
6: {"name":{"first":"Blues","last":"Li"},"age":"31"}
1: {"name":{"first":"Tom","last":"Johnson"},"age":"38"}
4: {"name":{"first":"Alan","last":"Cooper"},"age":"28"}
3: {"name":{"first":"Carol","last":"Anderson"},"age":"52"}
---------------------

tx.CreateIndex("last_name", "*", buntdb.IndexJSON("name.last")
replace
tx.CreateIndex("last_name", "*", buntdb.Desc(buntdb.IndexJSON("name.last")))

result:

---------------------
3: {"name":{"first":"Carol","last":"Anderson"},"age":"52"}
4: {"name":{"first":"Alan","last":"Cooper"},"age":"28"}
1: {"name":{"first":"Tom","last":"Johnson"},"age":"38"}
8: {"name":{"first":"Robin","last":"Li"},"age":"2"}
6: {"name":{"first":"Blues","last":"Li"},"age":"31"}
9: {"name":{"first":"Larry","last":"Liu"},"age":""}
7: {"name":{"first":"Pony","last":"Ma"},"age":"3"}
5: {"name":{"first":"Jack","last":"Ma"},"age":"20"}
2: {"name":{"first":"Janet","last":"Prichard"},"age":"47"}
---------------------
3: {"name":{"first":"Carol","last":"Anderson"},"age":"52"}
4: {"name":{"first":"Alan","last":"Cooper"},"age":"28"}
1: {"name":{"first":"Tom","last":"Johnson"},"age":"38"}
8: {"name":{"first":"Robin","last":"Li"},"age":"2"}
6: {"name":{"first":"Blues","last":"Li"},"age":"31"}
9: {"name":{"first":"Larry","last":"Liu"},"age":""}

Is the function of data expiration still valid in such case?

I want to create a database on disk, and let all item expire in 7 days after creating.

Here is the code:

db.Update(func(tx *buntdb.Tx) error {
		_, _, err = tx.Set("word", "define",
			&buntdb.SetOptions{Expires: true, TTL: time.Duration(7*24) * time.Hour})
		return
})

However, the program will not run for such long time. Is the function of data expiration still valid?

Here is the result I expect:

  1. Run the program in Nov 1st, add item "A" in the database and then exit.
  2. Run the program in Nov 3rd, add item "B" in the database and then exit.
  3. Run the program in Nov 7th. At this time, item "A" and "B" are still existent.
  4. Run the program in Nov 9th. At this time, only item "B" is still existent.
  5. Run the program in Nov 11th. At this time, non of item "A" and "B" is existent.

Is it possible?

Is there a another way to avoid with deprecate warning ?

Thanks in advance..
I'm new to buntdb. but while Testng example code, I got a message about deprecated method.
for example, below ....
db.CreateIndex("ages", "user:*:age", buntdb.IndexInt)

readme.md is too old ? if not, (in source code) comment is misspelled ?

Tests fail with Go 1.14 race detector.

Context: https://golang.org/doc/go1.14#compiler

Test output:

go test -v -race ./...
=== RUN   TestBackgroudOperations
--- PASS: TestBackgroudOperations (3.46s)
=== RUN   TestSaveLoad
--- PASS: TestSaveLoad (0.00s)
=== RUN   TestMutatingIterator
--- PASS: TestMutatingIterator (0.19s)
=== RUN   TestCaseInsensitiveIndex
--- PASS: TestCaseInsensitiveIndex (0.03s)
=== RUN   TestIndexTransaction
--- PASS: TestIndexTransaction (0.01s)
=== RUN   TestDeleteAll
--- PASS: TestDeleteAll (0.02s)
=== RUN   TestAscendEqual
--- PASS: TestAscendEqual (0.02s)
=== RUN   TestDescendEqual
--- PASS: TestDescendEqual (0.02s)
=== RUN   TestVariousTx
fatal error: checkptr: unsafe pointer conversion

goroutine 27 [running]:
runtime.throw(0x12c2d5f, 0x23)
	/usr/local/go/src/runtime/panic.go:1116 +0x72 fp=0xc0001372e8 sp=0xc0001372b8 pc=0x1070f52
runtime.checkptrAlignment(0xc0001692c0, 0x12a37a0, 0x1)
	/usr/local/go/src/runtime/checkptr.go:20 +0xc9 fp=0xc000137318 sp=0xc0001372e8 pc=0x10443d9
github.com/tidwall/rtree/base.(*treeItem).unsafeNode(...)
	/Users/olone/go/pkg/mod/github.com/tidwall/[email protected]/base/rtree.go:187
github.com/tidwall/rtree/base.(*RTree).insert(0xc00024c000, 0xc00007c6c0, 0x1296c60, 0xc0002483c0, 0x0, 0xc000058c00)
	/Users/olone/go/pkg/mod/github.com/tidwall/[email protected]/base/rtree.go:242 +0x2bc fp=0xc000137408 sp=0xc000137318 pc=0x12179cc
github.com/tidwall/rtree/base.(*RTree).Insert(0xc00024c000, 0xc000234990, 0x2, 0x2, 0xc0002349a0, 0x2, 0x2, 0x1296c60, 0xc0002483c0)
	/Users/olone/go/pkg/mod/github.com/tidwall/[email protected]/base/rtree.go:227 +0x1cd fp=0xc0001374b8 sp=0xc000137408 pc=0x121765d
github.com/tidwall/rtree.(*RTree).Insert(0xc0001691c0, 0x12fef00, 0xc0002483c0)
	/Users/olone/go/pkg/mod/github.com/tidwall/[email protected]/rtree.go:57 +0x2f7 fp=0xc000137598 sp=0xc0001374b8 pc=0x121b9a7
github.com/tidwall/buntdb.(*DB).insertIntoDatabase(0xc00012a160, 0xc0002483c0, 0xc0001376f8)
	/Users/olone/playground/buntdb/buntdb.go:494 +0x456 fp=0xc0001376a8 sp=0xc000137598 pc=0x12236a6
github.com/tidwall/buntdb.(*Tx).Set(0xc00000e700, 0x12bb6d9, 0x6, 0x12bd469, 0xf, 0x0, 0x1008710, 0xc000137868, 0x104a0bc, 0x1007970, ...)
	/Users/olone/playground/buntdb/buntdb.go:1378 +0x1cc fp=0xc000137780 sp=0xc0001376a8 pc=0x122b89c
github.com/tidwall/buntdb.TestVariousTx.func13(0xc00000e700, 0xc000137801, 0xc00000e700)
	/Users/olone/playground/buntdb/buntdb_test.go:950 +0x515 fp=0xc000137940 sp=0xc000137780 pc=0x1254245
github.com/tidwall/buntdb.(*DB).managed(0xc00012a160, 0x12bb101, 0xc000137e40, 0x0, 0x0)
	/Users/olone/playground/buntdb/buntdb.go:949 +0x153 fp=0xc000137a30 sp=0xc000137940 pc=0x1226f33
github.com/tidwall/buntdb.(*DB).Update(...)
	/Users/olone/playground/buntdb/buntdb.go:972
github.com/tidwall/buntdb.TestVariousTx(0xc00017e240)
	/Users/olone/playground/buntdb/buntdb_test.go:928 +0x171f fp=0xc000137ed0 sp=0xc000137a30 pc=0x123778f
testing.tRunner(0xc00017e240, 0x12c7870)
	/usr/local/go/src/testing/testing.go:991 +0x1ec fp=0xc000137fd0 sp=0xc000137ed0 pc=0x11721bc
runtime.goexit()
	/usr/local/go/src/runtime/asm_amd64.s:1373 +0x1 fp=0xc000137fd8 sp=0xc000137fd0 pc=0x10a4851
created by testing.(*T).Run
	/usr/local/go/src/testing/testing.go:1042 +0x661

goroutine 1 [chan receive]:
testing.(*T).Run(0xc000158000, 0x12bce1d, 0xd, 0x12c7870, 0x1)
	/usr/local/go/src/testing/testing.go:1043 +0x699
testing.runTests.func1(0xc000158000)
	/usr/local/go/src/testing/testing.go:1284 +0xa7
testing.tRunner(0xc000158000, 0xc000135d50)
	/usr/local/go/src/testing/testing.go:991 +0x1ec
testing.runTests(0xc000138020, 0x14697a0, 0x20, 0x20, 0x0)
	/usr/local/go/src/testing/testing.go:1282 +0x528
testing.(*M).Run(0xc00014e080, 0x0)
	/usr/local/go/src/testing/testing.go:1199 +0x300
main.main()
	_testmain.go:168 +0x224

goroutine 40 [chan receive]:
github.com/tidwall/buntdb.(*DB).backgroundManager(0xc000142210)
	/Users/olone/playground/buntdb/buntdb.go:536 +0x2c7
created by github.com/tidwall/buntdb.Open
	/Users/olone/playground/buntdb/buntdb.go:171 +0x640

goroutine 14 [chan receive]:
github.com/tidwall/buntdb.(*DB).backgroundManager(0xc0001422c0)
	/Users/olone/playground/buntdb/buntdb.go:536 +0x2c7
created by github.com/tidwall/buntdb.Open
	/Users/olone/playground/buntdb/buntdb.go:171 +0x640

goroutine 16 [chan receive]:
github.com/tidwall/buntdb.(*DB).backgroundManager(0xc000142370)
	/Users/olone/playground/buntdb/buntdb.go:536 +0x2c7
created by github.com/tidwall/buntdb.Open
	/Users/olone/playground/buntdb/buntdb.go:171 +0x640

goroutine 50 [chan receive]:
github.com/tidwall/buntdb.(*DB).backgroundManager(0xc000142420)
	/Users/olone/playground/buntdb/buntdb.go:536 +0x2c7
created by github.com/tidwall/buntdb.Open
	/Users/olone/playground/buntdb/buntdb.go:171 +0x640

goroutine 52 [chan receive]:
github.com/tidwall/buntdb.(*DB).backgroundManager(0xc0001424d0)
	/Users/olone/playground/buntdb/buntdb.go:536 +0x2c7
created by github.com/tidwall/buntdb.Open
	/Users/olone/playground/buntdb/buntdb.go:171 +0x640

goroutine 54 [chan receive]:
github.com/tidwall/buntdb.(*DB).backgroundManager(0xc000142580)
	/Users/olone/playground/buntdb/buntdb.go:536 +0x2c7
created by github.com/tidwall/buntdb.Open
	/Users/olone/playground/buntdb/buntdb.go:171 +0x640

goroutine 42 [chan receive]:
github.com/tidwall/buntdb.(*DB).backgroundManager(0xc0004c4000)
	/Users/olone/playground/buntdb/buntdb.go:536 +0x2c7
created by github.com/tidwall/buntdb.Open
	/Users/olone/playground/buntdb/buntdb.go:171 +0x640

goroutine 43 [chan receive]:
github.com/tidwall/buntdb.(*DB).backgroundManager(0xc0004c40b0)
	/Users/olone/playground/buntdb/buntdb.go:536 +0x2c7
created by github.com/tidwall/buntdb.Open
	/Users/olone/playground/buntdb/buntdb.go:171 +0x640

goroutine 24 [chan receive]:
github.com/tidwall/buntdb.(*DB).backgroundManager(0xc00012a000)
	/Users/olone/playground/buntdb/buntdb.go:536 +0x2c7
created by github.com/tidwall/buntdb.Open
	/Users/olone/playground/buntdb/buntdb.go:171 +0x640

goroutine 26 [chan receive]:
github.com/tidwall/buntdb.(*DB).backgroundManager(0xc00012a0b0)
	/Users/olone/playground/buntdb/buntdb.go:536 +0x2c7
created by github.com/tidwall/buntdb.Open
	/Users/olone/playground/buntdb/buntdb.go:171 +0x640

goroutine 28 [chan receive]:
github.com/tidwall/buntdb.(*DB).backgroundManager(0xc00012a160)
	/Users/olone/playground/buntdb/buntdb.go:536 +0x2c7
created by github.com/tidwall/buntdb.Open
	/Users/olone/playground/buntdb/buntdb.go:171 +0x640
FAIL	github.com/tidwall/buntdb	5.555s
FAIL

Save index operations

THE AOF saves the set and del operations. However, the index creation operations are not saved. When a program restarts it's hard to reproduce the previous indexes.

Does pattern del disk val supported?

look source code, i find if delete btree value.program append del value in disk. long time! disk size very big!I think use pattern handle update and del.

Question: Retrieve by index

How to select data for condition - json field absent ?

    db.CreateIndex("age", "*", buntdb.IndexJSON("age"))
       tx.Set("5", `{"name":{"first":"Alan"},"age":28}`, nil)
        tx.Set("6", `{"name":{"first":"Dred"}}`, nil)

How to get number key=6 by age == nil?

And second question how to get key=5 by exact value age=28 ?

documentation mark and question

Hi,

2D point: [10 15 12]
this will be
2D point: [10 15].

Question: Is it possible/correct to use the 2D point spatial index as data time range?
For example, [startTime stopTime] UnixNano --> int64 as XY.

Q: why strings?

I'm just curious as to why keys and values are strings and not byte slices like it is more common with other Go databases/kv stores like Badger or Bolt?

Looking at the code I see you are using the first character of a new line in the db file as metadata info which I think can be easily done with bytes as well.

Is this because SummitDB and GJSON, SJSON were planned beforehand or is there a different reason for this decision?

data expiration

It seems like the expiration only works as long as the program does not exit before the expiration.

If I set a key to expire in 10 seconds and the program finishes in 5 I can run it as many times as I want without the expiration ever happening. Is this intentional?

Performance on large datasets

How does buntdb perform on data too large to fit into ram? would i have good performance with a dataset of over 5gb (mongodb dump), on a 500mb or 1gig ram server?

I'm currently evaluating buntdb over boltdb. As replacement for a system that currently runs on mongodb. I intend to use bleve search as well for indexing and search. Can I query with an array of keys? or get each key from the array individually?

Advice? Slow JSON unmarshalling because of string -> []bytes

I wonder if anyone can offer any advice.

I've been doing extensive benchmarking of different Go KV stores for an ERP application. I am saving millions of 'transactions' serialised with JSON, reading them back, deserialising and running some aggregation operations.

I was comparing Bunt to BadgerDB and finding that Badger was 4x faster for my benchmark for reading and aggregating, which seemed odd to me.

After a little digging, I realised that if I removed the JSON unmarshalling step, Bunt became 2x faster than Badger. Obviously I am using the same JSON unmarshalling technique in both cases.

The problem lies in the fact that Bunt returns strings whereas Badger returns []byte. This introduces another step in the deserialisation for Bunt json.Unmarshal([]byte(value), &order) which is what is causing the slowdown on the Bunt benchmark.

Ive Googled about a bit and understand that the string -> []byte conversion is costly, and have come across some solutions involving unsafe, but haven't been able to get them to work for me.

Has anyone tackled this issue and come up with a good solution. BadgerDB is really nice, but Bunt's indexing functionality is just what I'm looking for and would really love to choose it for this project. I can see that the raw iteration speed is faster than Badger - its just this string issue thats causing the slower aggregation.

Is it in-memory db?

I worked with boltdb,it's like Buntdb. As mentioned in Documentation buntdb is an in-memory database(while boltdb isn't).
So can it manage write(on-disk) operation faster than disk memory DBs?(Does it use any in-memory middle cache and write queue?)

How to save one key with multiple values?

Hello,

I have an XML of this structure and I want to transfer its data to Bunt DB

<MonitoringApps>
    <VD Id="XYZ">
        <ClientId>123456789</ClientId>
        <ClientSecret>0987654321</ClientSecret>
    </VD>
</MonitoringApps>

As it is shown per VD id we have one client id and one secret. Is there any way to save the VD id as key and others as 2 values of it in the DB.

Auto Shrink dont get triggered

Hi,

I have a program that performs some crawling, store and update data in buntdb and stops, after a few minutes a crontab launch de program again.

I don't know why but the database size grows and grows, containing some keys duplicated more than 50 times (Ending in multiple GB values completely full of duplicates).

There's any way I can force BuntDB to shrink the file at the end of every execution of the program or something like this?

Delete inside iterator

tx.AscendKeys("object:*", func(k, v string) bool {
	if someCondition(k) == true {
		if _, err = tx.Delete(k); err != nil {

                            // got: "tx is iterating" error here

			return false // break
		}
	}
	return true // continue
})

Is there a way to Delete inside iterator?

How to prevent the duplicated data with the same key?

I'm playing around with BuntDB with this code (and executed it few times):

db.Update(func(tx *buntdb.Tx) error {
    _, _, err := tx.Set("token:ABCDEF", "Test", nil)
    return err
})

And I opened the data.db file I saw this.

*3
$3
set
$38
token:ABCDEF
$31
Test
*3
$3
set
$38
token:ABCDEF
$31
Test
*3
$3
set
$38
token:ABCDEF
$31
Test
*3
$3
set
$38
token:ABCDEF
$31
Test

Why would something like this happened? I thought BuntDB will overwrite the key, value but it just added another record.

Do I have to check the key does exist or not before I Set the key? If so, should I use Get to check it before I Set it?

How to store and retrieve a tree?

Hi,
I have items that are linked in a hierarchical structure. Any item can have zero or more parents(or be a parent to zero or more children).. ie. one-to-many relationships. I need to be able retrieve all parents for a child item up to the upper most item(s).

How should I set this into Bunt and how would I go about retrieving the data?

--
I asked because of the support of rtrees but I think this db is not graph-friendly.

how to save the DB

I'm unable to call save function for making my DB persistent. getting an error in io.Writer

panic: runtime error: index out of range

Hi, when try to ran this code:

package main

import (
    "fmt"
    "log"
    "math/rand"
    "time"

    "github.com/tidwall/buntdb"
)

var count = 1000

func init() {
    rand.Seed(time.Now().UnixNano())
}

func main() {
    db, err := buntdb.Open("data.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    db.CreateIndex("ages", "user:*:age", buntdb.IndexInt)

    for i := 0; i < 10; i++ {

        fmt.Println("iteration: ", i)

        db.Update(func(tx *buntdb.Tx) error {
            for j := 0; j < count; j++ {
                key := fmt.Sprintf("user:%d:age", j)
                val := fmt.Sprintf("%d", rand.Intn(100))
                tx.Set(key, val, nil)
            }
            return nil
        })

        db.Update(func(tx *buntdb.Tx) error {
            tx.Ascend("ages", func(key, val string) bool {
                _, err := tx.Delete(key)
                if err != nil {
                    log.Fatal(err)
                }
                return true
            })
            return nil
        })

    }

}

I've got a panic on different iteration

panic: runtime error: index out of range

goroutine 1 [running]:
panic(0x50c8a0, 0xc42000a1a0)
        /home/theaidem/.envirius/envs/develope/go/src/runtime/panic.go:500 +0x1a1
github.com/tidwall/btree.(*node).iterate(0xc4200127c0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x520100, 0xc420043d40, 0x4fa280, 0xc4200161e0, ...)
        /home/theaidem/work/golang/sandbox/buntdb_ex/src/github.com/tidwall/btree/btree.go:459 +0x743
github.com/tidwall/btree.(*BTree).Ascend(0xc420014e70, 0xc420043d40)
        /home/theaidem/work/golang/sandbox/buntdb_ex/src/github.com/tidwall/btree/btree.go:637 +0x88
github.com/tidwall/buntdb.(*Tx).scan(0xc420010540, 0xc420000000, 0x52d7f4, 0x4, 0x0, 0x0, 0x0, 0x0, 0xc420043df8, 0xc420043e08, ...)
        /home/theaidem/work/golang/sandbox/buntdb_ex/src/github.com/tidwall/buntdb/buntdb.go:1347 +0x317
github.com/tidwall/buntdb.(*Tx).Ascend(0xc420010540, 0x52d7f4, 0x4, 0xc420043df8, 0xc420043e20, 0x46f040)
        /home/theaidem/work/golang/sandbox/buntdb_ex/src/github.com/tidwall/buntdb/buntdb.go:1434 +0x80
main.main.func2(0xc420010540, 0x53c298, 0xc420010540)
        /home/theaidem/work/golang/sandbox/buntdb_ex/main.go:47 +0x5b
github.com/tidwall/buntdb.(*DB).managed(0xc42008a000, 0x1, 0x53c2b0, 0x0, 0x0)
        /home/theaidem/work/golang/sandbox/buntdb_ex/src/github.com/tidwall/buntdb/buntdb.go:869 +0xf7
github.com/tidwall/buntdb.(*DB).Update(0xc42008a000, 0x53c2b0, 0x0, 0x0)
        /home/theaidem/work/golang/sandbox/buntdb_ex/src/github.com/tidwall/buntdb/buntdb.go:892 +0x3a
main.main()
        /home/theaidem/work/golang/sandbox/buntdb_ex/main.go:49 +0x244
exit status 2

my go version is 1.7
not sure, probably the issue relate tidwall/btree package
any ideas?

Shrink, panic to many open files

~40 recording per seconds
small data (max 1Kb), raw json, without indexes
25/70 write and read

init code

db, err := buntdb.Open("_workspace/_cache.db")
	if err != nil {
		return
	}
	db.SetConfig(buntdb.Config{
		SyncPolicy:         buntdb.EverySecond,
	})

log error

panic: open _workspace/_cache.db: too many open files

goroutine 39 [running]:
github.com/tidwall/buntdb.(*DB).Shrink.func4(0xc4200e26e0, 0xc420172480, 0x14, 0xab24, 0xc43805e2d8, 0xc439733920, 0x18, 0x0, 0x0)
	/go/src/github.com/tidwall/buntdb/buntdb.go:738 +0x326
github.com/tidwall/buntdb.(*DB).Shrink(0xc4200e26e0, 0x0, 0x0)
	/go/src/github.com/tidwall/buntdb/buntdb.go:746 +0x2db
github.com/tidwall/buntdb.(*DB).backgroundManager(0xc4200e26e0)
	/go/src/github.com/tidwall/buntdb/buntdb.go:602 +0x247
created by github.com/tidwall/buntdb.Open
	/go/src/github.com/tidwall/buntdb/buntdb.go:165 +0x206

real	1m46.244s
user	0m34.317s
sys	0m2.755s

Why does invalid database appear?

Hello, I found that my database is broken. There is only \r at the end of a line, so an invalid database error is reported. I would like to ask why this is the case. Will it happen when multiple processes are open? Thank you!

Can't get AscendGreaterOrEqual to work.

I have created an index with:
db.CreateIndex("idx", "idx:*", buntdb.IndexInt)
Then I set values with something like:

idx:=1
idxs := fmt.Sprintf("%d", idx)
_, _, err = tx.Set("idx:"+idxs, msg, nil)

Finally I try to retrieve values with:

since:= 1
err = dht.db.View(func(tx *buntdb.Tx) error {
	err = tx.AscendGreaterOrEqual("idx", string(since), func(key, value string) bool {
		log.Debugf("Got key:%s  and val:%s", key, value)
		return true
	})
	return err
})

if since = 1 or 0 then I get back all entries, but if since is > 1 I still get back all values! What am I doing wrong? Thanks! I.e. it doesn't actually seem to retrieve a subset.

Collate index invalid

hi , I used the collate index, but the result is not what I expected.
can anyone help me? Thanks!

db, _ := buntdb.Open(":memory:")
	db.Update(func(tx *buntdb.Tx) error {
		tx.CreateIndex("last_name", "*", collate.IndexJSON("ENGLISH_CS", "name.last"))
		tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":"38"}`, nil)
		tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":"47"}`, nil)
		tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":"52"}`, nil)
		tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":"28"}`, nil)
		tx.Set("5", `{"name":{"first":"Jack","last":"Ma"},"age":"20"}`, nil)
		tx.Set("6", `{"name":{"first":"Blues","last":"Li"},"age":"31"}`, nil)
		tx.Set("7", `{"name":{"first":"Pony","last":"Ma"},"age":"3"}`, nil)
		tx.Set("8", `{"name":{"first":"Robin","last":"Li"},"age":"2"}`, nil)
		tx.Set("9", `{"name":{"first":"Larry","last":"Liu"},"age":""}`, nil)
		return nil
	})
	db.View(func(tx *buntdb.Tx) error {
		fmt.Println("---------------------")
		tx.Ascend("last_name", func(key, value string) bool {
			fmt.Printf("%s: %s\n", key, value)
			return true
		})
		return nil
	})

result:

-------------------------
4: {"name":{"first":"Alan","last":"Cooper"},"age":"28"}
6: {"name":{"first":"Blues","last":"Li"},"age":"31"}
3: {"name":{"first":"Carol","last":"Anderson"},"age":"52"}
5: {"name":{"first":"Jack","last":"Ma"},"age":"20"}
2: {"name":{"first":"Janet","last":"Prichard"},"age":"47"}
9: {"name":{"first":"Larry","last":"Liu"},"age":""}
7: {"name":{"first":"Pony","last":"Ma"},"age":"3"}
8: {"name":{"first":"Robin","last":"Li"},"age":"2"}
1: {"name":{"first":"Tom","last":"Johnson"},"age":"38"}

DB server basing buntdb

Would it be a good idea to build an web wrap for buntdb for using buntdb by multiple consumers?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.