• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..14-Nov-2021-

LICENSEH A D14-Nov-20211.1 KiB2116

README.mdH A D14-Nov-202120.2 KiB633486

buntdb.goH A D14-Nov-202169 KiB2,3711,654

README.md

1<p align="center">
2<img
3    src="logo.png"
4    width="307" height="150" border="0" alt="BuntDB">
5<br>
6<a href="https://godoc.org/github.com/tidwall/buntdb"><img src="https://img.shields.io/badge/go-documentation-blue.svg?style=flat-square" alt="Godoc"></a>
7<a href="https://github.com/tidwall/buntdb/blob/master/LICENSE"><img src="https://img.shields.io/github/license/tidwall/buntdb.svg?style=flat-square" alt="LICENSE"></a>
8</p>
9
10BuntDB is a low-level, in-memory, key/value store in pure Go.
11It persists to disk, is ACID compliant, and uses locking for multiple
12readers and a single writer. It supports custom indexes and geospatial
13data. It's ideal for projects that need a dependable database and favor
14speed over data size.
15
16Features
17========
18
19- In-memory database for [fast reads and writes](#performance)
20- Embeddable with a [simple API](https://godoc.org/github.com/tidwall/buntdb)
21- [Spatial indexing](#spatial-indexes) for up to 20 dimensions; Useful for Geospatial data
22- Index fields inside [JSON](#json-indexes) documents
23- [Collate i18n Indexes](#collate-i18n-indexes) using the optional [collate package](https://github.com/tidwall/collate)
24- Create [custom indexes](#custom-indexes) for any data type
25- Support for [multi value indexes](#multi-value-index); Similar to a SQL multi column index
26- [Built-in types](#built-in-types) that are easy to get up & running; String, Uint, Int, Float
27- Flexible [iteration](#iterating) of data; ascending, descending, and ranges
28- [Durable append-only file](#append-only-file) format for persistence
29- Option to evict old items with an [expiration](#data-expiration) TTL
30- ACID semantics with locking [transactions](#transactions) that support rollbacks
31
32
33Getting Started
34===============
35
36## Installing
37
38To start using BuntDB, install Go and run `go get`:
39
40```sh
41$ go get -u github.com/tidwall/buntdb
42```
43
44This will retrieve the library.
45
46
47## Opening a database
48
49The primary object in BuntDB is a `DB`. To open or create your
50database, use the `buntdb.Open()` function:
51
52```go
53package main
54
55import (
56	"log"
57
58	"github.com/tidwall/buntdb"
59)
60
61func main() {
62	// Open the data.db file. It will be created if it doesn't exist.
63	db, err := buntdb.Open("data.db")
64	if err != nil {
65		log.Fatal(err)
66	}
67	defer db.Close()
68
69	...
70}
71```
72
73It's also possible to open a database that does not persist to disk by using `:memory:` as the path of the file.
74
75```go
76buntdb.Open(":memory:") // Open a file that does not persist to disk.
77```
78
79## Transactions
80All 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.
81
82Transactions 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.
83
84When 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.
85
86### Read-only Transactions
87A 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.
88
89```go
90err := db.View(func(tx *buntdb.Tx) error {
91	...
92	return nil
93})
94```
95
96### Read/write Transactions
97A 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.
98
99```go
100err := db.Update(func(tx *buntdb.Tx) error {
101	...
102	return nil
103})
104```
105
106## Setting and getting key/values
107
108To set a value you must open a read/write transaction:
109
110```go
111err := db.Update(func(tx *buntdb.Tx) error {
112	_, _, err := tx.Set("mykey", "myvalue", nil)
113	return err
114})
115```
116
117
118To get the value:
119
120```go
121err := db.View(func(tx *buntdb.Tx) error {
122	val, err := tx.Get("mykey")
123	if err != nil{
124		return err
125	}
126	fmt.Printf("value is %s\n", val)
127	return nil
128})
129```
130
131Getting non-existent values will cause an `ErrNotFound` error.
132
133### Iterating
134All keys/value pairs are ordered in the database by the key. To iterate over the keys:
135
136```go
137err := db.View(func(tx *buntdb.Tx) error {
138	err := tx.Ascend("", func(key, value string) bool {
139		fmt.Printf("key: %s, value: %s\n", key, value)
140	})
141	return err
142})
143```
144
145There is also `AscendGreaterOrEqual`, `AscendLessThan`, `AscendRange`, `AscendEqual`, `Descend`, `DescendLessOrEqual`, `DescendGreaterThan`, `DescendRange`, and `DescendEqual`. Please see the [documentation](https://godoc.org/github.com/tidwall/buntdb) for more information on these functions.
146
147
148## Custom Indexes
149Initially all data is stored in a single [B-tree](https://en.wikipedia.org/wiki/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](#iterating) over the keys. Feel free to peruse the [B-tree implementation](https://github.com/tidwall/btree).
150
151You can also create custom indexes that allow for ordering and [iterating](#iterating) over values. A custom index also uses a B-tree, but it's more flexible because it allows for custom ordering.
152
153For example, let's say you want to create an index for ordering names:
154
155```go
156db.CreateIndex("names", "*", buntdb.IndexString)
157```
158
159This 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
160
161Now you can add various names:
162
163```go
164db.Update(func(tx *buntdb.Tx) error {
165	tx.Set("user:0:name", "tom", nil)
166	tx.Set("user:1:name", "Randi", nil)
167	tx.Set("user:2:name", "jane", nil)
168	tx.Set("user:4:name", "Janet", nil)
169	tx.Set("user:5:name", "Paula", nil)
170	tx.Set("user:6:name", "peter", nil)
171	tx.Set("user:7:name", "Terri", nil)
172	return nil
173})
174```
175
176Finally you can iterate over the index:
177
178```go
179db.View(func(tx *buntdb.Tx) error {
180	tx.Ascend("names", func(key, val string) bool {
181	fmt.Printf(buf, "%s %s\n", key, val)
182		return true
183	})
184	return nil
185})
186```
187The output should be:
188```
189user:2:name jane
190user:4:name Janet
191user:5:name Paula
192user:6:name peter
193user:1:name Randi
194user:7:name Terri
195user:0:name tom
196```
197
198The pattern parameter can be used to filter on keys like this:
199
200```go
201db.CreateIndex("names", "user:*", buntdb.IndexString)
202```
203
204Now only items with keys that have the prefix `user:` will be added to the `names` index.
205
206
207### Built-in types
208Along with `IndexString`, there is also `IndexInt`, `IndexUint`, and `IndexFloat`.
209These are built-in types for indexing. You can choose to use these or create your own.
210
211So to create an index that is numerically ordered on an age key, we could use:
212
213```go
214db.CreateIndex("ages", "user:*:age", buntdb.IndexInt)
215```
216
217And then add values:
218
219```go
220db.Update(func(tx *buntdb.Tx) error {
221	tx.Set("user:0:age", "35", nil)
222	tx.Set("user:1:age", "49", nil)
223	tx.Set("user:2:age", "13", nil)
224	tx.Set("user:4:age", "63", nil)
225	tx.Set("user:5:age", "8", nil)
226	tx.Set("user:6:age", "3", nil)
227	tx.Set("user:7:age", "16", nil)
228	return nil
229})
230```
231
232```go
233db.View(func(tx *buntdb.Tx) error {
234	tx.Ascend("ages", func(key, val string) bool {
235	fmt.Printf(buf, "%s %s\n", key, val)
236		return true
237	})
238	return nil
239})
240```
241
242The output should be:
243```
244user:6:age 3
245user:5:age 8
246user:2:age 13
247user:7:age 16
248user:0:age 35
249user:1:age 49
250user:4:age 63
251```
252
253## Spatial Indexes
254BuntDB has support for spatial indexes by storing rectangles in an [R-tree](https://en.wikipedia.org/wiki/R-tree). An R-tree is organized in a similar manner as a [B-tree](https://en.wikipedia.org/wiki/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.
255
256To create a spatial index use the `CreateSpatialIndex` function:
257
258```go
259db.CreateSpatialIndex("fleet", "fleet:*:pos", buntdb.IndexRect)
260```
261
262Then `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](https://en.wikipedia.org/wiki/Well-known_text) or [GeoJSON](http://geojson.org/).
263
264To add some lon,lat points to the `fleet` index:
265
266```go
267db.Update(func(tx *buntdb.Tx) error {
268	tx.Set("fleet:0:pos", "[-115.567 33.532]", nil)
269	tx.Set("fleet:1:pos", "[-116.671 35.735]", nil)
270	tx.Set("fleet:2:pos", "[-113.902 31.234]", nil)
271	return nil
272})
273```
274
275And then you can run the `Intersects` function on the index:
276
277```go
278db.View(func(tx *buntdb.Tx) error {
279	tx.Intersects("fleet", "[-117 30],[-112 36]", func(key, val string) bool {
280		...
281		return true
282	})
283	return nil
284})
285```
286
287This will get all three positions.
288
289### k-Nearest Neighbors
290
291Use the `Nearby` function to get all the positions in order of nearest to farthest :
292
293```go
294db.View(func(tx *buntdb.Tx) error {
295	tx.Nearby("fleet", "[-113 33]", func(key, val string, dist float64) bool {
296		...
297		return true
298	})
299	return nil
300})
301```
302
303### Spatial bracket syntax
304
305The 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`.
306
307- **2D rectangle:** `[10 15],[20 25]`
308*Min XY: "10x15", Max XY: "20x25"*
309
310- **3D rectangle:** `[10 15 12],[20 25 18]`
311*Min XYZ: "10x15x12", Max XYZ: "20x25x18"*
312
313- **2D point:** `[10 15]`
314*XY: "10x15"*
315
316- **LonLat point:** `[-112.2693 33.5123]`
317*LatLon: "33.5123 -112.2693"*
318
319- **LonLat bounding box:** `[-112.26 33.51],[-112.18 33.67]`
320*Min LatLon: "33.51 -112.26", Max LatLon: "33.67 -112.18"*
321
322**Notice:** The longitude is the Y axis and is on the left, and latitude is the X axis and is on the right.
323
324You can also represent `Infinity` by using `-inf` and `+inf`.
325For example, you might have the following points (`[X Y M]` where XY is a point and M is a timestamp):
326```
327[3 9 1]
328[3 8 2]
329[4 8 3]
330[4 7 4]
331[5 7 5]
332[5 6 6]
333```
334
335You can then do a search for all points with `M` between 2-4 by calling `Intersects`.
336
337```go
338tx.Intersects("points", "[-inf -inf 2],[+inf +inf 4]", func(key, val string) bool {
339	println(val)
340	return true
341})
342```
343
344Which will return:
345
346```
347[3 8 2]
348[4 8 3]
349[4 7 4]
350```
351
352## JSON Indexes
353Indexes can be created on individual fields inside JSON documents. BuntDB uses [GJSON](https://github.com/tidwall/gjson) under the hood.
354
355For example:
356
357```go
358package main
359
360import (
361	"fmt"
362
363	"github.com/tidwall/buntdb"
364)
365
366func main() {
367	db, _ := buntdb.Open(":memory:")
368	db.CreateIndex("last_name", "*", buntdb.IndexJSON("name.last"))
369	db.CreateIndex("age", "*", buntdb.IndexJSON("age"))
370	db.Update(func(tx *buntdb.Tx) error {
371		tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil)
372		tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil)
373		tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil)
374		tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil)
375		return nil
376	})
377	db.View(func(tx *buntdb.Tx) error {
378		fmt.Println("Order by last name")
379		tx.Ascend("last_name", func(key, value string) bool {
380			fmt.Printf("%s: %s\n", key, value)
381			return true
382		})
383		fmt.Println("Order by age")
384		tx.Ascend("age", func(key, value string) bool {
385			fmt.Printf("%s: %s\n", key, value)
386			return true
387		})
388		fmt.Println("Order by age range 30-50")
389		tx.AscendRange("age", `{"age":30}`, `{"age":50}`, func(key, value string) bool {
390			fmt.Printf("%s: %s\n", key, value)
391			return true
392		})
393		return nil
394	})
395}
396```
397
398Results:
399
400```
401Order by last name
4023: {"name":{"first":"Carol","last":"Anderson"},"age":52}
4034: {"name":{"first":"Alan","last":"Cooper"},"age":28}
4041: {"name":{"first":"Tom","last":"Johnson"},"age":38}
4052: {"name":{"first":"Janet","last":"Prichard"},"age":47}
406
407Order by age
4084: {"name":{"first":"Alan","last":"Cooper"},"age":28}
4091: {"name":{"first":"Tom","last":"Johnson"},"age":38}
4102: {"name":{"first":"Janet","last":"Prichard"},"age":47}
4113: {"name":{"first":"Carol","last":"Anderson"},"age":52}
412
413Order by age range 30-50
4141: {"name":{"first":"Tom","last":"Johnson"},"age":38}
4152: {"name":{"first":"Janet","last":"Prichard"},"age":47}
416```
417
418## Multi Value Index
419With BuntDB it's possible to join multiple values on a single index.
420This is similar to a [multi column index](http://dev.mysql.com/doc/refman/5.7/en/multiple-column-indexes.html) in a traditional SQL database.
421
422In this example we are creating a multi value index on "name.last" and "age":
423
424```go
425db, _ := buntdb.Open(":memory:")
426db.CreateIndex("last_name_age", "*", buntdb.IndexJSON("name.last"), buntdb.IndexJSON("age"))
427db.Update(func(tx *buntdb.Tx) error {
428	tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil)
429	tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil)
430	tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil)
431	tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil)
432	tx.Set("5", `{"name":{"first":"Sam","last":"Anderson"},"age":51}`, nil)
433	tx.Set("6", `{"name":{"first":"Melinda","last":"Prichard"},"age":44}`, nil)
434	return nil
435})
436db.View(func(tx *buntdb.Tx) error {
437	tx.Ascend("last_name_age", func(key, value string) bool {
438		fmt.Printf("%s: %s\n", key, value)
439		return true
440	})
441	return nil
442})
443
444// Output:
445// 5: {"name":{"first":"Sam","last":"Anderson"},"age":51}
446// 3: {"name":{"first":"Carol","last":"Anderson"},"age":52}
447// 4: {"name":{"first":"Alan","last":"Cooper"},"age":28}
448// 1: {"name":{"first":"Tom","last":"Johnson"},"age":38}
449// 6: {"name":{"first":"Melinda","last":"Prichard"},"age":44}
450// 2: {"name":{"first":"Janet","last":"Prichard"},"age":47}
451```
452
453## Descending Ordered Index
454Any index can be put in descending order by wrapping it's less function with `buntdb.Desc`.
455
456```go
457db.CreateIndex("last_name_age", "*",
458    buntdb.IndexJSON("name.last"),
459    buntdb.Desc(buntdb.IndexJSON("age")),
460)
461```
462
463This will create a multi value index where the last name is ascending and the age is descending.
464
465## Collate i18n Indexes
466
467Using the external [collate package](https://github.com/tidwall/collate) it's possible to create
468indexes that are sorted by the specified language. This is similar to the [SQL COLLATE keyword](https://msdn.microsoft.com/en-us/library/ms174596.aspx) found in traditional databases.
469
470To install:
471
472```
473go get -u github.com/tidwall/collate
474```
475
476For example:
477
478```go
479import "github.com/tidwall/collate"
480
481// To sort case-insensitive in French.
482db.CreateIndex("name", "*", collate.IndexString("FRENCH_CI"))
483
484// To specify that numbers should sort numerically ("2" < "12")
485// and use a comma to represent a decimal point.
486db.CreateIndex("amount", "*", collate.IndexString("FRENCH_NUM"))
487```
488
489There's also support for Collation on JSON indexes:
490
491```go
492db.CreateIndex("last_name", "*", collate.IndexJSON("CHINESE_CI", "name.last"))
493```
494
495Check out the [collate project](https://github.com/tidwall/collate) for more information.
496
497## Data Expiration
498Items can be automatically evicted by using the `SetOptions` object in the `Set` function to set a `TTL`.
499
500```go
501db.Update(func(tx *buntdb.Tx) error {
502	tx.Set("mykey", "myval", &buntdb.SetOptions{Expires:true, TTL:time.Second})
503	return nil
504})
505```
506
507Now `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.
508
509## Delete while iterating
510BuntDB does not currently support deleting a key while in the process of iterating.
511As a workaround you'll need to delete keys following the completion of the iterator.
512
513```go
514var delkeys []string
515tx.AscendKeys("object:*", func(k, v string) bool {
516	if someCondition(k) == true {
517		delkeys = append(delkeys, k)
518	}
519	return true // continue
520})
521for _, k := range delkeys {
522	if _, err = tx.Delete(k); err != nil {
523		return err
524	}
525}
526```
527
528## Append-only File
529
530BuntDB uses an AOF (append-only file) which is a log of all database changes that occur from operations like `Set()` and `Delete()`.
531
532The format of this file looks like:
533```
534set key:1 value1
535set key:2 value2
536set key:1 value3
537del key:2
538...
539```
540
541When the database opens again, it will read back the aof file and process each command in exact order.
542This read process happens one time when the database opens.
543From there on the file is only appended.
544
545As you may guess this log file can grow large over time.
546There's a background routine that automatically shrinks the log file when it gets too large.
547There is also a `Shrink()` function which will rewrite the aof file so that it contains only the items in the database.
548The shrink operation does not lock up the database so read and write transactions can continue while shrinking is in process.
549
550### Durability and fsync
551
552By default BuntDB executes an `fsync` once every second on the [aof file](#append-only-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`.
553
554The `Config.SyncPolicy` has the following options:
555
556- `Never` - fsync is managed by the operating system, less safe
557- `EverySecond` - fsync every second, fast and safer, this is the default
558- `Always` - fsync after every write, very durable, slower
559
560## Config
561
562Here are some configuration options that can be use to change various behaviors of the database.
563
564- **SyncPolicy** adjusts how often the data is synced to disk. This value can be Never, EverySecond, or Always. Default is EverySecond.
565- **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.
566- **AutoShrinkMinSize** defines the minimum size of the aof file before an automatic shrink can occur. Default is 32MB.
567- **AutoShrinkDisabled** turns off automatic background shrinking. Default is false.
568
569To update the configuration you should call `ReadConfig` followed by `SetConfig`. For example:
570
571```go
572
573var config buntdb.Config
574if err := db.ReadConfig(&config); err != nil{
575	log.Fatal(err)
576}
577if err := db.SetConfig(config); err != nil{
578	log.Fatal(err)
579}
580```
581
582## Performance
583
584How fast is BuntDB?
585
586Here are some example [benchmarks](https://github.com/tidwall/raft-buntdb#raftstore-performance-comparison) when using BuntDB in a Raft Store implementation.
587
588You can also run the standard Go benchmark tool from the project root directory:
589
590```
591go test --bench=.
592```
593
594### BuntDB-Benchmark
595
596There's a [custom utility](https://github.com/tidwall/buntdb-benchmark) that was created specifically for benchmarking BuntDB.
597
598*These are the results from running the benchmarks on a MacBook Pro 15" 2.8 GHz Intel Core i7:*
599
600```
601$ buntdb-benchmark -q
602GET: 4609604.74 operations per second
603SET: 248500.33 operations per second
604ASCEND_100: 2268998.79 operations per second
605ASCEND_200: 1178388.14 operations per second
606ASCEND_400: 679134.20 operations per second
607ASCEND_800: 348445.55 operations per second
608DESCEND_100: 2313821.69 operations per second
609DESCEND_200: 1292738.38 operations per second
610DESCEND_400: 675258.76 operations per second
611DESCEND_800: 337481.67 operations per second
612SPATIAL_SET: 134824.60 operations per second
613SPATIAL_INTERSECTS_100: 939491.47 operations per second
614SPATIAL_INTERSECTS_200: 561590.40 operations per second
615SPATIAL_INTERSECTS_400: 306951.15 operations per second
616SPATIAL_INTERSECTS_800: 159673.91 operations per second
617```
618
619To install this utility:
620
621```
622go get github.com/tidwall/buntdb-benchmark
623```
624
625
626
627## Contact
628Josh Baker [@tidwall](http://twitter.com/tidwall)
629
630## License
631
632BuntDB source code is available under the MIT [License](/LICENSE).
633