Signed-off-by: Alessandro Boch <aboch@docker.com>
| ... | ... |
@@ -88,7 +88,7 @@ clone git github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1 |
| 88 | 88 |
clone git github.com/coreos/etcd 3a49cbb769ebd8d1dd25abb1e83386e9883a5707 |
| 89 | 89 |
clone git github.com/ugorji/go f1f1a805ed361a0e078bb537e4ea78cd37dcf065 |
| 90 | 90 |
clone git github.com/hashicorp/consul v0.5.2 |
| 91 |
-clone git github.com/boltdb/bolt v1.2.1 |
|
| 91 |
+clone git github.com/boltdb/bolt fff57c100f4dea1905678da7e90d92429dff2904 |
|
| 92 | 92 |
clone git github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7 |
| 93 | 93 |
|
| 94 | 94 |
# get graph and distribution packages |
| ... | ... |
@@ -1,4 +1,4 @@ |
| 1 |
-Bolt [](https://coveralls.io/r/boltdb/bolt?branch=master) [](https://godoc.org/github.com/boltdb/bolt)  |
|
| 1 |
+Bolt [](https://coveralls.io/r/boltdb/bolt?branch=master) [](https://godoc.org/github.com/boltdb/bolt)  |
|
| 2 | 2 |
==== |
| 3 | 3 |
|
| 4 | 4 |
Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas] |
| ... | ... |
@@ -15,11 +15,11 @@ and setting values. That's it. |
| 15 | 15 |
|
| 16 | 16 |
## Project Status |
| 17 | 17 |
|
| 18 |
-Bolt is stable and the API is fixed. Full unit test coverage and randomized |
|
| 19 |
-black box testing are used to ensure database consistency and thread safety. |
|
| 20 |
-Bolt is currently in high-load production environments serving databases as |
|
| 21 |
-large as 1TB. Many companies such as Shopify and Heroku use Bolt-backed |
|
| 22 |
-services every day. |
|
| 18 |
+Bolt is stable, the API is fixed, and the file format is fixed. Full unit |
|
| 19 |
+test coverage and randomized black box testing are used to ensure database |
|
| 20 |
+consistency and thread safety. Bolt is currently in high-load production |
|
| 21 |
+environments serving databases as large as 1TB. Many companies such as |
|
| 22 |
+Shopify and Heroku use Bolt-backed services every day. |
|
| 23 | 23 |
|
| 24 | 24 |
## Table of Contents |
| 25 | 25 |
|
| ... | ... |
@@ -209,7 +209,7 @@ and then safely close your transaction if an error is returned. This is the |
| 209 | 209 |
recommended way to use Bolt transactions. |
| 210 | 210 |
|
| 211 | 211 |
However, sometimes you may want to manually start and end your transactions. |
| 212 |
-You can use the `Tx.Begin()` function directly but **please** be sure to close |
|
| 212 |
+You can use the `DB.Begin()` function directly but **please** be sure to close |
|
| 213 | 213 |
the transaction. |
| 214 | 214 |
|
| 215 | 215 |
```go |
| ... | ... |
@@ -313,7 +313,7 @@ func (s *Store) CreateUser(u *User) error {
|
| 313 | 313 |
// Generate ID for the user. |
| 314 | 314 |
// This returns an error only if the Tx is closed or not writeable. |
| 315 | 315 |
// That can't happen in an Update() call so I ignore the error check. |
| 316 |
- id, _ = b.NextSequence() |
|
| 316 |
+ id, _ := b.NextSequence() |
|
| 317 | 317 |
u.ID = int(id) |
| 318 | 318 |
|
| 319 | 319 |
// Marshal user data into bytes. |
| ... | ... |
@@ -448,6 +448,10 @@ db.View(func(tx *bolt.Tx) error {
|
| 448 | 448 |
}) |
| 449 | 449 |
``` |
| 450 | 450 |
|
| 451 |
+Please note that keys and values in `ForEach()` are only valid while |
|
| 452 |
+the transaction is open. If you need to use a key or value outside of |
|
| 453 |
+the transaction, you must use `copy()` to copy it to another byte |
|
| 454 |
+slice. |
|
| 451 | 455 |
|
| 452 | 456 |
### Nested buckets |
| 453 | 457 |
|
| ... | ... |
@@ -557,7 +561,7 @@ if err != nil {
|
| 557 | 557 |
Bolt is able to run on mobile devices by leveraging the binding feature of the |
| 558 | 558 |
[gomobile](https://github.com/golang/mobile) tool. Create a struct that will |
| 559 | 559 |
contain your database logic and a reference to a `*bolt.DB` with a initializing |
| 560 |
-contstructor that takes in a filepath where the database file will be stored. |
|
| 560 |
+constructor that takes in a filepath where the database file will be stored. |
|
| 561 | 561 |
Neither Android nor iOS require extra permissions or cleanup from using this method. |
| 562 | 562 |
|
| 563 | 563 |
```go |
| ... | ... |
@@ -807,6 +811,7 @@ them via pull request. |
| 807 | 807 |
|
| 808 | 808 |
Below is a list of public, open source projects that use Bolt: |
| 809 | 809 |
|
| 810 |
+* [BoltDbWeb](https://github.com/evnix/boltdbweb) - A web based GUI for BoltDB files. |
|
| 810 | 811 |
* [Operation Go: A Routine Mission](http://gocode.io) - An online programming game for Golang using Bolt for user accounts and a leaderboard. |
| 811 | 812 |
* [Bazil](https://bazil.org/) - A file system that lets your data reside where it is most convenient for it to reside. |
| 812 | 813 |
* [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb. |
| ... | ... |
@@ -825,7 +830,6 @@ Below is a list of public, open source projects that use Bolt: |
| 825 | 825 |
* [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend. |
| 826 | 826 |
* [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend. |
| 827 | 827 |
* [tentacool](https://github.com/optiflows/tentacool) - REST api server to manage system stuff (IP, DNS, Gateway...) on a linux server. |
| 828 |
-* [SkyDB](https://github.com/skydb/sky) - Behavioral analytics database. |
|
| 829 | 828 |
* [Seaweed File System](https://github.com/chrislusf/seaweedfs) - Highly scalable distributed key~file system with O(1) disk read. |
| 830 | 829 |
* [InfluxDB](https://influxdata.com) - Scalable datastore for metrics, events, and real-time analytics. |
| 831 | 830 |
* [Freehold](http://tshannon.bitbucket.org/freehold/) - An open, secure, and lightweight platform for your files and data. |
| ... | ... |
@@ -842,9 +846,12 @@ Below is a list of public, open source projects that use Bolt: |
| 842 | 842 |
* [Go Report Card](https://goreportcard.com/) - Go code quality report cards as a (free and open source) service. |
| 843 | 843 |
* [Boltdb Boilerplate](https://github.com/bobintornado/boltdb-boilerplate) - Boilerplate wrapper around bolt aiming to make simple calls one-liners. |
| 844 | 844 |
* [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores. |
| 845 |
-* [Storm](https://github.com/asdine/storm) - A simple ORM around BoltDB. |
|
| 845 |
+* [Storm](https://github.com/asdine/storm) - Simple and powerful ORM for BoltDB. |
|
| 846 | 846 |
* [GoWebApp](https://github.com/josephspurrier/gowebapp) - A basic MVC web application in Go using BoltDB. |
| 847 | 847 |
* [SimpleBolt](https://github.com/xyproto/simplebolt) - A simple way to use BoltDB. Deals mainly with strings. |
| 848 | 848 |
* [Algernon](https://github.com/xyproto/algernon) - A HTTP/2 web server with built-in support for Lua. Uses BoltDB as the default database backend. |
| 849 |
+* [MuLiFS](https://github.com/dankomiocevic/mulifs) - Music Library Filesystem creates a filesystem to organise your music files. |
|
| 850 |
+* [GoShort](https://github.com/pankajkhairnar/goShort) - GoShort is a URL shortener written in Golang and BoltDB for persistent key/value storage and for routing it's using high performent HTTPRouter. |
|
| 851 |
+* [torrent](https://github.com/anacrolix/torrent) - Full-featured BitTorrent client package and utilities in Go. BoltDB is a storage backend in development. |
|
| 849 | 852 |
|
| 850 | 853 |
If you are using Bolt in a project please send a pull request to add it to the list. |
| ... | ... |
@@ -1,7 +1,28 @@ |
| 1 | 1 |
package bolt |
| 2 | 2 |
|
| 3 |
+import "unsafe" |
|
| 4 |
+ |
|
| 3 | 5 |
// maxMapSize represents the largest mmap size supported by Bolt. |
| 4 | 6 |
const maxMapSize = 0x7FFFFFFF // 2GB |
| 5 | 7 |
|
| 6 | 8 |
// maxAllocSize is the size used when creating array pointers. |
| 7 | 9 |
const maxAllocSize = 0xFFFFFFF |
| 10 |
+ |
|
| 11 |
+// Are unaligned load/stores broken on this arch? |
|
| 12 |
+var brokenUnaligned bool |
|
| 13 |
+ |
|
| 14 |
+func init() {
|
|
| 15 |
+ // Simple check to see whether this arch handles unaligned load/stores |
|
| 16 |
+ // correctly. |
|
| 17 |
+ |
|
| 18 |
+ // ARM9 and older devices require load/stores to be from/to aligned |
|
| 19 |
+ // addresses. If not, the lower 2 bits are cleared and that address is |
|
| 20 |
+ // read in a jumbled up order. |
|
| 21 |
+ |
|
| 22 |
+ // See http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka15414.html |
|
| 23 |
+ |
|
| 24 |
+ raw := [6]byte{0xfe, 0xef, 0x11, 0x22, 0x22, 0x11}
|
|
| 25 |
+ val := *(*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(&raw)) + 2)) |
|
| 26 |
+ |
|
| 27 |
+ brokenUnaligned = val != 0x11222211 |
|
| 28 |
+} |
| ... | ... |
@@ -130,9 +130,17 @@ func (b *Bucket) Bucket(name []byte) *Bucket {
|
| 130 | 130 |
func (b *Bucket) openBucket(value []byte) *Bucket {
|
| 131 | 131 |
var child = newBucket(b.tx) |
| 132 | 132 |
|
| 133 |
+ // If unaligned load/stores are broken on this arch and value is |
|
| 134 |
+ // unaligned simply clone to an aligned byte array. |
|
| 135 |
+ unaligned := brokenUnaligned && uintptr(unsafe.Pointer(&value[0]))&3 != 0 |
|
| 136 |
+ |
|
| 137 |
+ if unaligned {
|
|
| 138 |
+ value = cloneBytes(value) |
|
| 139 |
+ } |
|
| 140 |
+ |
|
| 133 | 141 |
// If this is a writable transaction then we need to copy the bucket entry. |
| 134 | 142 |
// Read-only transactions can point directly at the mmap entry. |
| 135 |
- if b.tx.writable {
|
|
| 143 |
+ if b.tx.writable && !unaligned {
|
|
| 136 | 144 |
child.bucket = &bucket{}
|
| 137 | 145 |
*child.bucket = *(*bucket)(unsafe.Pointer(&value[0])) |
| 138 | 146 |
} else {
|
| ... | ... |
@@ -329,6 +337,28 @@ func (b *Bucket) Delete(key []byte) error {
|
| 329 | 329 |
return nil |
| 330 | 330 |
} |
| 331 | 331 |
|
| 332 |
+// Sequence returns the current integer for the bucket without incrementing it. |
|
| 333 |
+func (b *Bucket) Sequence() uint64 { return b.bucket.sequence }
|
|
| 334 |
+ |
|
| 335 |
+// SetSequence updates the sequence number for the bucket. |
|
| 336 |
+func (b *Bucket) SetSequence(v uint64) error {
|
|
| 337 |
+ if b.tx.db == nil {
|
|
| 338 |
+ return ErrTxClosed |
|
| 339 |
+ } else if !b.Writable() {
|
|
| 340 |
+ return ErrTxNotWritable |
|
| 341 |
+ } |
|
| 342 |
+ |
|
| 343 |
+ // Materialize the root node if it hasn't been already so that the |
|
| 344 |
+ // bucket will be saved during commit. |
|
| 345 |
+ if b.rootNode == nil {
|
|
| 346 |
+ _ = b.node(b.root, nil) |
|
| 347 |
+ } |
|
| 348 |
+ |
|
| 349 |
+ // Increment and return the sequence. |
|
| 350 |
+ b.bucket.sequence = v |
|
| 351 |
+ return nil |
|
| 352 |
+} |
|
| 353 |
+ |
|
| 332 | 354 |
// NextSequence returns an autoincrementing integer for the bucket. |
| 333 | 355 |
func (b *Bucket) NextSequence() (uint64, error) {
|
| 334 | 356 |
if b.tx.db == nil {
|
| ... | ... |
@@ -166,12 +166,16 @@ func (f *freelist) read(p *page) {
|
| 166 | 166 |
} |
| 167 | 167 |
|
| 168 | 168 |
// Copy the list of page ids from the freelist. |
| 169 |
- ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx:count] |
|
| 170 |
- f.ids = make([]pgid, len(ids)) |
|
| 171 |
- copy(f.ids, ids) |
|
| 169 |
+ if count == 0 {
|
|
| 170 |
+ f.ids = nil |
|
| 171 |
+ } else {
|
|
| 172 |
+ ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx:count] |
|
| 173 |
+ f.ids = make([]pgid, len(ids)) |
|
| 174 |
+ copy(f.ids, ids) |
|
| 172 | 175 |
|
| 173 |
- // Make sure they're sorted. |
|
| 174 |
- sort.Sort(pgids(f.ids)) |
|
| 176 |
+ // Make sure they're sorted. |
|
| 177 |
+ sort.Sort(pgids(f.ids)) |
|
| 178 |
+ } |
|
| 175 | 179 |
|
| 176 | 180 |
// Rebuild the page cache. |
| 177 | 181 |
f.reindex() |
| ... | ... |
@@ -189,7 +193,9 @@ func (f *freelist) write(p *page) error {
|
| 189 | 189 |
|
| 190 | 190 |
// The page.count can only hold up to 64k elements so if we overflow that |
| 191 | 191 |
// number then we handle it by putting the size in the first element. |
| 192 |
- if len(ids) < 0xFFFF {
|
|
| 192 |
+ if len(ids) == 0 {
|
|
| 193 |
+ p.count = uint16(len(ids)) |
|
| 194 |
+ } else if len(ids) < 0xFFFF {
|
|
| 193 | 195 |
p.count = uint16(len(ids)) |
| 194 | 196 |
copy(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[:], ids) |
| 195 | 197 |
} else {
|
| ... | ... |
@@ -230,7 +236,7 @@ func (f *freelist) reload(p *page) {
|
| 230 | 230 |
|
| 231 | 231 |
// reindex rebuilds the free cache based on available and pending free lists. |
| 232 | 232 |
func (f *freelist) reindex() {
|
| 233 |
- f.cache = make(map[pgid]bool) |
|
| 233 |
+ f.cache = make(map[pgid]bool, len(f.ids)) |
|
| 234 | 234 |
for _, id := range f.ids {
|
| 235 | 235 |
f.cache[id] = true |
| 236 | 236 |
} |
| ... | ... |
@@ -201,6 +201,11 @@ func (n *node) write(p *page) {
|
| 201 | 201 |
} |
| 202 | 202 |
p.count = uint16(len(n.inodes)) |
| 203 | 203 |
|
| 204 |
+ // Stop here if there are no items to write. |
|
| 205 |
+ if p.count == 0 {
|
|
| 206 |
+ return |
|
| 207 |
+ } |
|
| 208 |
+ |
|
| 204 | 209 |
// Loop over each item and write it to the page. |
| 205 | 210 |
b := (*[maxAllocSize]byte)(unsafe.Pointer(&p.ptr))[n.pageElementSize()*len(n.inodes):] |
| 206 | 211 |
for i, item := range n.inodes {
|
| ... | ... |
@@ -62,6 +62,9 @@ func (p *page) leafPageElement(index uint16) *leafPageElement {
|
| 62 | 62 |
|
| 63 | 63 |
// leafPageElements retrieves a list of leaf nodes. |
| 64 | 64 |
func (p *page) leafPageElements() []leafPageElement {
|
| 65 |
+ if p.count == 0 {
|
|
| 66 |
+ return nil |
|
| 67 |
+ } |
|
| 65 | 68 |
return ((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[:] |
| 66 | 69 |
} |
| 67 | 70 |
|
| ... | ... |
@@ -72,6 +75,9 @@ func (p *page) branchPageElement(index uint16) *branchPageElement {
|
| 72 | 72 |
|
| 73 | 73 |
// branchPageElements retrieves a list of branch nodes. |
| 74 | 74 |
func (p *page) branchPageElements() []branchPageElement {
|
| 75 |
+ if p.count == 0 {
|
|
| 76 |
+ return nil |
|
| 77 |
+ } |
|
| 75 | 78 |
return ((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[:] |
| 76 | 79 |
} |
| 77 | 80 |
|