package dedo import ( "bytes" "encoding/binary" "errors" "flag" "fmt" "hash/fnv" "io" "io/fs" "io/ioutil" "log" "math/rand" "os" "runtime" "runtime/debug" "runtime/pprof" "sort" "strconv" "strings" "sync" "syscall" "time" "unicode" "unicode/utf8" "unsafe" ) type pgid uint64 // bucket represents the on-file representation of a bucket. // This is stored as the "value" of a bucket key. If the bucket is small enough, // then its root page can be stored inline in the "value", after the bucket // header. In the case of inline buckets, the "root" will be 0. type bucket struct { root pgid // page id of the bucket's root-level page sequence uint64 // monotonically incrementing, used by NextSequence() } // Bucket represents a distinct collection of key/value pairs inside the // database. Keys aren't unique across different buckets. type Bucket struct { ref *bucket tx *Tx // the associated transaction buckets map[string]*Bucket // subbucket cache page *page // inline page reference rootNode *node // materialized node for the root page. nodes map[pgid]*node // node cache } // BucketStats records statistics about resources used by a bucket. type BucketStats struct { // Page count statistics. BranchPageN int // number of logical branch pages BranchOverflowN int // number of physical branch overflow pages LeafPageN int // number of logical leaf pages LeafOverflowN int // number of physical leaf overflow pages // Tree statistics. KeyN int // number of keys/value pairs Depth int // number of levels in B+tree // Page size utilization. BranchAlloc int // bytes allocated for physical branch pages BranchInuse int // bytes actually used for branch data LeafAlloc int // bytes allocated for physical leaf pages LeafInuse int // bytes actually used for leaf data // Bucket statistics BucketN int // total number of buckets including the top bucket InlineBucketN int // total number on inlined buckets InlineBucketInuse int // bytes used for inlined buckets (also accounted for in LeafInuse) } // Cursor represents an iterator that can traverse over all key/value pairs in a bucket in sorted order. // Cursors see nested buckets with value == nil. // Cursors can be obtained from a transaction and are valid as long as the transaction is open. // // Keys and values returned from the cursor are only valid for the life of the transaction. // // Changing data while traversing with a cursor may cause it to be invalidated // and return unexpected keys and/or values. You must reposition your cursor // after mutating data. type Cursor struct { bucket *Bucket stack []elemRef } // elemRef represents a reference to an element on a given page/node. type elemRef struct { page *page node *node index int } // DB represents a collection of buckets persisted to a file on disk. // All data access is performed through transactions which can be obtained through the DB. // All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called. type DB struct { // When enabled, the database will perform a Check() after every commit. // A panic is issued if the database is in an inconsistent state. This // flag has a large performance impact so it should only be used for // debugging purposes. StrictMode bool // MaxBatchSize is the maximum size of a batch. Default value is // copied from DefaultMaxBatchSize in Open. // // If <=0, disables batching. // // Do not change concurrently with calls to Batch. MaxBatchSize int // MaxBatchDelay is the maximum delay before a batch starts. // Default value is copied from DefaultMaxBatchDelay in Open. // // If <=0, effectively disables batching. // // Do not change concurrently with calls to Batch. MaxBatchDelay time.Duration // AllocSize is the amount of space allocated when the database // needs to create new pages. This is done to amortize the cost // of truncate() and fsync() when growing the data file. AllocSize int path string file *os.File lockfile *os.File // windows only dataref []byte // mmap'ed read-only via PROT_READ, write throws SEGV data *[maxMapSize]byte datasz int filesz int // current on disk file size meta0 *meta meta1 *meta pageSize int opened bool rwtx *Tx txs []*Tx freelist *freelist stats Stats pagePool sync.Pool batchMu sync.Mutex batch *batch rwlock sync.Mutex // Allows only one writer at a time. metalock sync.Mutex // Protects meta page access. mmaplock sync.RWMutex // Protects mmap access during remapping. statlock sync.RWMutex // Protects stats access. ops struct { writeAt func(b []byte, off int64) (n int, err error) } } type call struct { fn func(*Tx) error err chan<- error } type batch struct { db *DB timer *time.Timer start sync.Once calls []call } type panicked struct { reason interface{} } // Stats represents statistics about the database. type Stats struct { // Freelist stats FreePageN int // total number of free pages on the freelist PendingPageN int // total number of pending pages on the freelist FreeAlloc int // total bytes allocated in free pages FreelistInuse int // total bytes used by the freelist // Transaction stats TxN int // total number of started read transactions OpenTxN int // number of currently open read transactions TxStats TxStats // global, ongoing stats. } type meta struct { magic uint32 version uint32 pageSize uint32 flags uint32 root bucket freelist pgid pgid pgid txid txid checksum uint64 } // freelist represents a list of all pages that are available for allocation. // It also tracks pages that have been freed but are still in use by open transactions. type freelist struct { ids []pgid // all free and available free page ids. pending map[txid][]pgid // mapping of soon-to-be free page ids by tx. cache map[pgid]bool // fast lookup of all free and pending page ids. } // node represents an in-memory, deserialized page. type node struct { bucket *Bucket isLeaf bool unbalanced bool spilled bool key []byte pgid pgid parent *node children nodes inodes inodes } type nodes []*node type pages []*page // inode represents an internal node inside of a node. // It can be used to point to elements in a page or point // to an element which hasn't been added to a page yet. type inode struct { flags uint32 pgid pgid key []byte value []byte } type inodes []inode type page struct { id pgid flags uint16 count uint16 overflow uint32 ptr uintptr } // branchPageElement represents a node on a branch page. type branchPageElement struct { pos uint32 ksize uint32 pgid pgid } // leafPageElement represents a node on a leaf page. type leafPageElement struct { flags uint32 pos uint32 ksize uint32 vsize uint32 } // PageInfo represents human readable information about a page. type PageInfo struct { ID int Type string Count int OverflowCount int } type pgids []pgid // txid represents the internal transaction identifier. type txid uint64 // Tx represents a read-only or read/write transaction on the database. // Read-only transactions can be used for retrieving values for keys and creating cursors. // Read/write transactions can create and remove buckets and create and remove keys. // // IMPORTANT: You must commit or rollback transactions when you are done with // them. Pages can not be reclaimed by the writer until no more transactions // are using them. A long running read transaction can cause the database to // quickly grow. type Tx struct { writable bool managed bool db *DB meta *meta root Bucket pages map[pgid]*page stats TxStats commitHandlers []func() } // TxStats represents statistics about the actions performed by the transaction. type TxStats struct { // Page statistics. PageCount int // number of page allocations PageAlloc int // total bytes allocated // Cursor statistics. CursorCount int // number of cursors created // Node statistics NodeCount int // number of node allocations NodeDeref int // number of node dereferences // Rebalance statistics. Rebalance int // number of node rebalances RebalanceTime time.Duration // total time spent rebalancing // Split/Spill statistics. Split int // number of nodes split Spill int // number of nodes spilled SpillTime time.Duration // total time spent spilling // Write statistics. Write int // number of writes performed WriteTime time.Duration // total time spent writing to disk } // Main represents the main program execution. type MainT struct { Stdin io.Reader Stdout io.Writer Stderr io.Writer } // CheckCommand represents the "check" command execution. type CheckCommand struct { Stdin io.Reader Stdout io.Writer Stderr io.Writer } // InfoCommand represents the "info" command execution. type InfoCommand struct { Stdin io.Reader Stdout io.Writer Stderr io.Writer } // DumpCommand represents the "dump" command execution. type DumpCommand struct { Stdin io.Reader Stdout io.Writer Stderr io.Writer } // PageCommand represents the "page" command execution. type PageCommand struct { Stdin io.Reader Stdout io.Writer Stderr io.Writer } // PagesCommand represents the "pages" command execution. type PagesCommand struct { Stdin io.Reader Stdout io.Writer Stderr io.Writer } // StatsCommand represents the "stats" command execution. type StatsCommand struct { Stdin io.Reader Stdout io.Writer Stderr io.Writer } // BenchCommand represents the "bench" command execution. type BenchCommand struct { Stdin io.Reader Stdout io.Writer Stderr io.Writer } // BenchOptions represents the set of options that can be passed to "bolt bench". type BenchOptions struct { ProfileMode string WriteMode string ReadMode string Iterations int BatchSize int KeySize int ValueSize int CPUProfile string MemProfile string BlockProfile string StatsInterval time.Duration Work bool Path string } // BenchResults represents the performance results of the benchmark. type BenchResults struct { WriteOps int WriteDuration time.Duration ReadOps int ReadDuration time.Duration } type PageError struct { ID int Err error } // CompactCommand represents the "compact" command execution. type CompactCommand struct { Stdin io.Reader Stdout io.Writer Stderr io.Writer SrcPath string DstPath string TxMaxSize int64 } // walkFunc is the type of the function called for keys (buckets and "normal" // values) discovered by Walk. keys is the list of keys to descend to the bucket // owning the discovered key/value pair k/v. type walkFunc func(keys [][]byte, k, v []byte, seq uint64) error const ( // maxMapSize represents the largest mmap size supported by Bolt. maxMapSize = 0xFFFFFFFFFFFF // 256TB // maxAllocSize is the size used when creating array pointers. maxAllocSize = 0x7FFFFFFF // MaxKeySize is the maximum length of a key, in bytes. MaxKeySize = 32768 // MaxValueSize is the maximum length of a value, in bytes. MaxValueSize = (1 << 31) - 2 bucketHeaderSize = int(unsafe.Sizeof(bucket{})) // The largest step that can be taken when remapping the mmap. maxMmapStep = 1 << 30 // 1GB // The data file format version. version = 2 // Represents a marker value to indicate that a file is a Bolt DB. magic uint32 = 0xED0CDAED // Default values if not set in a DB instance. DefaultMaxBatchSize int = 1000 DefaultMaxBatchDelay = 10 * time.Millisecond DefaultAllocSize = 16 * 1024 * 1024 pageHeaderSize = int(unsafe.Offsetof(((*page)(nil)).ptr)) minKeysPerPage = 2 branchPageElementSize = int(unsafe.Sizeof(branchPageElement{})) leafPageElementSize = int(unsafe.Sizeof(leafPageElement{})) branchPageFlag = 0x01 leafPageFlag = 0x02 metaPageFlag = 0x04 freelistPageFlag = 0x10 bucketLeafFlag = 0x01 // PageHeaderSize represents the size of the bolt.page header. PageHeaderSize = 16 ) var ( // Are unaligned load/stores broken on this arch? brokenUnaligned = false // trySolo is a special sentinel error value used for signaling that a // transaction function should be re-run. It should never be seen by // callers. trySolo = errors.New("batch function returned an error and should be re-run solo") // // These errors can be returned when opening or calling methods on a DB. // // ErrDatabaseNotOpen is returned when a DB instance is accessed before it // is opened or after it is closed. ErrDatabaseNotOpen = errors.New("database not open") // ErrInvalid is returned when both meta pages on a database are invalid. // This typically occurs when a file is not a bolt database. ErrInvalid = errors.New("invalid database") // ErrVersionMismatch is returned when the data file was created with a // different version of Bolt. ErrVersionMismatch = errors.New("version mismatch") // ErrChecksum is returned when either meta page checksum does not match. ErrChecksum = errors.New("checksum error") // // These errors can occur when beginning or committing a Tx. // // ErrTxNotWritable is returned when performing a write operation on a // read-only transaction. ErrTxNotWritable = errors.New("tx not writable") // ErrTxClosed is returned when committing or rolling back a transaction // that has already been committed or rolled back. ErrTxClosed = errors.New("tx closed") // // These errors can occur when putting or deleting a value or a bucket. // // ErrBucketNotFound is returned when trying to access a bucket that has // not been created yet. ErrBucketNotFound = errors.New("bucket not found") // ErrBucketExists is returned when creating a bucket that already exists. ErrBucketExists = errors.New("bucket already exists") // ErrBucketNameRequired is returned when creating a bucket with a blank name. ErrBucketNameRequired = errors.New("bucket name required") // ErrKeyRequired is returned when inserting a zero-length key. ErrKeyRequired = errors.New("key required") // ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize. ErrKeyTooLarge = errors.New("key too large") // ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize. ErrValueTooLarge = errors.New("value too large") // ErrIncompatibleValue is returned when trying create or delete a bucket // on an existing non-bucket key or when trying to create or delete a // non-bucket key on an existing bucket key. ErrIncompatibleValue = errors.New("incompatible value") // // // // ErrUsage is returned when a usage message was printed and the process // should simply exit with an error. ErrUsage = errors.New("usage") // ErrUnknownCommand is returned when a CLI command is not specified. ErrUnknownCommand = errors.New("unknown command") // ErrPathRequired is returned when the path to a Bolt database is not specified. ErrPathRequired = errors.New("path required") // ErrFileNotFound is returned when a Bolt database does not exist. ErrFileNotFound = errors.New("file not found") // ErrInvalidValue is returned when a benchmark reads an unexpected value. ErrInvalidValue = errors.New("invalid value") // ErrCorrupt is returned when a checking a data file finds errors. ErrCorrupt = errors.New("invalid value") // ErrNonDivisibleBatchSize is returned when the batch size can't be evenly // divided by the iteration count. ErrNonDivisibleBatchSize = errors.New("number of iterations must be divisible by the batch size") // ErrPageIDRequired is returned when a required page id is not specified. ErrPageIDRequired = errors.New("page id required") benchBucketName = []byte("bench") // File handlers for the various profiles. cpuprofile *os.File = nil memprofile *os.File = nil blockprofile *os.File = nil ) // fdatasync flushes written data to a file descriptor. func fdatasync(db *DB) error { return db.file.Sync() } // flock acquires an advisory lock on a file descriptor. func flock(db *DB) error { const lockFlags = syscall.LOCK_EX | syscall.LOCK_NB for { err := syscall.Flock(int(db.file.Fd()), lockFlags) if err == nil { return nil } else if err != syscall.EWOULDBLOCK { return err } // Wait for a bit and try again. time.Sleep(50 * time.Millisecond) } } // funlock releases an advisory lock on a file descriptor. func funlock(db *DB) error { return syscall.Flock(int(db.file.Fd()), syscall.LOCK_UN) } // mmap memory maps a DB's data file. func mmap(db *DB, sz int) error { // Map the data file to memory. fd := int(db.file.Fd()) b, err := syscall.Mmap(fd, 0, sz, syscall.PROT_READ, syscall.MAP_SHARED) if err != nil { return err } // Advise the kernel that the mmap is accessed randomly. if err := madvise(b, syscall.MADV_RANDOM); err != nil { return fmt.Errorf("madvise: %s", err) } // Save the original byte slice and convert to a byte array pointer. db.dataref = b db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0])) db.datasz = sz return nil } // munmap unmaps a DB's data file from memory. func munmap(db *DB) error { // Ignore the unmap if we have no mapped data. if db.dataref == nil { return nil } // Unmap using the original byte slice. err := syscall.Munmap(db.dataref) db.dataref = nil db.data = nil db.datasz = 0 return err } // NOTE: This function is copied from stdlib because it is not available on darwin. func madvise(b []byte, advice int) (err error) { _, _, e1 := syscall.Syscall(syscall.SYS_MADVISE, uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), uintptr(advice)) if e1 != 0 { err = e1 } return } // newBucket returns a new bucket associated with a transaction. func newBucket(tx *Tx) Bucket { var b = Bucket{tx: tx} if tx.writable { b.buckets = make(map[string]*Bucket) b.nodes = make(map[pgid]*node) } return b } // Writable returns whether the bucket is writable. func (b *Bucket) Writable() bool { return b.tx.writable } // Cursor creates a cursor associated with the bucket. // The cursor is only valid as long as the transaction is open. // Do not use a cursor after the transaction is closed. func (b *Bucket) Cursor() *Cursor { // Update transaction statistics. b.tx.stats.CursorCount++ // Allocate and return a cursor. return &Cursor{ bucket: b, stack: make([]elemRef, 0), } } // Bucket retrieves a nested bucket by name. // Returns nil if the bucket does not exist. // The bucket instance is only valid for the lifetime of the transaction. func (b *Bucket) Bucket(name []byte) *Bucket { if b.buckets != nil { if child := b.buckets[string(name)]; child != nil { return child } } // Move cursor to key. c := b.Cursor() k, v, flags := c.seek(name) // Return nil if the key doesn't exist or it is not a bucket. if !bytes.Equal(name, k) || (flags&bucketLeafFlag) == 0 { return nil } // Otherwise create a bucket and cache it. var child = b.openBucket(v) if b.buckets != nil { b.buckets[string(name)] = child } return child } // Helper method that re-interprets a sub-bucket value // from a parent into a Bucket func (b *Bucket) openBucket(value []byte) *Bucket { var child = newBucket(b.tx) // If unaligned load/stores are broken on this arch and value is // unaligned simply clone to an aligned byte array. unaligned := brokenUnaligned && uintptr(unsafe.Pointer(&value[0]))&3 != 0 if unaligned { value = cloneBytes(value) } // If this is a writable transaction then we need to copy the bucket entry. // Read-only transactions can point directly at the mmap entry. if b.tx.writable && !unaligned { child.ref = &bucket{} *child.ref = *(*bucket)(unsafe.Pointer(&value[0])) } else { child.ref = (*bucket)(unsafe.Pointer(&value[0])) } // Save a reference to the inline page if the bucket is inline. if child.ref.root == 0 { child.page = (*page)(unsafe.Pointer(&value[bucketHeaderSize])) } return &child } // CreateBucket creates a new bucket at the given key and returns the new bucket. // Returns an error if the key already exists, if the bucket name is blank, or if the bucket name is too long. // The bucket instance is only valid for the lifetime of the transaction. func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) { if b.tx.db == nil { return nil, ErrTxClosed } else if !b.tx.writable { return nil, ErrTxNotWritable } else if len(key) == 0 { return nil, ErrBucketNameRequired } // Move cursor to correct position. c := b.Cursor() k, _, flags := c.seek(key) // Return an error if there is an existing key. if bytes.Equal(key, k) { if (flags & bucketLeafFlag) != 0 { return nil, ErrBucketExists } return nil, ErrIncompatibleValue } // Create empty, inline bucket. var bucket = Bucket{ ref: &bucket{}, rootNode: &node{isLeaf: true}, } var value = bucket.write() // Insert into node. key = cloneBytes(key) c.node().put(key, key, value, 0, bucketLeafFlag) // Since subbuckets are not allowed on inline buckets, we need to // dereference the inline page, if it exists. This will cause the bucket // to be treated as a regular, non-inline bucket for the rest of the tx. b.page = nil return b.Bucket(key), nil } // CreateBucketIfNotExists creates a new bucket if it doesn't already exist and returns a reference to it. // Returns an error if the bucket name is blank, or if the bucket name is too long. // The bucket instance is only valid for the lifetime of the transaction. func (b *Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) { child, err := b.CreateBucket(key) if err == ErrBucketExists { return b.Bucket(key), nil } else if err != nil { return nil, err } return child, nil } // DeleteBucket deletes a bucket at the given key. // Returns an error if the bucket does not exists, or if the key represents a non-bucket value. func (b *Bucket) DeleteBucket(key []byte) error { if b.tx.db == nil { return ErrTxClosed } else if !b.Writable() { return ErrTxNotWritable } // Move cursor to correct position. c := b.Cursor() k, _, flags := c.seek(key) // Return an error if bucket doesn't exist or is not a bucket. if !bytes.Equal(key, k) { return ErrBucketNotFound } else if (flags & bucketLeafFlag) == 0 { return ErrIncompatibleValue } // Recursively delete all child buckets. child := b.Bucket(key) err := child.ForEach(func(k, v []byte) error { if v == nil { if err := child.DeleteBucket(k); err != nil { return fmt.Errorf("delete bucket: %s", err) } } return nil }) if err != nil { return err } // Remove cached copy. delete(b.buckets, string(key)) // Release all bucket pages to freelist. child.nodes = nil child.rootNode = nil child.free() // Delete the node if we have a matching key. c.node().del(key) return nil } // Get retrieves the value for a key in the bucket. // Returns a nil value if the key does not exist or if the key is a nested bucket. // The returned value is only valid for the life of the transaction. func (b *Bucket) Get(key []byte) []byte { k, v, flags := b.Cursor().seek(key) // Return nil if this is a bucket. if (flags & bucketLeafFlag) != 0 { return nil } // If our target node isn't the same key as what's passed in then return nil. if !bytes.Equal(key, k) { return nil } return v } // Put sets the value for a key in the bucket. // If the key exist then its previous value will be overwritten. // Supplied value must remain valid for the life of the transaction. // Returns an error if the bucket was created from a read-only transaction, if the key is blank, if the key is too large, or if the value is too large. func (b *Bucket) Put(key []byte, value []byte) error { if b.tx.db == nil { return ErrTxClosed } else if !b.Writable() { return ErrTxNotWritable } else if len(key) == 0 { return ErrKeyRequired } else if len(key) > MaxKeySize { return ErrKeyTooLarge } else if int64(len(value)) > MaxValueSize { return ErrValueTooLarge } // Move cursor to correct position. c := b.Cursor() k, _, flags := c.seek(key) // Return an error if there is an existing key with a bucket value. if bytes.Equal(key, k) && (flags&bucketLeafFlag) != 0 { return ErrIncompatibleValue } // Insert into node. key = cloneBytes(key) c.node().put(key, key, value, 0, 0) return nil } // Delete removes a key from the bucket. // If the key does not exist then nothing is done and a nil error is returned. // Returns an error if the bucket was created from a read-only transaction. func (b *Bucket) Delete(key []byte) error { if b.tx.db == nil { return ErrTxClosed } else if !b.Writable() { return ErrTxNotWritable } // Move cursor to correct position. c := b.Cursor() _, _, flags := c.seek(key) // Return an error if there is already existing bucket value. if (flags & bucketLeafFlag) != 0 { return ErrIncompatibleValue } // Delete the node if we have a matching key. c.node().del(key) return nil } // Sequence returns the current integer for the bucket without incrementing it. func (b *Bucket) Sequence() uint64 { return b.ref.sequence } // SetSequence updates the sequence number for the bucket. func (b *Bucket) SetSequence(v uint64) error { if b.tx.db == nil { return ErrTxClosed } else if !b.Writable() { return ErrTxNotWritable } // Materialize the root node if it hasn't been already so that the // bucket will be saved during commit. if b.rootNode == nil { _ = b.node(b.ref.root, nil) } // Increment and return the sequence. b.ref.sequence = v return nil } // NextSequence returns an autoincrementing integer for the bucket. func (b *Bucket) NextSequence() (uint64, error) { if b.tx.db == nil { return 0, ErrTxClosed } else if !b.Writable() { return 0, ErrTxNotWritable } // Materialize the root node if it hasn't been already so that the // bucket will be saved during commit. if b.rootNode == nil { _ = b.node(b.ref.root, nil) } // Increment and return the sequence. b.ref.sequence++ return b.ref.sequence, nil } // ForEach executes a function for each key/value pair in a bucket. // If the provided function returns an error then the iteration is stopped and // the error is returned to the caller. The provided function must not modify // the bucket; this will result in undefined behavior. func (b *Bucket) ForEach(fn func(k, v []byte) error) error { if b.tx.db == nil { return ErrTxClosed } c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { if err := fn(k, v); err != nil { return err } } return nil } // Stat returns stats on a bucket. func (b *Bucket) Stats() BucketStats { var s, subStats BucketStats pageSize := b.tx.db.pageSize s.BucketN += 1 if b.ref.root == 0 { s.InlineBucketN += 1 } b.forEachPage(func(p *page, depth int) { if (p.flags & leafPageFlag) != 0 { s.KeyN += int(p.count) // used totals the used bytes for the page used := pageHeaderSize if p.count != 0 { // If page has any elements, add all element headers. used += leafPageElementSize * int(p.count-1) // Add all element key, value sizes. // The computation takes advantage of the fact that the position // of the last element's key/value equals to the total of the sizes // of all previous elements' keys and values. // It also includes the last element's header. lastElement := p.leafPageElement(p.count - 1) used += int(lastElement.pos + lastElement.ksize + lastElement.vsize) } if b.ref.root == 0 { // For inlined bucket just update the inline stats s.InlineBucketInuse += used } else { // For non-inlined bucket update all the leaf stats s.LeafPageN++ s.LeafInuse += used s.LeafOverflowN += int(p.overflow) // Collect stats from sub-buckets. // Do that by iterating over all element headers // looking for the ones with the bucketLeafFlag. for i := uint16(0); i < p.count; i++ { e := p.leafPageElement(i) if (e.flags & bucketLeafFlag) != 0 { // For any bucket element, open the element value // and recursively call Stats on the contained bucket. subStats.Add(b.openBucket(e.value()).Stats()) } } } } else if (p.flags & branchPageFlag) != 0 { s.BranchPageN++ lastElement := p.branchPageElement(p.count - 1) // used totals the used bytes for the page // Add header and all element headers. used := pageHeaderSize + (branchPageElementSize * int(p.count-1)) // Add size of all keys and values. // Again, use the fact that last element's position equals to // the total of key, value sizes of all previous elements. used += int(lastElement.pos + lastElement.ksize) s.BranchInuse += used s.BranchOverflowN += int(p.overflow) } // Keep track of maximum page depth. if depth+1 > s.Depth { s.Depth = (depth + 1) } }) // Alloc stats can be computed from page counts and pageSize. s.BranchAlloc = (s.BranchPageN + s.BranchOverflowN) * pageSize s.LeafAlloc = (s.LeafPageN + s.LeafOverflowN) * pageSize // Add the max depth of sub-buckets to get total nested depth. s.Depth += subStats.Depth // Add the stats for all sub-buckets s.Add(subStats) return s } // forEachPage iterates over every page in a bucket, including inline pages. func (b *Bucket) forEachPage(fn func(*page, int)) { // If we have an inline page then just use that. if b.page != nil { fn(b.page, 0) return } // Otherwise traverse the page hierarchy. b.tx.forEachPage(b.ref.root, 0, fn) } // forEachPageNode iterates over every page (or node) in a bucket. // This also includes inline pages. func (b *Bucket) forEachPageNode(fn func(*page, *node, int)) { // If we have an inline page or root node then just use that. if b.page != nil { fn(b.page, nil, 0) return } b._forEachPageNode(b.ref.root, 0, fn) } func (b *Bucket) _forEachPageNode(pgid pgid, depth int, fn func(*page, *node, int)) { var p, n = b.pageNode(pgid) // Execute function. fn(p, n, depth) // Recursively loop over children. if p != nil { if (p.flags & branchPageFlag) != 0 { for i := 0; i < int(p.count); i++ { elem := p.branchPageElement(uint16(i)) b._forEachPageNode(elem.pgid, depth+1, fn) } } } else { if !n.isLeaf { for _, inode := range n.inodes { b._forEachPageNode(inode.pgid, depth+1, fn) } } } } // spill writes all the nodes for this bucket to dirty pages. func (b *Bucket) spill() error { // Spill all child buckets first. for name, child := range b.buckets { // If the child bucket is small enough and it has no child buckets then // write it inline into the parent bucket's page. Otherwise spill it // like a normal bucket and make the parent value a pointer to the page. var value []byte if child.inlineable() { child.free() value = child.write() } else { if err := child.spill(); err != nil { return err } // Update the child bucket header in this bucket. value = make([]byte, unsafe.Sizeof(bucket{})) var bucket = (*bucket)(unsafe.Pointer(&value[0])) *bucket = *child.ref } // Skip writing the bucket if there are no materialized nodes. if child.rootNode == nil { continue } // Update parent node. var c = b.Cursor() k, _, flags := c.seek([]byte(name)) if !bytes.Equal([]byte(name), k) { panic(fmt.Sprintf("misplaced bucket header: %x -> %x", []byte(name), k)) } if flags&bucketLeafFlag == 0 { panic(fmt.Sprintf("unexpected bucket header flag: %x", flags)) } c.node().put([]byte(name), []byte(name), value, 0, bucketLeafFlag) } // Ignore if there's not a materialized root node. if b.rootNode == nil { return nil } // Spill nodes. if err := b.rootNode.spill(); err != nil { return err } b.rootNode = b.rootNode.root() // Update the root node for this bucket. if b.rootNode.pgid >= b.tx.meta.pgid { panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", b.rootNode.pgid, b.tx.meta.pgid)) } b.ref.root = b.rootNode.pgid return nil } // inlineable returns true if a bucket is small enough to be written inline // and if it contains no subbuckets. Otherwise returns false. func (b *Bucket) inlineable() bool { var n = b.rootNode // Bucket must only contain a single leaf node. if n == nil || !n.isLeaf { return false } // Bucket is not inlineable if it contains subbuckets or if it goes beyond // our threshold for inline bucket size. var size = pageHeaderSize for _, inode := range n.inodes { size += leafPageElementSize + len(inode.key) + len(inode.value) if inode.flags&bucketLeafFlag != 0 { return false } else if size > b.maxInlineBucketSize() { return false } } return true } // Returns the maximum total size of a bucket to make it a candidate for inlining. func (b *Bucket) maxInlineBucketSize() int { return b.tx.db.pageSize / 4 } // write allocates and writes a bucket to a byte slice. func (b *Bucket) write() []byte { // Allocate the appropriate size. var n = b.rootNode var value = make([]byte, bucketHeaderSize+n.size()) // Write a bucket header. var bucket = (*bucket)(unsafe.Pointer(&value[0])) *bucket = *b.ref // Convert byte slice to a fake page and write the root node. var p = (*page)(unsafe.Pointer(&value[bucketHeaderSize])) n.write(p) return value } // rebalance attempts to balance all nodes. func (b *Bucket) rebalance() { for _, n := range b.nodes { n.rebalance() } for _, child := range b.buckets { child.rebalance() } } // node creates a node from a page and associates it with a given parent. func (b *Bucket) node(pgid pgid, parent *node) *node { _assert(b.nodes != nil, "nodes map expected") // Retrieve node if it's already been created. if n := b.nodes[pgid]; n != nil { return n } // Otherwise create a node and cache it. n := &node{bucket: b, parent: parent} if parent == nil { b.rootNode = n } else { parent.children = append(parent.children, n) } // Use the inline page if this is an inline bucket. var p = b.page if p == nil { p = b.tx.page(pgid) } // Read the page into the node and cache it. n.read(p) b.nodes[pgid] = n // Update statistics. b.tx.stats.NodeCount++ return n } // free recursively frees all pages in the bucket. func (b *Bucket) free() { if b.ref.root == 0 { return } var tx = b.tx b.forEachPageNode(func(p *page, n *node, _ int) { if p != nil { tx.db.freelist.free(tx.meta.txid, p) } else { n.free() } }) b.ref.root = 0 } // dereference removes all references to the old mmap. func (b *Bucket) dereference() { if b.rootNode != nil { b.rootNode.root().dereference() } for _, child := range b.buckets { child.dereference() } } // pageNode returns the in-memory node, if it exists. // Otherwise returns the underlying page. func (b *Bucket) pageNode(id pgid) (*page, *node) { // Inline buckets have a fake page embedded in their value so treat them // differently. We'll return the rootNode (if available) or the fake page. if b.ref.root == 0 { if id != 0 { panic(fmt.Sprintf("inline bucket non-zero page access(2): %d != 0", id)) } if b.rootNode != nil { return nil, b.rootNode } return b.page, nil } // Check the node cache for non-inline buckets. if b.nodes != nil { if n := b.nodes[id]; n != nil { return nil, n } } // Finally lookup the page from the transaction if no node is materialized. return b.tx.page(id), nil } func (s *BucketStats) Add(other BucketStats) { s.BranchPageN += other.BranchPageN s.BranchOverflowN += other.BranchOverflowN s.LeafPageN += other.LeafPageN s.LeafOverflowN += other.LeafOverflowN s.KeyN += other.KeyN if s.Depth < other.Depth { s.Depth = other.Depth } s.BranchAlloc += other.BranchAlloc s.BranchInuse += other.BranchInuse s.LeafAlloc += other.LeafAlloc s.LeafInuse += other.LeafInuse s.BucketN += other.BucketN s.InlineBucketN += other.InlineBucketN s.InlineBucketInuse += other.InlineBucketInuse } // cloneBytes returns a copy of a given slice. func cloneBytes(v []byte) []byte { var clone = make([]byte, len(v)) copy(clone, v) return clone } // Bucket returns the bucket that this cursor was created from. func (c *Cursor) Bucket() *Bucket { return c.bucket } // First moves the cursor to the first item in the bucket and returns its key and value. // If the bucket is empty then a nil key and value are returned. // The returned key and value are only valid for the life of the transaction. func (c *Cursor) First() (key []byte, value []byte) { _assert(c.bucket.tx.db != nil, "tx closed") c.stack = c.stack[:0] p, n := c.bucket.pageNode(c.bucket.ref.root) c.stack = append(c.stack, elemRef{page: p, node: n, index: 0}) c.first() // If we land on an empty page then move to the next value. // https://github.com/boltdb/bolt/issues/450 if c.stack[len(c.stack)-1].count() == 0 { c.next() } k, v, flags := c.keyValue() if (flags & uint32(bucketLeafFlag)) != 0 { return k, nil } return k, v } // Last moves the cursor to the last item in the bucket and returns its key and value. // If the bucket is empty then a nil key and value are returned. // The returned key and value are only valid for the life of the transaction. func (c *Cursor) Last() (key []byte, value []byte) { _assert(c.bucket.tx.db != nil, "tx closed") c.stack = c.stack[:0] p, n := c.bucket.pageNode(c.bucket.ref.root) ref := elemRef{page: p, node: n} ref.index = ref.count() - 1 c.stack = append(c.stack, ref) c.last() k, v, flags := c.keyValue() if (flags & uint32(bucketLeafFlag)) != 0 { return k, nil } return k, v } // Next moves the cursor to the next item in the bucket and returns its key and value. // If the cursor is at the end of the bucket then a nil key and value are returned. // The returned key and value are only valid for the life of the transaction. func (c *Cursor) Next() (key []byte, value []byte) { _assert(c.bucket.tx.db != nil, "tx closed") k, v, flags := c.next() if (flags & uint32(bucketLeafFlag)) != 0 { return k, nil } return k, v } // Prev moves the cursor to the previous item in the bucket and returns its key and value. // If the cursor is at the beginning of the bucket then a nil key and value are returned. // The returned key and value are only valid for the life of the transaction. func (c *Cursor) Prev() (key []byte, value []byte) { _assert(c.bucket.tx.db != nil, "tx closed") // Attempt to move back one element until we're successful. // Move up the stack as we hit the beginning of each page in our stack. for i := len(c.stack) - 1; i >= 0; i-- { elem := &c.stack[i] if elem.index > 0 { elem.index-- break } c.stack = c.stack[:i] } // If we've hit the end then return nil. if len(c.stack) == 0 { return nil, nil } // Move down the stack to find the last element of the last leaf under this branch. c.last() k, v, flags := c.keyValue() if (flags & uint32(bucketLeafFlag)) != 0 { return k, nil } return k, v } // Seek moves the cursor to a given key and returns it. // If the key does not exist then the next key is used. If no keys // follow, a nil key is returned. // The returned key and value are only valid for the life of the transaction. func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) { k, v, flags := c.seek(seek) // If we ended up after the last element of a page then move to the next one. if ref := &c.stack[len(c.stack)-1]; ref.index >= ref.count() { k, v, flags = c.next() } if k == nil { return nil, nil } else if (flags & uint32(bucketLeafFlag)) != 0 { return k, nil } return k, v } // Delete removes the current key/value under the cursor from the bucket. // Delete fails if current key/value is a bucket or if the transaction is not writable. func (c *Cursor) Delete() error { if c.bucket.tx.db == nil { return ErrTxClosed } else if !c.bucket.Writable() { return ErrTxNotWritable } key, _, flags := c.keyValue() // Return an error if current value is a bucket. if (flags & bucketLeafFlag) != 0 { return ErrIncompatibleValue } c.node().del(key) return nil } // seek moves the cursor to a given key and returns it. // If the key does not exist then the next key is used. func (c *Cursor) seek(seek []byte) (key []byte, value []byte, flags uint32) { _assert(c.bucket.tx.db != nil, "tx closed") // Start from root page/node and traverse to correct page. c.stack = c.stack[:0] c.search(seek, c.bucket.ref.root) ref := &c.stack[len(c.stack)-1] // If the cursor is pointing to the end of page/node then return nil. if ref.index >= ref.count() { return nil, nil, 0 } // If this is a bucket then return a nil value. return c.keyValue() } // first moves the cursor to the first leaf element under the last page in the stack. func (c *Cursor) first() { for { // Exit when we hit a leaf page. var ref = &c.stack[len(c.stack)-1] if ref.isLeaf() { break } // Keep adding pages pointing to the first element to the stack. var pgid pgid if ref.node != nil { pgid = ref.node.inodes[ref.index].pgid } else { pgid = ref.page.branchPageElement(uint16(ref.index)).pgid } p, n := c.bucket.pageNode(pgid) c.stack = append(c.stack, elemRef{page: p, node: n, index: 0}) } } // last moves the cursor to the last leaf element under the last page in the stack. func (c *Cursor) last() { for { // Exit when we hit a leaf page. ref := &c.stack[len(c.stack)-1] if ref.isLeaf() { break } // Keep adding pages pointing to the last element in the stack. var pgid pgid if ref.node != nil { pgid = ref.node.inodes[ref.index].pgid } else { pgid = ref.page.branchPageElement(uint16(ref.index)).pgid } p, n := c.bucket.pageNode(pgid) var nextRef = elemRef{page: p, node: n} nextRef.index = nextRef.count() - 1 c.stack = append(c.stack, nextRef) } } // next moves to the next leaf element and returns the key and value. // If the cursor is at the last leaf element then it stays there and returns nil. func (c *Cursor) next() (key []byte, value []byte, flags uint32) { for { // Attempt to move over one element until we're successful. // Move up the stack as we hit the end of each page in our stack. var i int for i = len(c.stack) - 1; i >= 0; i-- { elem := &c.stack[i] if elem.index < elem.count()-1 { elem.index++ break } } // If we've hit the root page then stop and return. This will leave the // cursor on the last element of the last page. if i == -1 { return nil, nil, 0 } // Otherwise start from where we left off in the stack and find the // first element of the first leaf page. c.stack = c.stack[:i+1] c.first() // If this is an empty page then restart and move back up the stack. // https://github.com/boltdb/bolt/issues/450 if c.stack[len(c.stack)-1].count() == 0 { continue } return c.keyValue() } } // search recursively performs a binary search against a given page/node until it finds a given key. func (c *Cursor) search(key []byte, pgid pgid) { p, n := c.bucket.pageNode(pgid) if p != nil && (p.flags&(branchPageFlag|leafPageFlag)) == 0 { panic(fmt.Sprintf("invalid page type: %d: %x", p.id, p.flags)) } e := elemRef{page: p, node: n} c.stack = append(c.stack, e) // If we're on a leaf page/node then find the specific node. if e.isLeaf() { c.nsearch(key) return } if n != nil { c.searchNode(key, n) return } c.searchPage(key, p) } func (c *Cursor) searchNode(key []byte, n *node) { var exact bool index := sort.Search(len(n.inodes), func(i int) bool { // TODO(benbjohnson): Optimize this range search. It's a bit hacky right now. // sort.Search() finds the lowest index where f() != -1 but we need the highest index. ret := bytes.Compare(n.inodes[i].key, key) if ret == 0 { exact = true } return ret != -1 }) if !exact && index > 0 { index-- } c.stack[len(c.stack)-1].index = index // Recursively search to the next page. c.search(key, n.inodes[index].pgid) } func (c *Cursor) searchPage(key []byte, p *page) { // Binary search for the correct range. inodes := p.branchPageElements() var exact bool index := sort.Search(int(p.count), func(i int) bool { // TODO(benbjohnson): Optimize this range search. It's a bit hacky right now. // sort.Search() finds the lowest index where f() != -1 but we need the highest index. ret := bytes.Compare(inodes[i].key(), key) if ret == 0 { exact = true } return ret != -1 }) if !exact && index > 0 { index-- } c.stack[len(c.stack)-1].index = index // Recursively search to the next page. c.search(key, inodes[index].pgid) } // nsearch searches the leaf node on the top of the stack for a key. func (c *Cursor) nsearch(key []byte) { e := &c.stack[len(c.stack)-1] p, n := e.page, e.node // If we have a node then search its inodes. if n != nil { index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, key) != -1 }) e.index = index return } // If we have a page then search its leaf elements. inodes := p.leafPageElements() index := sort.Search(int(p.count), func(i int) bool { return bytes.Compare(inodes[i].key(), key) != -1 }) e.index = index } // keyValue returns the key and value of the current leaf element. func (c *Cursor) keyValue() ([]byte, []byte, uint32) { ref := &c.stack[len(c.stack)-1] if ref.count() == 0 || ref.index >= ref.count() { return nil, nil, 0 } // Retrieve value from node. if ref.node != nil { inode := &ref.node.inodes[ref.index] return inode.key, inode.value, inode.flags } // Or retrieve value from page. elem := ref.page.leafPageElement(uint16(ref.index)) return elem.key(), elem.value(), elem.flags } // node returns the node that the cursor is currently positioned on. func (c *Cursor) node() *node { _assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack") // If the top of the stack is a leaf node then just return it. if ref := &c.stack[len(c.stack)-1]; ref.node != nil && ref.isLeaf() { return ref.node } // Start from root and traverse down the hierarchy. var n = c.stack[0].node if n == nil { n = c.bucket.node(c.stack[0].page.id, nil) } for _, ref := range c.stack[:len(c.stack)-1] { _assert(!n.isLeaf, "expected branch node") n = n.childAt(int(ref.index)) } _assert(n.isLeaf, "expected leaf node") return n } // isLeaf returns whether the ref is pointing at a leaf page/node. func (r *elemRef) isLeaf() bool { if r.node != nil { return r.node.isLeaf } return (r.page.flags & leafPageFlag) != 0 } // count returns the number of inodes or page elements. func (r *elemRef) count() int { if r.node != nil { return len(r.node.inodes) } return int(r.page.count) } // Path returns the path to currently open database file. func (db *DB) Path() string { return db.path } // GoString returns the Go string representation of the database. func (db *DB) GoString() string { return fmt.Sprintf("bolt.DB{path:%q}", db.path) } // String returns the string representation of the database. func (db *DB) String() string { return fmt.Sprintf("DB<%q>", db.path) } func openFile(path string) (*os.File, error) { const ( fileMode = 0o666 flagsRW = os.O_RDWR | os.O_CREATE flagsRO = os.O_RDONLY ) file, err := os.OpenFile(path, flagsRW, fileMode) if err == nil { return file, nil } if !errors.Is(err, fs.ErrPermission) { return nil, err } return os.OpenFile(path, flagsRO, fileMode) } // Open creates and opens a database at the given path. // If the file does not exist then it will be created automatically. // Passing in nil options will cause Bolt to open the database with the default options. func Open(path string) (*DB, error) { var db = &DB{ opened: true, path: path, } // Set default values for later DB operations. db.MaxBatchSize = DefaultMaxBatchSize db.MaxBatchDelay = DefaultMaxBatchDelay db.AllocSize = DefaultAllocSize file, err := openFile(path) if err != nil { _ = db.close() return nil, err } db.file = file // Lock file so that other processes using Bolt in read-write mode cannot // use the database at the same time. This would cause corruption since // the two processes would write meta pages and free pages separately. // The database file is locked exclusively (only one process can grab the lock) // if !options.ReadOnly. // The database file is locked using the shared lock (more than one process may // hold a lock at the same time) otherwise (options.ReadOnly is set). if err := flock(db); err != nil { _ = db.close() return nil, err } // Default values for test hooks db.ops.writeAt = db.file.WriteAt // Initialize the database if it doesn't exist. if info, err := db.file.Stat(); err != nil { return nil, err } else if info.Size() == 0 { // Initialize new files with meta pages. if err := db.init(); err != nil { return nil, err } } else { // Read the first meta page to determine the page size. var buf [0x1000]byte if _, err := db.file.ReadAt(buf[:], 0); err == nil { m := db.pageInBuffer(buf[:], 0).meta() if err := m.validate(); err != nil { // If we can't read the page size, we can assume it's the same // as the OS -- since that's how the page size was chosen in the // first place. // // If the first page is invalid and this OS uses a different // page size than what the database was created with then we // are out of luck and cannot access the database. db.pageSize = os.Getpagesize() } else { db.pageSize = int(m.pageSize) } } } // Initialize page pool. db.pagePool = sync.Pool{ New: func() interface{} { return make([]byte, db.pageSize) }, } // Memory map the data file. if err := db.mmap(0); err != nil { _ = db.close() return nil, err } // Read in the freelist. db.freelist = newFreelist() db.freelist.read(db.page(db.meta().freelist)) // Mark the database as opened and return. return db, nil } // mmap opens the underlying memory-mapped file and initializes the meta references. // minsz is the minimum size that the new mmap can be. func (db *DB) mmap(minsz int) error { db.mmaplock.Lock() defer db.mmaplock.Unlock() info, err := db.file.Stat() if err != nil { return fmt.Errorf("mmap stat error: %s", err) } else if int(info.Size()) < db.pageSize*2 { return fmt.Errorf("file size too small") } // Ensure the size is at least the minimum size. var size = int(info.Size()) if size < minsz { size = minsz } size, err = db.mmapSize(size) if err != nil { return err } // Dereference all mmap references before unmapping. if db.rwtx != nil { db.rwtx.root.dereference() } // Unmap existing data before continuing. if err := db.munmap(); err != nil { return err } // Memory-map the data file as a byte slice. if err := mmap(db, size); err != nil { return err } // Save references to the meta pages. db.meta0 = db.page(0).meta() db.meta1 = db.page(1).meta() // Validate the meta pages. We only return an error if both meta pages fail // validation, since meta0 failing validation means that it wasn't saved // properly -- but we can recover using meta1. And vice-versa. err0 := db.meta0.validate() err1 := db.meta1.validate() if err0 != nil && err1 != nil { return err0 } return nil } // munmap unmaps the data file from memory. func (db *DB) munmap() error { if err := munmap(db); err != nil { return fmt.Errorf("unmap error: " + err.Error()) } return nil } // mmapSize determines the appropriate size for the mmap given the current size // of the database. The minimum size is 32KB and doubles until it reaches 1GB. // Returns an error if the new mmap size is greater than the max allowed. func (db *DB) mmapSize(size int) (int, error) { // Double the size from 32KB until 1GB. for i := uint(15); i <= 30; i++ { if size <= 1< maxMapSize { return 0, fmt.Errorf("mmap too large") } // If larger than 1GB then grow by 1GB at a time. sz := int64(size) if remainder := sz % int64(maxMmapStep); remainder > 0 { sz += int64(maxMmapStep) - remainder } // Ensure that the mmap size is a multiple of the page size. // This should always be true since we're incrementing in MBs. pageSize := int64(db.pageSize) if (sz % pageSize) != 0 { sz = ((sz / pageSize) + 1) * pageSize } // If we've exceeded the max size then only grow up to the max size. if sz > maxMapSize { sz = maxMapSize } return int(sz), nil } // init creates a new database file and initializes its meta pages. func (db *DB) init() error { // Set the page size to the OS page size. db.pageSize = os.Getpagesize() // Create two meta pages on a buffer. buf := make([]byte, db.pageSize*4) for i := 0; i < 2; i++ { p := db.pageInBuffer(buf[:], pgid(i)) p.id = pgid(i) p.flags = metaPageFlag // Initialize the meta page. m := p.meta() m.magic = magic m.version = version m.pageSize = uint32(db.pageSize) m.freelist = 2 m.root = bucket{root: 3} m.pgid = 4 m.txid = txid(i) m.checksum = m.sum64() } // Write an empty freelist at page 3. p := db.pageInBuffer(buf[:], pgid(2)) p.id = pgid(2) p.flags = freelistPageFlag p.count = 0 // Write an empty leaf page at page 4. p = db.pageInBuffer(buf[:], pgid(3)) p.id = pgid(3) p.flags = leafPageFlag p.count = 0 // Write the buffer to our data file. if _, err := db.ops.writeAt(buf, 0); err != nil { return err } if err := fdatasync(db); err != nil { return err } return nil } // Close releases all database resources. // All transactions must be closed before closing the database. func (db *DB) Close() error { db.rwlock.Lock() defer db.rwlock.Unlock() db.metalock.Lock() defer db.metalock.Unlock() db.mmaplock.RLock() defer db.mmaplock.RUnlock() return db.close() } func (db *DB) close() error { if !db.opened { return nil } db.opened = false db.freelist = nil // Clear ops. db.ops.writeAt = nil // Close the mmap. if err := db.munmap(); err != nil { return err } // Close file handles. if db.file != nil { // No need to unlock read-only file. // Unlock the file. if err := funlock(db); err != nil { log.Printf("bolt.Close(): funlock error: %s", err) } // Close the file descriptor. if err := db.file.Close(); err != nil { return fmt.Errorf("db file close: %s", err) } db.file = nil } db.path = "" return nil } // Begin starts a new transaction. // Multiple read-only transactions can be used concurrently but only one // write transaction can be used at a time. Starting multiple write transactions // will cause the calls to block and be serialized until the current write // transaction finishes. // // Transactions should not be dependent on one another. Opening a read // transaction and a write transaction in the same goroutine can cause the // writer to deadlock because the database periodically needs to re-mmap itself // as it grows and it cannot do that while a read transaction is open. // // If a long running read transaction (for example, a snapshot transaction) is // needed, you might want to set DB.InitialMmapSize to a large enough value // to avoid potential blocking of write transaction. // // IMPORTANT: You must close read-only transactions after you are finished or // else the database will not reclaim old pages. func (db *DB) Begin(writable bool) (*Tx, error) { if writable { return db.beginRWTx() } return db.beginTx() } func (db *DB) beginTx() (*Tx, error) { // Lock the meta pages while we initialize the transaction. We obtain // the meta lock before the mmap lock because that's the order that the // write transaction will obtain them. db.metalock.Lock() // Obtain a read-only lock on the mmap. When the mmap is remapped it will // obtain a write lock so all transactions must finish before it can be // remapped. db.mmaplock.RLock() // Exit if the database is not open yet. if !db.opened { db.mmaplock.RUnlock() db.metalock.Unlock() return nil, ErrDatabaseNotOpen } // Create a transaction associated with the database. t := &Tx{} t.init(db) // Keep track of transaction until it closes. db.txs = append(db.txs, t) n := len(db.txs) // Unlock the meta pages. db.metalock.Unlock() // Update the transaction stats. db.statlock.Lock() db.stats.TxN++ db.stats.OpenTxN = n db.statlock.Unlock() return t, nil } func (db *DB) beginRWTx() (*Tx, error) { // Obtain writer lock. This is released by the transaction when it closes. // This enforces only one writer transaction at a time. db.rwlock.Lock() // Once we have the writer lock then we can lock the meta pages so that // we can set up the transaction. db.metalock.Lock() defer db.metalock.Unlock() // Exit if the database is not open yet. if !db.opened { db.rwlock.Unlock() return nil, ErrDatabaseNotOpen } // Create a transaction associated with the database. t := &Tx{writable: true} t.init(db) db.rwtx = t // Free any pages associated with closed read-only transactions. var minid txid = 0xFFFFFFFFFFFFFFFF for _, t := range db.txs { if t.meta.txid < minid { minid = t.meta.txid } } if minid > 0 { db.freelist.release(minid - 1) } return t, nil } // removeTx removes a transaction from the database. func (db *DB) removeTx(tx *Tx) { // Release the read lock on the mmap. db.mmaplock.RUnlock() // Use the meta lock to restrict access to the DB object. db.metalock.Lock() // Remove the transaction. for i, t := range db.txs { if t == tx { last := len(db.txs) - 1 db.txs[i] = db.txs[last] db.txs[last] = nil db.txs = db.txs[:last] break } } n := len(db.txs) // Unlock the meta pages. db.metalock.Unlock() // Merge statistics. db.statlock.Lock() db.stats.OpenTxN = n db.stats.TxStats.add(&tx.stats) db.statlock.Unlock() } // Update executes a function within the context of a read-write managed transaction. // If no error is returned from the function then the transaction is committed. // If an error is returned then the entire transaction is rolled back. // Any error that is returned from the function or returned from the commit is // returned from the Update() method. // // Attempting to manually commit or rollback within the function will cause a panic. func (db *DB) Update(fn func(*Tx) error) error { t, err := db.Begin(true) if err != nil { return err } // Make sure the transaction rolls back in the event of a panic. defer func() { if t.db != nil { t.rollback() } }() // Mark as a managed tx so that the inner function cannot manually commit. t.managed = true // If an error is returned from the function then rollback and return error. err = fn(t) t.managed = false if err != nil { _ = t.Rollback() return err } return t.Commit() } // View executes a function within the context of a managed read-only transaction. // Any error that is returned from the function is returned from the View() method. // // Attempting to manually rollback within the function will cause a panic. func (db *DB) View(fn func(*Tx) error) error { t, err := db.Begin(false) if err != nil { return err } // Make sure the transaction rolls back in the event of a panic. defer func() { if t.db != nil { t.rollback() } }() // Mark as a managed tx so that the inner function cannot manually rollback. t.managed = true // If an error is returned from the function then pass it through. err = fn(t) t.managed = false if err != nil { _ = t.Rollback() return err } if err := t.Rollback(); err != nil { return err } return nil } // Batch calls fn as part of a batch. It behaves similar to Update, // except: // // 1. concurrent Batch calls can be combined into a single Bolt // transaction. // // 2. the function passed to Batch may be called multiple times, // regardless of whether it returns error or not. // // This means that Batch function side effects must be idempotent and // take permanent effect only after a successful return is seen in // caller. // // The maximum batch size and delay can be adjusted with DB.MaxBatchSize // and DB.MaxBatchDelay, respectively. // // Batch is only useful when there are multiple goroutines calling it. func (db *DB) Batch(fn func(*Tx) error) error { errCh := make(chan error, 1) db.batchMu.Lock() if (db.batch == nil) || (db.batch != nil && len(db.batch.calls) >= db.MaxBatchSize) { // There is no existing batch, or the existing batch is full; start a new one. db.batch = &batch{ db: db, } db.batch.timer = time.AfterFunc(db.MaxBatchDelay, db.batch.trigger) } db.batch.calls = append(db.batch.calls, call{fn: fn, err: errCh}) if len(db.batch.calls) >= db.MaxBatchSize { // wake up batch, it's ready to run go db.batch.trigger() } db.batchMu.Unlock() err := <-errCh if err == trySolo { err = db.Update(fn) } return err } // trigger runs the batch if it hasn't already been run. func (b *batch) trigger() { b.start.Do(b.run) } // run performs the transactions in the batch and communicates results // back to DB.Batch. func (b *batch) run() { b.db.batchMu.Lock() b.timer.Stop() // Make sure no new work is added to this batch, but don't break // other batches. if b.db.batch == b { b.db.batch = nil } b.db.batchMu.Unlock() retry: for len(b.calls) > 0 { var failIdx = -1 err := b.db.Update(func(tx *Tx) error { for i, c := range b.calls { if err := safelyCall(c.fn, tx); err != nil { failIdx = i return err } } return nil }) if failIdx >= 0 { // take the failing transaction out of the batch. it's // safe to shorten b.calls here because db.batch no longer // points to us, and we hold the mutex anyway. c := b.calls[failIdx] b.calls[failIdx], b.calls = b.calls[len(b.calls)-1], b.calls[:len(b.calls)-1] // tell the submitter re-run it solo, continue with the rest of the batch c.err <- trySolo continue retry } // pass success, or bolt internal errors, to all callers for _, c := range b.calls { c.err <- err } break retry } } func (p panicked) Error() string { if err, ok := p.reason.(error); ok { return err.Error() } return fmt.Sprintf("panic: %v", p.reason) } func safelyCall(fn func(*Tx) error, tx *Tx) (err error) { defer func() { if p := recover(); p != nil { err = panicked{p} } }() return fn(tx) } // Stats retrieves ongoing performance stats for the database. // This is only updated when a transaction closes. func (db *DB) Stats() Stats { db.statlock.RLock() defer db.statlock.RUnlock() return db.stats } // page retrieves a page reference from the mmap based on the current page size. func (db *DB) page(id pgid) *page { pos := id * pgid(db.pageSize) return (*page)(unsafe.Pointer(&db.data[pos])) } // pageInBuffer retrieves a page reference from a given byte array based on the current page size. func (db *DB) pageInBuffer(b []byte, id pgid) *page { return (*page)(unsafe.Pointer(&b[id*pgid(db.pageSize)])) } // meta retrieves the current meta page reference. func (db *DB) meta() *meta { // We have to return the meta with the highest txid which doesn't fail // validation. Otherwise, we can cause errors when in fact the database is // in a consistent state. metaA is the one with the higher txid. metaA := db.meta0 metaB := db.meta1 if db.meta1.txid > db.meta0.txid { metaA = db.meta1 metaB = db.meta0 } // Use higher meta page if valid. Otherwise fallback to previous, if valid. if err := metaA.validate(); err == nil { return metaA } else if err := metaB.validate(); err == nil { return metaB } // This should never be reached, because both meta1 and meta0 were validated // on mmap() and we do fsync() on every write. panic("bolt.DB.meta(): invalid meta pages") } // allocate returns a contiguous block of memory starting at a given page. func (db *DB) allocate(count int) (*page, error) { // Allocate a temporary buffer for the page. var buf []byte if count == 1 { buf = db.pagePool.Get().([]byte) } else { buf = make([]byte, count*db.pageSize) } p := (*page)(unsafe.Pointer(&buf[0])) p.overflow = uint32(count - 1) // Use pages from the freelist if they are available. if p.id = db.freelist.allocate(count); p.id != 0 { return p, nil } // Resize mmap() if we're at the end. p.id = db.rwtx.meta.pgid var minsz = int((p.id+pgid(count))+1) * db.pageSize if minsz >= db.datasz { if err := db.mmap(minsz); err != nil { return nil, fmt.Errorf("mmap allocate error: %s", err) } } // Move the page id high water mark. db.rwtx.meta.pgid += pgid(count) return p, nil } // grow grows the size of the database to the given sz. func (db *DB) grow(sz int) error { // Ignore if the new size is less than available file size. if sz <= db.filesz { return nil } // If the data is smaller than the alloc size then only allocate what's needed. // Once it goes over the allocation size then allocate in chunks. if db.datasz < db.AllocSize { sz = db.datasz } else { sz += db.AllocSize } // Truncate and fsync to ensure file size metadata is flushed. // https://github.com/boltdb/bolt/issues/284 if err := db.file.Truncate(int64(sz)); err != nil { return fmt.Errorf("file resize error: %s", err) } if err := db.file.Sync(); err != nil { return fmt.Errorf("file sync error: %s", err) } db.filesz = sz return nil } // Sub calculates and returns the difference between two sets of database stats. // This is useful when obtaining stats at two different points and time and // you need the performance counters that occurred within that time span. func (s *Stats) Sub(other *Stats) Stats { if other == nil { return *s } var diff Stats diff.FreePageN = s.FreePageN diff.PendingPageN = s.PendingPageN diff.FreeAlloc = s.FreeAlloc diff.FreelistInuse = s.FreelistInuse diff.TxN = s.TxN - other.TxN diff.TxStats = s.TxStats.Sub(&other.TxStats) return diff } func (s *Stats) add(other *Stats) { s.TxStats.add(&other.TxStats) } // validate checks the marker bytes and version of the meta page to ensure it matches this binary. func (m *meta) validate() error { if m.magic != magic { return ErrInvalid } else if m.version != version { return ErrVersionMismatch } else if m.checksum != 0 && m.checksum != m.sum64() { return ErrChecksum } return nil } // copy copies one meta object to another. func (m *meta) copy(dest *meta) { *dest = *m } // write writes the meta onto a page. func (m *meta) write(p *page) { if m.root.root >= m.pgid { panic(fmt.Sprintf("root bucket pgid (%d) above high water mark (%d)", m.root.root, m.pgid)) } else if m.freelist >= m.pgid { panic(fmt.Sprintf("freelist pgid (%d) above high water mark (%d)", m.freelist, m.pgid)) } // Page id is either going to be 0 or 1 which we can determine by the transaction ID. p.id = pgid(m.txid % 2) p.flags |= metaPageFlag // Calculate the checksum. m.checksum = m.sum64() m.copy(p.meta()) } // generates the checksum for the meta. func (m *meta) sum64() uint64 { var h = fnv.New64a() _, _ = h.Write((*[unsafe.Offsetof(meta{}.checksum)]byte)(unsafe.Pointer(m))[:]) return h.Sum64() } // _assert will panic with a given formatted message if the given condition is false. func _assert(condition bool, msg string, v ...interface{}) { if !condition { panic(fmt.Sprintf("assertion failed: "+msg, v...)) } } func warn(v ...interface{}) { fmt.Fprintln(os.Stderr, v...) } func warnf(msg string, v ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", v...) } func printstack() { stack := strings.Join(strings.Split(string(debug.Stack()), "\n")[2:], "\n") fmt.Fprintln(os.Stderr, stack) } /* Package bolt implements a low-level key/value store in pure Go. It supports fully serializable transactions, ACID semantics, and lock-free MVCC with multiple readers and a single writer. Bolt can be used for projects that want a simple data store without the need to add large dependencies such as Postgres or MySQL. Bolt is a single-level, zero-copy, B+tree data store. This means that Bolt is optimized for fast read access and does not require recovery in the event of a system crash. Transactions which have not finished committing will simply be rolled back in the event of a crash. The design of Bolt is based on Howard Chu's LMDB database project. Bolt currently works on Windows, Mac OS X, and Linux. Basics There are only a few types in Bolt: DB, Bucket, Tx, and Cursor. The DB is a collection of buckets and is represented by a single file on disk. A bucket is a collection of unique keys that are associated with values. Transactions provide either read-only or read-write access to the database. Read-only transactions can retrieve key/value pairs and can use Cursors to iterate over the dataset sequentially. Read-write transactions can create and delete buckets and can insert and remove keys. Only one read-write transaction is allowed at a time. Caveats The database uses a read-only, memory-mapped data file to ensure that applications cannot corrupt the database, however, this means that keys and values returned from Bolt cannot be changed. Writing to a read-only byte slice will cause Go to panic. Keys and values retrieved from the database are only valid for the life of the transaction. When used outside the transaction, these byte slices can point to different data or can point to invalid memory which will cause a panic. */ // newFreelist returns an empty, initialized freelist. func newFreelist() *freelist { return &freelist{ pending: make(map[txid][]pgid), cache: make(map[pgid]bool), } } // size returns the size of the page after serialization. func (f *freelist) size() int { n := f.count() if n >= 0xFFFF { // The first element will be used to store the count. See freelist.write. n++ } return pageHeaderSize + (int(unsafe.Sizeof(pgid(0))) * n) } // count returns count of pages on the freelist func (f *freelist) count() int { return f.free_count() + f.pending_count() } // free_count returns count of free pages func (f *freelist) free_count() int { return len(f.ids) } // pending_count returns count of pending pages func (f *freelist) pending_count() int { var count int for _, list := range f.pending { count += len(list) } return count } // copyall copies into dst a list of all free ids and all pending ids in one sorted list. // f.count returns the minimum length required for dst. func (f *freelist) copyall(dst []pgid) { m := make(pgids, 0, f.pending_count()) for _, list := range f.pending { m = append(m, list...) } sort.Sort(m) mergepgids(dst, f.ids, m) } // allocate returns the starting page id of a contiguous list of pages of a given size. // If a contiguous block cannot be found then 0 is returned. func (f *freelist) allocate(n int) pgid { if len(f.ids) == 0 { return 0 } var initial, previd pgid for i, id := range f.ids { if id <= 1 { panic(fmt.Sprintf("invalid page allocation: %d", id)) } // Reset initial page if this is not contiguous. if previd == 0 || id-previd != 1 { initial = id } // If we found a contiguous block then remove it and return it. if (id-initial)+1 == pgid(n) { // If we're allocating off the beginning then take the fast path // and just adjust the existing slice. This will use extra memory // temporarily but the append() in free() will realloc the slice // as is necessary. if (i + 1) == n { f.ids = f.ids[i+1:] } else { copy(f.ids[i-n+1:], f.ids[i+1:]) f.ids = f.ids[:len(f.ids)-n] } // Remove from the free cache. for i := pgid(0); i < pgid(n); i++ { delete(f.cache, initial+i) } return initial } previd = id } return 0 } // free releases a page and its overflow for a given transaction id. // If the page is already free then a panic will occur. func (f *freelist) free(txid txid, p *page) { if p.id <= 1 { panic(fmt.Sprintf("cannot free page 0 or 1: %d", p.id)) } // Free page and all its overflow pages. var ids = f.pending[txid] for id := p.id; id <= p.id+pgid(p.overflow); id++ { // Verify that page is not already free. if f.cache[id] { panic(fmt.Sprintf("page %d already freed", id)) } // Add to the freelist and cache. ids = append(ids, id) f.cache[id] = true } f.pending[txid] = ids } // release moves all page ids for a transaction id (or older) to the freelist. func (f *freelist) release(txid txid) { m := make(pgids, 0) for tid, ids := range f.pending { if tid <= txid { // Move transaction's pending pages to the available freelist. // Don't remove from the cache since the page is still free. m = append(m, ids...) delete(f.pending, tid) } } sort.Sort(m) f.ids = pgids(f.ids).merge(m) } // rollback removes the pages from a given pending tx. func (f *freelist) rollback(txid txid) { // Remove page ids from cache. for _, id := range f.pending[txid] { delete(f.cache, id) } // Remove pages from pending list. delete(f.pending, txid) } // freed returns whether a given page is in the free list. func (f *freelist) freed(pgid pgid) bool { return f.cache[pgid] } // read initializes the freelist from a freelist page. func (f *freelist) read(p *page) { // If the page.count is at the max uint16 value (64k) then it's considered // an overflow and the size of the freelist is stored as the first element. idx, count := 0, int(p.count) if count == 0xFFFF { idx = 1 count = int(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0]) } // Copy the list of page ids from the freelist. if count == 0 { f.ids = nil } else { ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx:count] f.ids = make([]pgid, len(ids)) copy(f.ids, ids) // Make sure they're sorted. sort.Sort(pgids(f.ids)) } // Rebuild the page cache. f.reindex() } // write writes the page ids onto a freelist page. All free and pending ids are // saved to disk since in the event of a program crash, all pending ids will // become free. func (f *freelist) write(p *page) error { // Combine the old free pgids and pgids waiting on an open transaction. // Update the header flag. p.flags |= freelistPageFlag // The page.count can only hold up to 64k elements so if we overflow that // number then we handle it by putting the size in the first element. lenids := f.count() if lenids == 0 { p.count = uint16(lenids) } else if lenids < 0xFFFF { p.count = uint16(lenids) f.copyall(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[:]) } else { p.count = 0xFFFF ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0] = pgid(lenids) f.copyall(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[1:]) } return nil } // reload reads the freelist from a page and filters out pending items. func (f *freelist) reload(p *page) { f.read(p) // Build a cache of only pending pages. pcache := make(map[pgid]bool) for _, pendingIDs := range f.pending { for _, pendingID := range pendingIDs { pcache[pendingID] = true } } // Check each page in the freelist and build a new available freelist // with any pages not in the pending lists. var a []pgid for _, id := range f.ids { if !pcache[id] { a = append(a, id) } } f.ids = a // Once the available list is rebuilt then rebuild the free cache so that // it includes the available and pending free pages. f.reindex() } // reindex rebuilds the free cache based on available and pending free lists. func (f *freelist) reindex() { f.cache = make(map[pgid]bool, len(f.ids)) for _, id := range f.ids { f.cache[id] = true } for _, pendingIDs := range f.pending { for _, pendingID := range pendingIDs { f.cache[pendingID] = true } } } // root returns the top-level node this node is attached to. func (n *node) root() *node { if n.parent == nil { return n } return n.parent.root() } // minKeys returns the minimum number of inodes this node should have. func (n *node) minKeys() int { if n.isLeaf { return 1 } return 2 } // size returns the size of the node after serialization. func (n *node) size() int { sz, elsz := pageHeaderSize, n.pageElementSize() for i := 0; i < len(n.inodes); i++ { item := &n.inodes[i] sz += elsz + len(item.key) + len(item.value) } return sz } // sizeLessThan returns true if the node is less than a given size. // This is an optimization to avoid calculating a large node when we only need // to know if it fits inside a certain page size. func (n *node) sizeLessThan(v int) bool { sz, elsz := pageHeaderSize, n.pageElementSize() for i := 0; i < len(n.inodes); i++ { item := &n.inodes[i] sz += elsz + len(item.key) + len(item.value) if sz >= v { return false } } return true } // pageElementSize returns the size of each page element based on the type of node. func (n *node) pageElementSize() int { if n.isLeaf { return leafPageElementSize } return branchPageElementSize } // childAt returns the child node at a given index. func (n *node) childAt(index int) *node { if n.isLeaf { panic(fmt.Sprintf("invalid childAt(%d) on a leaf node", index)) } return n.bucket.node(n.inodes[index].pgid, n) } // childIndex returns the index of a given child node. func (n *node) childIndex(child *node) int { index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, child.key) != -1 }) return index } // numChildren returns the number of children. func (n *node) numChildren() int { return len(n.inodes) } // nextSibling returns the next node with the same parent. func (n *node) nextSibling() *node { if n.parent == nil { return nil } index := n.parent.childIndex(n) if index >= n.parent.numChildren()-1 { return nil } return n.parent.childAt(index + 1) } // prevSibling returns the previous node with the same parent. func (n *node) prevSibling() *node { if n.parent == nil { return nil } index := n.parent.childIndex(n) if index == 0 { return nil } return n.parent.childAt(index - 1) } // put inserts a key/value. func (n *node) put(oldKey, newKey, value []byte, pgid pgid, flags uint32) { if pgid >= n.bucket.tx.meta.pgid { panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", pgid, n.bucket.tx.meta.pgid)) } else if len(oldKey) <= 0 { panic("put: zero-length old key") } else if len(newKey) <= 0 { panic("put: zero-length new key") } // Find insertion index. index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, oldKey) != -1 }) // Add capacity and shift nodes if we don't have an exact match and need to insert. exact := (len(n.inodes) > 0 && index < len(n.inodes) && bytes.Equal(n.inodes[index].key, oldKey)) if !exact { n.inodes = append(n.inodes, inode{}) copy(n.inodes[index+1:], n.inodes[index:]) } inode := &n.inodes[index] inode.flags = flags inode.key = newKey inode.value = value inode.pgid = pgid _assert(len(inode.key) > 0, "put: zero-length inode key") } // del removes a key from the node. func (n *node) del(key []byte) { // Find index of key. index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, key) != -1 }) // Exit if the key isn't found. if index >= len(n.inodes) || !bytes.Equal(n.inodes[index].key, key) { return } // Delete inode from the node. n.inodes = append(n.inodes[:index], n.inodes[index+1:]...) // Mark the node as needing rebalancing. n.unbalanced = true } // read initializes the node from a page. func (n *node) read(p *page) { n.pgid = p.id n.isLeaf = ((p.flags & leafPageFlag) != 0) n.inodes = make(inodes, int(p.count)) for i := 0; i < int(p.count); i++ { inode := &n.inodes[i] if n.isLeaf { elem := p.leafPageElement(uint16(i)) inode.flags = elem.flags inode.key = elem.key() inode.value = elem.value() } else { elem := p.branchPageElement(uint16(i)) inode.pgid = elem.pgid inode.key = elem.key() } _assert(len(inode.key) > 0, "read: zero-length inode key") } // Save first key so we can find the node in the parent when we spill. if len(n.inodes) > 0 { n.key = n.inodes[0].key _assert(len(n.key) > 0, "read: zero-length node key") } else { n.key = nil } } // write writes the items onto one or more pages. func (n *node) write(p *page) { // Initialize page. if n.isLeaf { p.flags |= leafPageFlag } else { p.flags |= branchPageFlag } if len(n.inodes) >= 0xFFFF { panic(fmt.Sprintf("inode overflow: %d (pgid=%d)", len(n.inodes), p.id)) } p.count = uint16(len(n.inodes)) // Stop here if there are no items to write. if p.count == 0 { return } // Loop over each item and write it to the page. b := (*[maxAllocSize]byte)(unsafe.Pointer(&p.ptr))[n.pageElementSize()*len(n.inodes):] for i, item := range n.inodes { _assert(len(item.key) > 0, "write: zero-length inode key") // Write the page element. if n.isLeaf { elem := p.leafPageElement(uint16(i)) elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem))) elem.flags = item.flags elem.ksize = uint32(len(item.key)) elem.vsize = uint32(len(item.value)) } else { elem := p.branchPageElement(uint16(i)) elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem))) elem.ksize = uint32(len(item.key)) elem.pgid = item.pgid _assert(elem.pgid != p.id, "write: circular dependency occurred") } // If the length of key+value is larger than the max allocation size // then we need to reallocate the byte array pointer. // // See: https://github.com/boltdb/bolt/pull/335 klen, vlen := len(item.key), len(item.value) if len(b) < klen+vlen { b = (*[maxAllocSize]byte)(unsafe.Pointer(&b[0]))[:] } // Write data for the element to the end of the page. copy(b[0:], item.key) b = b[klen:] copy(b[0:], item.value) b = b[vlen:] } // DEBUG ONLY: n.dump() } // split breaks up a node into multiple smaller nodes, if appropriate. // This should only be called from the spill() function. func (n *node) split(pageSize int) []*node { var nodes []*node node := n for { // Split node into two. a, b := node.splitTwo(pageSize) nodes = append(nodes, a) // If we can't split then exit the loop. if b == nil { break } // Set node to b so it gets split on the next iteration. node = b } return nodes } // splitTwo breaks up a node into two smaller nodes, if appropriate. // This should only be called from the split() function. func (n *node) splitTwo(pageSize int) (*node, *node) { const fillPercent = 0.5 // Ignore the split if the page doesn't have at least enough nodes for // two pages or if the nodes can fit in a single page. if len(n.inodes) <= (minKeysPerPage*2) || n.sizeLessThan(pageSize) { return n, nil } threshold := int(float64(pageSize) * fillPercent) // Determine split position and sizes of the two pages. splitIndex, _ := n.splitIndex(threshold) // Split node into two separate nodes. // If there's no parent then we'll need to create one. if n.parent == nil { n.parent = &node{bucket: n.bucket, children: []*node{n}} } // Create a new node and add it to the parent. next := &node{bucket: n.bucket, isLeaf: n.isLeaf, parent: n.parent} n.parent.children = append(n.parent.children, next) // Split inodes across two nodes. next.inodes = n.inodes[splitIndex:] n.inodes = n.inodes[:splitIndex] // Update the statistics. n.bucket.tx.stats.Split++ return n, next } // splitIndex finds the position where a page will fill a given threshold. // It returns the index as well as the size of the first page. // This is only be called from split(). func (n *node) splitIndex(threshold int) (index, sz int) { sz = pageHeaderSize // Loop until we only have the minimum number of keys required for the second page. for i := 0; i < len(n.inodes)-minKeysPerPage; i++ { index = i inode := n.inodes[i] elsize := n.pageElementSize() + len(inode.key) + len(inode.value) // If we have at least the minimum number of keys and adding another // node would put us over the threshold then exit and return. if i >= minKeysPerPage && sz+elsize > threshold { break } // Add the element size to the total size. sz += elsize } return } // spill writes the nodes to dirty pages and splits nodes as it goes. // Returns an error if dirty pages cannot be allocated. func (n *node) spill() error { var tx = n.bucket.tx if n.spilled { return nil } // Spill child nodes first. Child nodes can materialize sibling nodes in // the case of split-merge so we cannot use a range loop. We have to check // the children size on every loop iteration. sort.Sort(n.children) for i := 0; i < len(n.children); i++ { if err := n.children[i].spill(); err != nil { return err } } // We no longer need the child list because it's only used for spill tracking. n.children = nil // Split nodes into appropriate sizes. The first node will always be n. var nodes = n.split(tx.db.pageSize) for _, node := range nodes { // Add node's page to the freelist if it's not new. if node.pgid > 0 { tx.db.freelist.free(tx.meta.txid, tx.page(node.pgid)) node.pgid = 0 } // Allocate contiguous space for the node. p, err := tx.allocate((node.size() / tx.db.pageSize) + 1) if err != nil { return err } // Write the node. if p.id >= tx.meta.pgid { panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", p.id, tx.meta.pgid)) } node.pgid = p.id node.write(p) node.spilled = true // Insert into parent inodes. if node.parent != nil { var key = node.key if key == nil { key = node.inodes[0].key } node.parent.put(key, node.inodes[0].key, nil, node.pgid, 0) node.key = node.inodes[0].key _assert(len(node.key) > 0, "spill: zero-length node key") } // Update the statistics. tx.stats.Spill++ } // If the root node split and created a new root then we need to spill that // as well. We'll clear out the children to make sure it doesn't try to respill. if n.parent != nil && n.parent.pgid == 0 { n.children = nil return n.parent.spill() } return nil } // rebalance attempts to combine the node with sibling nodes if the node fill // size is below a threshold or if there are not enough keys. func (n *node) rebalance() { if !n.unbalanced { return } n.unbalanced = false // Update statistics. n.bucket.tx.stats.Rebalance++ // Ignore if node is above threshold (25%) and has enough keys. var threshold = n.bucket.tx.db.pageSize / 4 if n.size() > threshold && len(n.inodes) > n.minKeys() { return } // Root node has special handling. if n.parent == nil { // If root node is a branch and only has one node then collapse it. if !n.isLeaf && len(n.inodes) == 1 { // Move root's child up. child := n.bucket.node(n.inodes[0].pgid, n) n.isLeaf = child.isLeaf n.inodes = child.inodes[:] n.children = child.children // Reparent all child nodes being moved. for _, inode := range n.inodes { if child, ok := n.bucket.nodes[inode.pgid]; ok { child.parent = n } } // Remove old child. child.parent = nil delete(n.bucket.nodes, child.pgid) child.free() } return } // If node has no keys then just remove it. if n.numChildren() == 0 { n.parent.del(n.key) n.parent.removeChild(n) delete(n.bucket.nodes, n.pgid) n.free() n.parent.rebalance() return } _assert(n.parent.numChildren() > 1, "parent must have at least 2 children") // Destination node is right sibling if idx == 0, otherwise left sibling. var target *node var useNextSibling = (n.parent.childIndex(n) == 0) if useNextSibling { target = n.nextSibling() } else { target = n.prevSibling() } // If both this node and the target node are too small then merge them. if useNextSibling { // Reparent all child nodes being moved. for _, inode := range target.inodes { if child, ok := n.bucket.nodes[inode.pgid]; ok { child.parent.removeChild(child) child.parent = n child.parent.children = append(child.parent.children, child) } } // Copy over inodes from target and remove target. n.inodes = append(n.inodes, target.inodes...) n.parent.del(target.key) n.parent.removeChild(target) delete(n.bucket.nodes, target.pgid) target.free() } else { // Reparent all child nodes being moved. for _, inode := range n.inodes { if child, ok := n.bucket.nodes[inode.pgid]; ok { child.parent.removeChild(child) child.parent = target child.parent.children = append(child.parent.children, child) } } // Copy over inodes to target and remove node. target.inodes = append(target.inodes, n.inodes...) n.parent.del(n.key) n.parent.removeChild(n) delete(n.bucket.nodes, n.pgid) n.free() } // Either this node or the target node was deleted from the parent so rebalance it. n.parent.rebalance() } // removes a node from the list of in-memory children. // This does not affect the inodes. func (n *node) removeChild(target *node) { for i, child := range n.children { if child == target { n.children = append(n.children[:i], n.children[i+1:]...) return } } } // dereference causes the node to copy all its inode key/value references to heap memory. // This is required when the mmap is reallocated so inodes are not pointing to stale data. func (n *node) dereference() { if n.key != nil { key := make([]byte, len(n.key)) copy(key, n.key) n.key = key _assert(n.pgid == 0 || len(n.key) > 0, "dereference: zero-length node key on existing node") } for i := range n.inodes { inode := &n.inodes[i] key := make([]byte, len(inode.key)) copy(key, inode.key) inode.key = key _assert(len(inode.key) > 0, "dereference: zero-length inode key") value := make([]byte, len(inode.value)) copy(value, inode.value) inode.value = value } // Recursively dereference children. for _, child := range n.children { child.dereference() } // Update statistics. n.bucket.tx.stats.NodeDeref++ } // free adds the node's underlying page to the freelist. func (n *node) free() { if n.pgid != 0 { n.bucket.tx.db.freelist.free(n.bucket.tx.meta.txid, n.bucket.tx.page(n.pgid)) n.pgid = 0 } } // dump writes the contents of the node to STDERR for debugging purposes. /* func (n *node) dump() { // Write node header. var typ = "branch" if n.isLeaf { typ = "leaf" } warnf("[NODE %d {type=%s count=%d}]", n.pgid, typ, len(n.inodes)) // Write out abbreviated version of each item. for _, item := range n.inodes { if n.isLeaf { if item.flags&bucketLeafFlag != 0 { bucket := (*bucket)(unsafe.Pointer(&item.value[0])) warnf("+L %08x -> (bucket root=%d)", trunc(item.key, 4), bucket.root) } else { warnf("+L %08x -> %08x", trunc(item.key, 4), trunc(item.value, 4)) } } else { warnf("+B %08x -> pgid=%d", trunc(item.key, 4), item.pgid) } } warn("") } */ func (s nodes) Len() int { return len(s) } func (s nodes) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s nodes) Less(i, j int) bool { return bytes.Compare(s[i].inodes[0].key, s[j].inodes[0].key) == -1 } // typ returns a human readable page type string used for debugging. func (p *page) typ() string { if (p.flags & branchPageFlag) != 0 { return "branch" } else if (p.flags & leafPageFlag) != 0 { return "leaf" } else if (p.flags & metaPageFlag) != 0 { return "meta" } else if (p.flags & freelistPageFlag) != 0 { return "freelist" } return fmt.Sprintf("unknown<%02x>", p.flags) } // meta returns a pointer to the metadata section of the page. func (p *page) meta() *meta { return (*meta)(unsafe.Pointer(&p.ptr)) } // leafPageElement retrieves the leaf node by index func (p *page) leafPageElement(index uint16) *leafPageElement { n := &((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[index] return n } // leafPageElements retrieves a list of leaf nodes. func (p *page) leafPageElements() []leafPageElement { if p.count == 0 { return nil } return ((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[:] } // branchPageElement retrieves the branch node by index func (p *page) branchPageElement(index uint16) *branchPageElement { return &((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[index] } // branchPageElements retrieves a list of branch nodes. func (p *page) branchPageElements() []branchPageElement { if p.count == 0 { return nil } return ((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[:] } // dump writes n bytes of the page to STDERR as hex output. func (p *page) hexdump(n int) string { buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:n] return fmt.Sprintf("%x\n", buf) } func (s pages) Len() int { return len(s) } func (s pages) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s pages) Less(i, j int) bool { return s[i].id < s[j].id } // key returns a byte slice of the node key. func (n *branchPageElement) key() []byte { buf := (*[maxAllocSize]byte)(unsafe.Pointer(n)) return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize] } // key returns a byte slice of the node key. func (n *leafPageElement) key() []byte { buf := (*[maxAllocSize]byte)(unsafe.Pointer(n)) return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize:n.ksize] } // value returns a byte slice of the node value. func (n *leafPageElement) value() []byte { buf := (*[maxAllocSize]byte)(unsafe.Pointer(n)) return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos+n.ksize]))[:n.vsize:n.vsize] } func (s pgids) Len() int { return len(s) } func (s pgids) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s pgids) Less(i, j int) bool { return s[i] < s[j] } // merge returns the sorted union of a and b. func (a pgids) merge(b pgids) pgids { // Return the opposite slice if one is nil. if len(a) == 0 { return b } if len(b) == 0 { return a } merged := make(pgids, len(a)+len(b)) mergepgids(merged, a, b) return merged } // mergepgids copies the sorted union of a and b into dst. // If dst is too small, it panics. func mergepgids(dst, a, b pgids) { if len(dst) < len(a)+len(b) { panic(fmt.Errorf("mergepgids bad len %d < %d + %d", len(dst), len(a), len(b))) } // Copy in the opposite slice if one is nil. if len(a) == 0 { copy(dst, b) return } if len(b) == 0 { copy(dst, a) return } // Merged will hold all elements from both lists. merged := dst[:0] // Assign lead to the slice with a lower starting value, follow to the higher value. lead, follow := a, b if b[0] < a[0] { lead, follow = b, a } // Continue while there are elements in the lead. for len(lead) > 0 { // Merge largest prefix of lead that is ahead of follow[0]. n := sort.Search(len(lead), func(i int) bool { return lead[i] > follow[0] }) merged = append(merged, lead[:n]...) if n >= len(lead) { break } // Swap lead and follow. lead, follow = follow, lead[n:] } // Append what's left in follow. _ = append(merged, follow...) } // init initializes the transaction. func (tx *Tx) init(db *DB) { tx.db = db tx.pages = nil // Copy the meta page since it can be changed by the writer. tx.meta = &meta{} db.meta().copy(tx.meta) // Copy over the root bucket. tx.root = newBucket(tx) tx.root.ref = &bucket{} *tx.root.ref = tx.meta.root // Increment the transaction id and add a page cache for writable transactions. if tx.writable { tx.pages = make(map[pgid]*page) tx.meta.txid += txid(1) } } // ID returns the transaction id. func (tx *Tx) ID() int { return int(tx.meta.txid) } // DB returns a reference to the database that created the transaction. func (tx *Tx) DB() *DB { return tx.db } // Size returns current database size in bytes as seen by this transaction. func (tx *Tx) Size() int64 { return int64(tx.meta.pgid) * int64(tx.db.pageSize) } // Writable returns whether the transaction can perform write operations. func (tx *Tx) Writable() bool { return tx.writable } // Cursor creates a cursor associated with the root bucket. // All items in the cursor will return a nil value because all root bucket keys point to buckets. // The cursor is only valid as long as the transaction is open. // Do not use a cursor after the transaction is closed. func (tx *Tx) Cursor() *Cursor { return tx.root.Cursor() } // Stats retrieves a copy of the current transaction statistics. func (tx *Tx) Stats() TxStats { return tx.stats } // Bucket retrieves a bucket by name. // Returns nil if the bucket does not exist. // The bucket instance is only valid for the lifetime of the transaction. func (tx *Tx) Bucket(name []byte) *Bucket { return tx.root.Bucket(name) } // CreateBucket creates a new bucket. // Returns an error if the bucket already exists, if the bucket name is blank, or if the bucket name is too long. // The bucket instance is only valid for the lifetime of the transaction. func (tx *Tx) CreateBucket(name []byte) (*Bucket, error) { return tx.root.CreateBucket(name) } // CreateBucketIfNotExists creates a new bucket if it doesn't already exist. // Returns an error if the bucket name is blank, or if the bucket name is too long. // The bucket instance is only valid for the lifetime of the transaction. func (tx *Tx) CreateBucketIfNotExists(name []byte) (*Bucket, error) { return tx.root.CreateBucketIfNotExists(name) } // DeleteBucket deletes a bucket. // Returns an error if the bucket cannot be found or if the key represents a non-bucket value. func (tx *Tx) DeleteBucket(name []byte) error { return tx.root.DeleteBucket(name) } // ForEach executes a function for each bucket in the root. // If the provided function returns an error then the iteration is stopped and // the error is returned to the caller. func (tx *Tx) ForEach(fn func(name []byte, b *Bucket) error) error { return tx.root.ForEach(func(k, v []byte) error { if err := fn(k, tx.root.Bucket(k)); err != nil { return err } return nil }) } // OnCommit adds a handler function to be executed after the transaction successfully commits. func (tx *Tx) OnCommit(fn func()) { tx.commitHandlers = append(tx.commitHandlers, fn) } // Commit writes all changes to disk and updates the meta page. // Returns an error if a disk write error occurs, or if Commit is // called on a read-only transaction. func (tx *Tx) Commit() error { _assert(!tx.managed, "managed tx commit not allowed") if tx.db == nil { return ErrTxClosed } else if !tx.writable { return ErrTxNotWritable } // TODO(benbjohnson): Use vectorized I/O to write out dirty pages. // Rebalance nodes which have had deletions. var startTime = time.Now() tx.root.rebalance() if tx.stats.Rebalance > 0 { tx.stats.RebalanceTime += time.Since(startTime) } // spill data onto dirty pages. startTime = time.Now() if err := tx.root.spill(); err != nil { tx.rollback() return err } tx.stats.SpillTime += time.Since(startTime) // Free the old root bucket. tx.meta.root.root = tx.root.ref.root opgid := tx.meta.pgid // Free the freelist and allocate new pages for it. This will overestimate // the size of the freelist but not underestimate the size (which would be bad). tx.db.freelist.free(tx.meta.txid, tx.db.page(tx.meta.freelist)) p, err := tx.allocate((tx.db.freelist.size() / tx.db.pageSize) + 1) if err != nil { tx.rollback() return err } if err := tx.db.freelist.write(p); err != nil { tx.rollback() return err } tx.meta.freelist = p.id // If the high water mark has moved up then attempt to grow the database. if tx.meta.pgid > opgid { if err := tx.db.grow(int(tx.meta.pgid+1) * tx.db.pageSize); err != nil { tx.rollback() return err } } // Write dirty pages to disk. startTime = time.Now() if err := tx.write(); err != nil { tx.rollback() return err } // If strict mode is enabled then perform a consistency check. // Only the first consistency error is reported in the panic. if tx.db.StrictMode { ch := tx.Check() var errs []string for { err, ok := <-ch if !ok { break } errs = append(errs, err.Error()) } if len(errs) > 0 { panic("check fail: " + strings.Join(errs, "\n")) } } // Write meta to disk. if err := tx.writeMeta(); err != nil { tx.rollback() return err } tx.stats.WriteTime += time.Since(startTime) // Finalize the transaction. tx.close() // Execute commit handlers now that the locks have been removed. for _, fn := range tx.commitHandlers { fn() } return nil } // Rollback closes the transaction and ignores all previous updates. Read-only // transactions must be rolled back and not committed. func (tx *Tx) Rollback() error { _assert(!tx.managed, "managed tx rollback not allowed") if tx.db == nil { return ErrTxClosed } tx.rollback() return nil } func (tx *Tx) rollback() { if tx.db == nil { return } if tx.writable { tx.db.freelist.rollback(tx.meta.txid) tx.db.freelist.reload(tx.db.page(tx.db.meta().freelist)) } tx.close() } func (tx *Tx) close() { if tx.db == nil { return } if tx.writable { // Grab freelist stats. var freelistFreeN = tx.db.freelist.free_count() var freelistPendingN = tx.db.freelist.pending_count() var freelistAlloc = tx.db.freelist.size() // Remove transaction ref & writer lock. tx.db.rwtx = nil tx.db.rwlock.Unlock() // Merge statistics. tx.db.statlock.Lock() tx.db.stats.FreePageN = freelistFreeN tx.db.stats.PendingPageN = freelistPendingN tx.db.stats.FreeAlloc = (freelistFreeN + freelistPendingN) * tx.db.pageSize tx.db.stats.FreelistInuse = freelistAlloc tx.db.stats.TxStats.add(&tx.stats) tx.db.statlock.Unlock() } else { tx.db.removeTx(tx) } // Clear all references. tx.db = nil tx.meta = nil tx.root = Bucket{tx: tx} tx.pages = nil } // Copy writes the entire database to a writer. // This function exists for backwards compatibility. // // Deprecated; Use WriteTo() instead. func (tx *Tx) Copy(w io.Writer) error { _, err := tx.WriteTo(w) return err } // WriteTo writes the entire database to a writer. // If err == nil then exactly tx.Size() bytes will be written into the writer. func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) { f, err := os.OpenFile(tx.db.path, os.O_RDONLY, 0) if err != nil { return 0, err } defer f.Close() // Generate a meta page. We use the same page data for both meta pages. buf := make([]byte, tx.db.pageSize) page := (*page)(unsafe.Pointer(&buf[0])) page.flags = metaPageFlag *page.meta() = *tx.meta // Write meta 0. page.id = 0 page.meta().checksum = page.meta().sum64() nn, err := w.Write(buf) n += int64(nn) if err != nil { return n, fmt.Errorf("meta 0 copy: %s", err) } // Write meta 1 with a lower transaction id. page.id = 1 page.meta().txid -= 1 page.meta().checksum = page.meta().sum64() nn, err = w.Write(buf) n += int64(nn) if err != nil { return n, fmt.Errorf("meta 1 copy: %s", err) } // Move past the meta pages in the file. if _, err := f.Seek(int64(tx.db.pageSize*2), os.SEEK_SET); err != nil { return n, fmt.Errorf("seek: %s", err) } // Copy data pages. wn, err := io.CopyN(w, f, tx.Size()-int64(tx.db.pageSize*2)) n += wn if err != nil { return n, err } return n, f.Close() } // CopyFile copies the entire database to file at the given path. // A reader transaction is maintained during the copy so it is safe to continue // using the database while a copy is in progress. func (tx *Tx) CopyFile(path string, mode os.FileMode) error { f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode) if err != nil { return err } err = tx.Copy(f) if err != nil { _ = f.Close() return err } return f.Close() } // Check performs several consistency checks on the database for this transaction. // An error is returned if any inconsistency is found. // // It can be safely run concurrently on a writable transaction. However, this // incurs a high cost for large databases and databases with a lot of subbuckets // because of caching. This overhead can be removed if running on a read-only // transaction, however, it is not safe to execute other writer transactions at // the same time. func (tx *Tx) Check() <-chan error { ch := make(chan error) go tx.check(ch) return ch } func (tx *Tx) check(ch chan error) { // Check if any pages are double freed. freed := make(map[pgid]bool) all := make([]pgid, tx.db.freelist.count()) tx.db.freelist.copyall(all) for _, id := range all { if freed[id] { ch <- fmt.Errorf("page %d: already freed", id) } freed[id] = true } // Track every reachable page. reachable := make(map[pgid]*page) reachable[0] = tx.page(0) // meta0 reachable[1] = tx.page(1) // meta1 for i := uint32(0); i <= tx.page(tx.meta.freelist).overflow; i++ { reachable[tx.meta.freelist+pgid(i)] = tx.page(tx.meta.freelist) } // Recursively check buckets. tx.checkBucket(&tx.root, reachable, freed, ch) // Ensure all pages below high water mark are either reachable or freed. for i := pgid(0); i < tx.meta.pgid; i++ { _, isReachable := reachable[i] if !isReachable && !freed[i] { ch <- fmt.Errorf("page %d: unreachable unfreed", int(i)) } } // Close the channel to signal completion. close(ch) } func (tx *Tx) checkBucket(b *Bucket, reachable map[pgid]*page, freed map[pgid]bool, ch chan error) { // Ignore inline buckets. if b.ref.root == 0 { return } // Check every page used by this bucket. b.tx.forEachPage(b.ref.root, 0, func(p *page, _ int) { if p.id > tx.meta.pgid { ch <- fmt.Errorf("page %d: out of bounds: %d", int(p.id), int(b.tx.meta.pgid)) } // Ensure each page is only referenced once. for i := pgid(0); i <= pgid(p.overflow); i++ { var id = p.id + i if _, ok := reachable[id]; ok { ch <- fmt.Errorf("page %d: multiple references", int(id)) } reachable[id] = p } // We should only encounter un-freed leaf and branch pages. if freed[p.id] { ch <- fmt.Errorf("page %d: reachable freed", int(p.id)) } else if (p.flags&branchPageFlag) == 0 && (p.flags&leafPageFlag) == 0 { ch <- fmt.Errorf("page %d: invalid type: %s", int(p.id), p.typ()) } }) // Check each bucket within this bucket. _ = b.ForEach(func(k, v []byte) error { if child := b.Bucket(k); child != nil { tx.checkBucket(child, reachable, freed, ch) } return nil }) } // allocate returns a contiguous block of memory starting at a given page. func (tx *Tx) allocate(count int) (*page, error) { p, err := tx.db.allocate(count) if err != nil { return nil, err } // Save to our page cache. tx.pages[p.id] = p // Update statistics. tx.stats.PageCount++ tx.stats.PageAlloc += count * tx.db.pageSize return p, nil } // write writes any dirty pages to disk. func (tx *Tx) write() error { // Sort pages by id. pages := make(pages, 0, len(tx.pages)) for _, p := range tx.pages { pages = append(pages, p) } // Clear out page cache early. tx.pages = make(map[pgid]*page) sort.Sort(pages) // Write pages to disk in order. for _, p := range pages { size := (int(p.overflow) + 1) * tx.db.pageSize offset := int64(p.id) * int64(tx.db.pageSize) // Write out page in "max allocation" sized chunks. ptr := (*[maxAllocSize]byte)(unsafe.Pointer(p)) for { // Limit our write to our max allocation size. sz := size if sz > maxAllocSize-1 { sz = maxAllocSize - 1 } // Write chunk to disk. buf := ptr[:sz] if _, err := tx.db.ops.writeAt(buf, offset); err != nil { return err } // Update statistics. tx.stats.Write++ // Exit inner for loop if we've written all the chunks. size -= sz if size == 0 { break } // Otherwise move offset forward and move pointer to next chunk. offset += int64(sz) ptr = (*[maxAllocSize]byte)(unsafe.Pointer(&ptr[sz])) } } if err := fdatasync(tx.db); err != nil { return err } // Put small pages back to page pool. for _, p := range pages { // Ignore page sizes over 1 page. // These are allocated using make() instead of the page pool. if int(p.overflow) != 0 { continue } buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:tx.db.pageSize] // See https://go.googlesource.com/go/+/f03c9202c43e0abb130669852082117ca50aa9b1 for i := range buf { buf[i] = 0 } tx.db.pagePool.Put(buf) } return nil } // writeMeta writes the meta to the disk. func (tx *Tx) writeMeta() error { // Create a temporary buffer for the meta page. buf := make([]byte, tx.db.pageSize) p := tx.db.pageInBuffer(buf, 0) tx.meta.write(p) // Write the meta page to file. if _, err := tx.db.ops.writeAt(buf, int64(p.id)*int64(tx.db.pageSize)); err != nil { return err } if err := fdatasync(tx.db); err != nil { return err } // Update statistics. tx.stats.Write++ return nil } // page returns a reference to the page with a given id. // If page has been written to then a temporary buffered page is returned. func (tx *Tx) page(id pgid) *page { // Check the dirty pages first. if tx.pages != nil { if p, ok := tx.pages[id]; ok { return p } } // Otherwise return directly from the mmap. return tx.db.page(id) } // forEachPage iterates over every page within a given page and executes a function. func (tx *Tx) forEachPage(pgid pgid, depth int, fn func(*page, int)) { p := tx.page(pgid) // Execute function. fn(p, depth) // Recursively loop over children. if (p.flags & branchPageFlag) != 0 { for i := 0; i < int(p.count); i++ { elem := p.branchPageElement(uint16(i)) tx.forEachPage(elem.pgid, depth+1, fn) } } } // Page returns page information for a given page number. // This is only safe for concurrent use when used by a writable transaction. func (tx *Tx) Page(id int) (*PageInfo, error) { if tx.db == nil { return nil, ErrTxClosed } else if pgid(id) >= tx.meta.pgid { return nil, nil } // Build the page info. p := tx.db.page(pgid(id)) info := &PageInfo{ ID: id, Count: int(p.count), OverflowCount: int(p.overflow), } // Determine the type (or if it's free). if tx.db.freelist.freed(pgid(id)) { info.Type = "free" } else { info.Type = p.typ() } return info, nil } func (s *TxStats) add(other *TxStats) { s.PageCount += other.PageCount s.PageAlloc += other.PageAlloc s.CursorCount += other.CursorCount s.NodeCount += other.NodeCount s.NodeDeref += other.NodeDeref s.Rebalance += other.Rebalance s.RebalanceTime += other.RebalanceTime s.Split += other.Split s.Spill += other.Spill s.SpillTime += other.SpillTime s.Write += other.Write s.WriteTime += other.WriteTime } // Sub calculates and returns the difference between two sets of transaction stats. // This is useful when obtaining stats at two different points and time and // you need the performance counters that occurred within that time span. func (s *TxStats) Sub(other *TxStats) TxStats { var diff TxStats diff.PageCount = s.PageCount - other.PageCount diff.PageAlloc = s.PageAlloc - other.PageAlloc diff.CursorCount = s.CursorCount - other.CursorCount diff.NodeCount = s.NodeCount - other.NodeCount diff.NodeDeref = s.NodeDeref - other.NodeDeref diff.Rebalance = s.Rebalance - other.Rebalance diff.RebalanceTime = s.RebalanceTime - other.RebalanceTime diff.Split = s.Split - other.Split diff.Spill = s.Spill - other.Spill diff.SpillTime = s.SpillTime - other.SpillTime diff.Write = s.Write - other.Write diff.WriteTime = s.WriteTime - other.WriteTime return diff } func Main() { m := NewMain() if err := m.Run(os.Args[1:]...); err == ErrUsage { os.Exit(2) } else if err != nil { fmt.Println(err.Error()) os.Exit(1) } } // NewMain returns a new instance of Main connect to the standard input/output. func NewMain() *MainT { return &MainT{ Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, } } // Run executes the program. func (m *MainT) Run(args ...string) error { // Require a command at the beginning. if len(args) == 0 || strings.HasPrefix(args[0], "-") { fmt.Fprintln(m.Stderr, m.Usage()) return ErrUsage } // Execute command. switch args[0] { case "help": fmt.Fprintln(m.Stderr, m.Usage()) return ErrUsage case "bench": return newBenchCommand(m).Run(args[1:]...) case "check": return newCheckCommand(m).Run(args[1:]...) case "compact": return newCompactCommand(m).Run(args[1:]...) case "dump": return newDumpCommand(m).Run(args[1:]...) case "info": return newInfoCommand(m).Run(args[1:]...) case "page": return newPageCommand(m).Run(args[1:]...) case "pages": return newPagesCommand(m).Run(args[1:]...) case "stats": return newStatsCommand(m).Run(args[1:]...) default: return ErrUnknownCommand } } // Usage returns the help message. func (m *MainT) Usage() string { return strings.TrimLeft(` Bolt is a tool for inspecting bolt databases. Usage: bolt command [arguments] The commands are: bench run synthetic benchmark against bolt check verifies integrity of bolt database compact copies a bolt database, compacting it in the process info print basic info help print this screen pages print list of pages with their types stats iterate over all pages and generate usage stats Use "bolt [command] -h" for more information about a command. `, "\n") } // NewCheckCommand returns a CheckCommand. func newCheckCommand(m *MainT) *CheckCommand { return &CheckCommand{ Stdin: m.Stdin, Stdout: m.Stdout, Stderr: m.Stderr, } } // Run executes the command. func (cmd *CheckCommand) Run(args ...string) error { // Parse flags. fs := flag.NewFlagSet("", flag.ContinueOnError) help := fs.Bool("h", false, "") if err := fs.Parse(args); err != nil { return err } else if *help { fmt.Fprintln(cmd.Stderr, cmd.Usage()) return ErrUsage } // Require database path. path := fs.Arg(0) if path == "" { return ErrPathRequired } else if _, err := os.Stat(path); os.IsNotExist(err) { return ErrFileNotFound } // Open database. db, err := Open(path) if err != nil { return err } defer db.Close() // Perform consistency check. return db.View(func(tx *Tx) error { var count int ch := tx.Check() loop: for { select { case err, ok := <-ch: if !ok { break loop } fmt.Fprintln(cmd.Stdout, err) count++ } } // Print summary of errors. if count > 0 { fmt.Fprintf(cmd.Stdout, "%d errors found\n", count) return ErrCorrupt } // Notify user that database is valid. fmt.Fprintln(cmd.Stdout, "OK") return nil }) } // Usage returns the help message. func (cmd *CheckCommand) Usage() string { return strings.TrimLeft(` usage: bolt check PATH Check opens a database at PATH and runs an exhaustive check to verify that all pages are accessible or are marked as freed. It also verifies that no pages are double referenced. Verification errors will stream out as they are found and the process will return after all pages have been checked. `, "\n") } // NewInfoCommand returns a InfoCommand. func newInfoCommand(m *MainT) *InfoCommand { return &InfoCommand{ Stdin: m.Stdin, Stdout: m.Stdout, Stderr: m.Stderr, } } // Run executes the command. func (cmd *InfoCommand) Run(args ...string) error { // Parse flags. fs := flag.NewFlagSet("", flag.ContinueOnError) help := fs.Bool("h", false, "") if err := fs.Parse(args); err != nil { return err } else if *help { fmt.Fprintln(cmd.Stderr, cmd.Usage()) return ErrUsage } // Require database path. path := fs.Arg(0) if path == "" { return ErrPathRequired } else if _, err := os.Stat(path); os.IsNotExist(err) { return ErrFileNotFound } // Open the database. db, err := Open(path) if err != nil { return err } defer db.Close() // Print basic database info. // FIXME fmt.Sprintf("Page Size: %d\n", db.pageSize) return nil } // Usage returns the help message. func (cmd *InfoCommand) Usage() string { return strings.TrimLeft(` usage: bolt info PATH Info prints basic information about the Bolt database at PATH. `, "\n") } // newDumpCommand returns a DumpCommand. func newDumpCommand(m *MainT) *DumpCommand { return &DumpCommand{ Stdin: m.Stdin, Stdout: m.Stdout, Stderr: m.Stderr, } } // Run executes the command. func (cmd *DumpCommand) Run(args ...string) error { // Parse flags. fs := flag.NewFlagSet("", flag.ContinueOnError) help := fs.Bool("h", false, "") if err := fs.Parse(args); err != nil { return err } else if *help { fmt.Fprintln(cmd.Stderr, cmd.Usage()) return ErrUsage } // Require database path and page id. path := fs.Arg(0) if path == "" { return ErrPathRequired } else if _, err := os.Stat(path); os.IsNotExist(err) { return ErrFileNotFound } // Read page ids. pageIDs, err := atois(fs.Args()[1:]) if err != nil { return err } else if len(pageIDs) == 0 { return ErrPageIDRequired } // Open database to retrieve page size. pageSize, err := ReadPageSize(path) if err != nil { return err } // Open database file handler. f, err := os.Open(path) if err != nil { return err } defer func() { _ = f.Close() }() // Print each page listed. for i, pageID := range pageIDs { // Print a separator. if i > 0 { fmt.Fprintln(cmd.Stdout, "===============================================") } // Print page to stdout. if err := cmd.PrintPage(cmd.Stdout, f, pageID, pageSize); err != nil { return err } } return nil } // PrintPage prints a given page as hexadecimal. func (cmd *DumpCommand) PrintPage(w io.Writer, r io.ReaderAt, pageID int, pageSize int) error { const bytesPerLineN = 16 // Read page into buffer. buf := make([]byte, pageSize) addr := pageID * pageSize if n, err := r.ReadAt(buf, int64(addr)); err != nil { return err } else if n != pageSize { return io.ErrUnexpectedEOF } // Write out to writer in 16-byte lines. var prev []byte var skipped bool for offset := 0; offset < pageSize; offset += bytesPerLineN { // Retrieve current 16-byte line. line := buf[offset : offset+bytesPerLineN] isLastLine := (offset == (pageSize - bytesPerLineN)) // If it's the same as the previous line then print a skip. if bytes.Equal(line, prev) && !isLastLine { if !skipped { fmt.Fprintf(w, "%07x *\n", addr+offset) skipped = true } } else { // Print line as hexadecimal in 2-byte groups. fmt.Fprintf(w, "%07x %04x %04x %04x %04x %04x %04x %04x %04x\n", addr+offset, line[0:2], line[2:4], line[4:6], line[6:8], line[8:10], line[10:12], line[12:14], line[14:16], ) skipped = false } // Save the previous line. prev = line } fmt.Fprint(w, "\n") return nil } // Usage returns the help message. func (cmd *DumpCommand) Usage() string { return strings.TrimLeft(` usage: bolt dump -page PAGEID PATH Dump prints a hexadecimal dump of a single page. `, "\n") } // newPageCommand returns a PageCommand. func newPageCommand(m *MainT) *PageCommand { return &PageCommand{ Stdin: m.Stdin, Stdout: m.Stdout, Stderr: m.Stderr, } } // Run executes the command. func (cmd *PageCommand) Run(args ...string) error { // Parse flags. fs := flag.NewFlagSet("", flag.ContinueOnError) help := fs.Bool("h", false, "") if err := fs.Parse(args); err != nil { return err } else if *help { fmt.Fprintln(cmd.Stderr, cmd.Usage()) return ErrUsage } // Require database path and page id. path := fs.Arg(0) if path == "" { return ErrPathRequired } else if _, err := os.Stat(path); os.IsNotExist(err) { return ErrFileNotFound } // Read page ids. pageIDs, err := atois(fs.Args()[1:]) if err != nil { return err } else if len(pageIDs) == 0 { return ErrPageIDRequired } // Open database file handler. f, err := os.Open(path) if err != nil { return err } defer func() { _ = f.Close() }() // Print each page listed. for i, pageID := range pageIDs { // Print a separator. if i > 0 { fmt.Fprintln(cmd.Stdout, "===============================================") } // Retrieve page info and page size. p, buf, err := ReadPage(path, pageID) if err != nil { return err } // Print basic page info. fmt.Fprintf(cmd.Stdout, "Page ID: %d\n", p.id) fmt.Fprintf(cmd.Stdout, "Page Type: %s\n", p.Type()) fmt.Fprintf(cmd.Stdout, "Total Size: %d bytes\n", len(buf)) // Print type-specific data. switch p.Type() { case "meta": err = cmd.PrintMeta(cmd.Stdout, buf) case "leaf": err = cmd.PrintLeaf(cmd.Stdout, buf) case "branch": err = cmd.PrintBranch(cmd.Stdout, buf) case "freelist": err = cmd.PrintFreelist(cmd.Stdout, buf) } if err != nil { return err } } return nil } // PrintMeta prints the data from the meta page. func (cmd *PageCommand) PrintMeta(w io.Writer, buf []byte) error { m := (*meta)(unsafe.Pointer(&buf[PageHeaderSize])) fmt.Fprintf(w, "Version: %d\n", m.version) fmt.Fprintf(w, "Page Size: %d bytes\n", m.pageSize) fmt.Fprintf(w, "Flags: %08x\n", m.flags) fmt.Fprintf(w, "Root: \n", m.root.root) fmt.Fprintf(w, "Freelist: \n", m.freelist) fmt.Fprintf(w, "HWM: \n", m.pgid) fmt.Fprintf(w, "Txn ID: %d\n", m.txid) fmt.Fprintf(w, "Checksum: %016x\n", m.checksum) fmt.Fprintf(w, "\n") return nil } // PrintLeaf prints the data for a leaf page. func (cmd *PageCommand) PrintLeaf(w io.Writer, buf []byte) error { p := (*page)(unsafe.Pointer(&buf[0])) // Print number of items. fmt.Fprintf(w, "Item Count: %d\n", p.count) fmt.Fprintf(w, "\n") // Print each key/value. for i := uint16(0); i < p.count; i++ { e := p.leafPageElement(i) // Format key as string. var k string if isPrintable(string(e.key())) { k = fmt.Sprintf("%q", string(e.key())) } else { k = fmt.Sprintf("%x", string(e.key())) } // Format value as string. var v string if (e.flags & uint32(bucketLeafFlag)) != 0 { b := (*bucket)(unsafe.Pointer(&e.value()[0])) v = fmt.Sprintf("", b.root, b.sequence) } else if isPrintable(string(e.value())) { v = fmt.Sprintf("%q", string(e.value())) } else { v = fmt.Sprintf("%x", string(e.value())) } fmt.Fprintf(w, "%s: %s\n", k, v) } fmt.Fprintf(w, "\n") return nil } // PrintBranch prints the data for a leaf page. func (cmd *PageCommand) PrintBranch(w io.Writer, buf []byte) error { p := (*page)(unsafe.Pointer(&buf[0])) // Print number of items. fmt.Fprintf(w, "Item Count: %d\n", p.count) fmt.Fprintf(w, "\n") // Print each key/value. for i := uint16(0); i < p.count; i++ { e := p.branchPageElement(i) // Format key as string. var k string if isPrintable(string(e.key())) { k = fmt.Sprintf("%q", string(e.key())) } else { k = fmt.Sprintf("%x", string(e.key())) } fmt.Fprintf(w, "%s: \n", k, e.pgid) } fmt.Fprintf(w, "\n") return nil } // PrintFreelist prints the data for a freelist page. func (cmd *PageCommand) PrintFreelist(w io.Writer, buf []byte) error { p := (*page)(unsafe.Pointer(&buf[0])) // Print number of items. fmt.Fprintf(w, "Item Count: %d\n", p.count) fmt.Fprintf(w, "\n") // Print each page in the freelist. ids := (*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)) for i := uint16(0); i < p.count; i++ { fmt.Fprintf(w, "%d\n", ids[i]) } fmt.Fprintf(w, "\n") return nil } // PrintPage prints a given page as hexadecimal. func (cmd *PageCommand) PrintPage(w io.Writer, r io.ReaderAt, pageID int, pageSize int) error { const bytesPerLineN = 16 // Read page into buffer. buf := make([]byte, pageSize) addr := pageID * pageSize if n, err := r.ReadAt(buf, int64(addr)); err != nil { return err } else if n != pageSize { return io.ErrUnexpectedEOF } // Write out to writer in 16-byte lines. var prev []byte var skipped bool for offset := 0; offset < pageSize; offset += bytesPerLineN { // Retrieve current 16-byte line. line := buf[offset : offset+bytesPerLineN] isLastLine := (offset == (pageSize - bytesPerLineN)) // If it's the same as the previous line then print a skip. if bytes.Equal(line, prev) && !isLastLine { if !skipped { fmt.Fprintf(w, "%07x *\n", addr+offset) skipped = true } } else { // Print line as hexadecimal in 2-byte groups. fmt.Fprintf(w, "%07x %04x %04x %04x %04x %04x %04x %04x %04x\n", addr+offset, line[0:2], line[2:4], line[4:6], line[6:8], line[8:10], line[10:12], line[12:14], line[14:16], ) skipped = false } // Save the previous line. prev = line } fmt.Fprint(w, "\n") return nil } // Usage returns the help message. func (cmd *PageCommand) Usage() string { return strings.TrimLeft(` usage: bolt page -page PATH pageid [pageid...] Page prints one or more pages in human readable format. `, "\n") } // NewPagesCommand returns a PagesCommand. func newPagesCommand(m *MainT) *PagesCommand { return &PagesCommand{ Stdin: m.Stdin, Stdout: m.Stdout, Stderr: m.Stderr, } } // Run executes the command. func (cmd *PagesCommand) Run(args ...string) error { // Parse flags. fs := flag.NewFlagSet("", flag.ContinueOnError) help := fs.Bool("h", false, "") if err := fs.Parse(args); err != nil { return err } else if *help { fmt.Fprintln(cmd.Stderr, cmd.Usage()) return ErrUsage } // Require database path. path := fs.Arg(0) if path == "" { return ErrPathRequired } else if _, err := os.Stat(path); os.IsNotExist(err) { return ErrFileNotFound } // Open database. db, err := Open(path) if err != nil { return err } defer func() { _ = db.Close() }() // Write header. fmt.Fprintln(cmd.Stdout, "ID TYPE ITEMS OVRFLW") fmt.Fprintln(cmd.Stdout, "======== ========== ====== ======") return db.Update(func(tx *Tx) error { var id int for { p, err := tx.Page(id) if err != nil { return &PageError{ID: id, Err: err} } else if p == nil { break } // Only display count and overflow if this is a non-free page. var count, overflow string if p.Type != "free" { count = strconv.Itoa(p.Count) if p.OverflowCount > 0 { overflow = strconv.Itoa(p.OverflowCount) } } // Print table row. fmt.Fprintf(cmd.Stdout, "%-8d %-10s %-6s %-6s\n", p.ID, p.Type, count, overflow) // Move to the next non-overflow page. id += 1 if p.Type != "free" { id += p.OverflowCount } } return nil }) } // Usage returns the help message. func (cmd *PagesCommand) Usage() string { return strings.TrimLeft(` usage: bolt pages PATH Pages prints a table of pages with their type (meta, leaf, branch, freelist). Leaf and branch pages will show a key count in the "items" column while the freelist will show the number of free pages in the "items" column. The "overflow" column shows the number of blocks that the page spills over into. Normally there is no overflow but large keys and values can cause a single page to take up multiple blocks. `, "\n") } // NewStatsCommand returns a StatsCommand. func newStatsCommand(m *MainT) *StatsCommand { return &StatsCommand{ Stdin: m.Stdin, Stdout: m.Stdout, Stderr: m.Stderr, } } // Run executes the command. func (cmd *StatsCommand) Run(args ...string) error { // Parse flags. fs := flag.NewFlagSet("", flag.ContinueOnError) help := fs.Bool("h", false, "") if err := fs.Parse(args); err != nil { return err } else if *help { fmt.Fprintln(cmd.Stderr, cmd.Usage()) return ErrUsage } // Require database path. path, prefix := fs.Arg(0), fs.Arg(1) if path == "" { return ErrPathRequired } else if _, err := os.Stat(path); os.IsNotExist(err) { return ErrFileNotFound } // Open database. db, err := Open(path) if err != nil { return err } defer db.Close() return db.View(func(tx *Tx) error { var s BucketStats var count int if err := tx.ForEach(func(name []byte, b *Bucket) error { if bytes.HasPrefix(name, []byte(prefix)) { s.Add(b.Stats()) count += 1 } return nil }); err != nil { return err } fmt.Fprintf(cmd.Stdout, "Aggregate statistics for %d buckets\n\n", count) fmt.Fprintln(cmd.Stdout, "Page count statistics") fmt.Fprintf(cmd.Stdout, "\tNumber of logical branch pages: %d\n", s.BranchPageN) fmt.Fprintf(cmd.Stdout, "\tNumber of physical branch overflow pages: %d\n", s.BranchOverflowN) fmt.Fprintf(cmd.Stdout, "\tNumber of logical leaf pages: %d\n", s.LeafPageN) fmt.Fprintf(cmd.Stdout, "\tNumber of physical leaf overflow pages: %d\n", s.LeafOverflowN) fmt.Fprintln(cmd.Stdout, "Tree statistics") fmt.Fprintf(cmd.Stdout, "\tNumber of keys/value pairs: %d\n", s.KeyN) fmt.Fprintf(cmd.Stdout, "\tNumber of levels in B+tree: %d\n", s.Depth) fmt.Fprintln(cmd.Stdout, "Page size utilization") fmt.Fprintf(cmd.Stdout, "\tBytes allocated for physical branch pages: %d\n", s.BranchAlloc) var percentage int if s.BranchAlloc != 0 { percentage = int(float32(s.BranchInuse) * 100.0 / float32(s.BranchAlloc)) } fmt.Fprintf(cmd.Stdout, "\tBytes actually used for branch data: %d (%d%%)\n", s.BranchInuse, percentage) fmt.Fprintf(cmd.Stdout, "\tBytes allocated for physical leaf pages: %d\n", s.LeafAlloc) percentage = 0 if s.LeafAlloc != 0 { percentage = int(float32(s.LeafInuse) * 100.0 / float32(s.LeafAlloc)) } fmt.Fprintf(cmd.Stdout, "\tBytes actually used for leaf data: %d (%d%%)\n", s.LeafInuse, percentage) fmt.Fprintln(cmd.Stdout, "Bucket statistics") fmt.Fprintf(cmd.Stdout, "\tTotal number of buckets: %d\n", s.BucketN) percentage = 0 if s.BucketN != 0 { percentage = int(float32(s.InlineBucketN) * 100.0 / float32(s.BucketN)) } fmt.Fprintf(cmd.Stdout, "\tTotal number on inlined buckets: %d (%d%%)\n", s.InlineBucketN, percentage) percentage = 0 if s.LeafInuse != 0 { percentage = int(float32(s.InlineBucketInuse) * 100.0 / float32(s.LeafInuse)) } fmt.Fprintf(cmd.Stdout, "\tBytes used for inlined buckets: %d (%d%%)\n", s.InlineBucketInuse, percentage) return nil }) } // Usage returns the help message. func (cmd *StatsCommand) Usage() string { return strings.TrimLeft(` usage: bolt stats PATH Stats performs an extensive search of the database to track every page reference. It starts at the current meta page and recursively iterates through every accessible bucket. The following errors can be reported: already freed The page is referenced more than once in the freelist. unreachable unfreed The page is not referenced by a bucket or in the freelist. reachable freed The page is referenced by a bucket but is also in the freelist. out of bounds A page is referenced that is above the high water mark. multiple references A page is referenced by more than one other page. invalid type The page type is not "meta", "leaf", "branch", or "freelist". No errors should occur in your database. However, if for some reason you experience corruption, please submit a ticket to the Bolt project page: https://github.com/boltdb/bolt/issues `, "\n") } // NewBenchCommand returns a BenchCommand using the func newBenchCommand(m *MainT) *BenchCommand { return &BenchCommand{ Stdin: m.Stdin, Stdout: m.Stdout, Stderr: m.Stderr, } } // Run executes the "bench" command. func (cmd *BenchCommand) Run(args ...string) error { // Parse CLI arguments. options, err := cmd.ParseFlags(args) if err != nil { return err } // Remove path if "-work" is not set. Otherwise keep path. if options.Work { fmt.Fprintf(cmd.Stdout, "work: %s\n", options.Path) } else { defer os.Remove(options.Path) } // Create database. db, err := Open(options.Path) if err != nil { return err } defer db.Close() // Write to the database. var results BenchResults if err := cmd.runWrites(db, options, &results); err != nil { return fmt.Errorf("write: %v", err) } // Read from the database. if err := cmd.runReads(db, options, &results); err != nil { return fmt.Errorf("bench: read: %s", err) } // Print results. fmt.Fprintf(os.Stderr, "# Write\t%v\t(%v/op)\t(%v op/sec)\n", results.WriteDuration, results.WriteOpDuration(), results.WriteOpsPerSecond()) fmt.Fprintf(os.Stderr, "# Read\t%v\t(%v/op)\t(%v op/sec)\n", results.ReadDuration, results.ReadOpDuration(), results.ReadOpsPerSecond()) fmt.Fprintln(os.Stderr, "") return nil } // ParseFlags parses the command line flags. func (cmd *BenchCommand) ParseFlags(args []string) (*BenchOptions, error) { var options BenchOptions // Parse flagset. fs := flag.NewFlagSet("", flag.ContinueOnError) fs.StringVar(&options.ProfileMode, "profile-mode", "rw", "") fs.StringVar(&options.WriteMode, "write-mode", "seq", "") fs.StringVar(&options.ReadMode, "read-mode", "seq", "") fs.IntVar(&options.Iterations, "count", 1000, "") fs.IntVar(&options.BatchSize, "batch-size", 0, "") fs.IntVar(&options.KeySize, "key-size", 8, "") fs.IntVar(&options.ValueSize, "value-size", 32, "") fs.StringVar(&options.CPUProfile, "cpuprofile", "", "") fs.StringVar(&options.MemProfile, "memprofile", "", "") fs.StringVar(&options.BlockProfile, "blockprofile", "", "") fs.BoolVar(&options.Work, "work", false, "") fs.StringVar(&options.Path, "path", "", "") fs.SetOutput(cmd.Stderr) if err := fs.Parse(args); err != nil { return nil, err } // Set batch size to iteration size if not set. // Require that batch size can be evenly divided by the iteration count. if options.BatchSize == 0 { options.BatchSize = options.Iterations } else if options.Iterations%options.BatchSize != 0 { return nil, ErrNonDivisibleBatchSize } // Generate temp path if one is not passed in. if options.Path == "" { f, err := ioutil.TempFile("", "bolt-bench-") if err != nil { return nil, fmt.Errorf("temp file: %s", err) } f.Close() os.Remove(f.Name()) options.Path = f.Name() } return &options, nil } // Writes to the database. func (cmd *BenchCommand) runWrites(db *DB, options *BenchOptions, results *BenchResults) error { // Start profiling for writes. if options.ProfileMode == "rw" || options.ProfileMode == "w" { cmd.startProfiling(options) } t := time.Now() var err error switch options.WriteMode { case "seq": err = cmd.runWritesSequential(db, options, results) case "rnd": err = cmd.runWritesRandom(db, options, results) case "seq-nest": err = cmd.runWritesSequentialNested(db, options, results) case "rnd-nest": err = cmd.runWritesRandomNested(db, options, results) default: return fmt.Errorf("invalid write mode: %s", options.WriteMode) } // Save time to write. results.WriteDuration = time.Since(t) // Stop profiling for writes only. if options.ProfileMode == "w" { cmd.stopProfiling() } return err } func (cmd *BenchCommand) runWritesSequential(db *DB, options *BenchOptions, results *BenchResults) error { var i = uint32(0) return cmd.runWritesWithSource(db, options, results, func() uint32 { i++; return i }) } func (cmd *BenchCommand) runWritesRandom(db *DB, options *BenchOptions, results *BenchResults) error { r := rand.New(rand.NewSource(time.Now().UnixNano())) return cmd.runWritesWithSource(db, options, results, func() uint32 { return r.Uint32() }) } func (cmd *BenchCommand) runWritesSequentialNested(db *DB, options *BenchOptions, results *BenchResults) error { var i = uint32(0) return cmd.runWritesWithSource(db, options, results, func() uint32 { i++; return i }) } func (cmd *BenchCommand) runWritesRandomNested(db *DB, options *BenchOptions, results *BenchResults) error { r := rand.New(rand.NewSource(time.Now().UnixNano())) return cmd.runWritesWithSource(db, options, results, func() uint32 { return r.Uint32() }) } func (cmd *BenchCommand) runWritesWithSource(db *DB, options *BenchOptions, results *BenchResults, keySource func() uint32) error { results.WriteOps = options.Iterations for i := 0; i < options.Iterations; i += options.BatchSize { if err := db.Update(func(tx *Tx) error { b, _ := tx.CreateBucketIfNotExists(benchBucketName) for j := 0; j < options.BatchSize; j++ { key := make([]byte, options.KeySize) value := make([]byte, options.ValueSize) // Write key as uint32. binary.BigEndian.PutUint32(key, keySource()) // Insert key/value. if err := b.Put(key, value); err != nil { return err } } return nil }); err != nil { return err } } return nil } func (cmd *BenchCommand) runWritesNestedWithSource(db *DB, options *BenchOptions, results *BenchResults, keySource func() uint32) error { results.WriteOps = options.Iterations for i := 0; i < options.Iterations; i += options.BatchSize { if err := db.Update(func(tx *Tx) error { top, err := tx.CreateBucketIfNotExists(benchBucketName) if err != nil { return err } // Create bucket key. name := make([]byte, options.KeySize) binary.BigEndian.PutUint32(name, keySource()) // Create bucket. b, err := top.CreateBucketIfNotExists(name) if err != nil { return err } for j := 0; j < options.BatchSize; j++ { var key = make([]byte, options.KeySize) var value = make([]byte, options.ValueSize) // Generate key as uint32. binary.BigEndian.PutUint32(key, keySource()) // Insert value into subbucket. if err := b.Put(key, value); err != nil { return err } } return nil }); err != nil { return err } } return nil } // Reads from the database. func (cmd *BenchCommand) runReads(db *DB, options *BenchOptions, results *BenchResults) error { // Start profiling for reads. if options.ProfileMode == "r" { cmd.startProfiling(options) } t := time.Now() var err error switch options.ReadMode { case "seq": switch options.WriteMode { case "seq-nest", "rnd-nest": err = cmd.runReadsSequentialNested(db, options, results) default: err = cmd.runReadsSequential(db, options, results) } default: return fmt.Errorf("invalid read mode: %s", options.ReadMode) } // Save read time. results.ReadDuration = time.Since(t) // Stop profiling for reads. if options.ProfileMode == "rw" || options.ProfileMode == "r" { cmd.stopProfiling() } return err } func (cmd *BenchCommand) runReadsSequential(db *DB, options *BenchOptions, results *BenchResults) error { return db.View(func(tx *Tx) error { t := time.Now() for { var count int c := tx.Bucket(benchBucketName).Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { if v == nil { return errors.New("invalid value") } count++ } if options.WriteMode == "seq" && count != options.Iterations { return fmt.Errorf("read seq: iter mismatch: expected %d, got %d", options.Iterations, count) } results.ReadOps += count // Make sure we do this for at least a second. if time.Since(t) >= time.Second { break } } return nil }) } func (cmd *BenchCommand) runReadsSequentialNested(db *DB, options *BenchOptions, results *BenchResults) error { return db.View(func(tx *Tx) error { t := time.Now() for { var count int var top = tx.Bucket(benchBucketName) if err := top.ForEach(func(name, _ []byte) error { c := top.Bucket(name).Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { if v == nil { return ErrInvalidValue } count++ } return nil }); err != nil { return err } if options.WriteMode == "seq-nest" && count != options.Iterations { return fmt.Errorf("read seq-nest: iter mismatch: expected %d, got %d", options.Iterations, count) } results.ReadOps += count // Make sure we do this for at least a second. if time.Since(t) >= time.Second { break } } return nil }) } // Starts all profiles set on the options. func (cmd *BenchCommand) startProfiling(options *BenchOptions) { var err error // Start CPU profiling. if options.CPUProfile != "" { cpuprofile, err = os.Create(options.CPUProfile) if err != nil { fmt.Fprintf(cmd.Stderr, "bench: could not create cpu profile %q: %v\n", options.CPUProfile, err) os.Exit(1) } pprof.StartCPUProfile(cpuprofile) } // Start memory profiling. if options.MemProfile != "" { memprofile, err = os.Create(options.MemProfile) if err != nil { fmt.Fprintf(cmd.Stderr, "bench: could not create memory profile %q: %v\n", options.MemProfile, err) os.Exit(1) } runtime.MemProfileRate = 4096 } // Start fatal profiling. if options.BlockProfile != "" { blockprofile, err = os.Create(options.BlockProfile) if err != nil { fmt.Fprintf(cmd.Stderr, "bench: could not create block profile %q: %v\n", options.BlockProfile, err) os.Exit(1) } runtime.SetBlockProfileRate(1) } } // Stops all profiles. func (cmd *BenchCommand) stopProfiling() { if cpuprofile != nil { pprof.StopCPUProfile() cpuprofile.Close() cpuprofile = nil } if memprofile != nil { pprof.Lookup("heap").WriteTo(memprofile, 0) memprofile.Close() memprofile = nil } if blockprofile != nil { pprof.Lookup("block").WriteTo(blockprofile, 0) blockprofile.Close() blockprofile = nil runtime.SetBlockProfileRate(0) } } // Returns the duration for a single write operation. func (r *BenchResults) WriteOpDuration() time.Duration { if r.WriteOps == 0 { return 0 } return r.WriteDuration / time.Duration(r.WriteOps) } // Returns average number of write operations that can be performed per second. func (r *BenchResults) WriteOpsPerSecond() int { var op = r.WriteOpDuration() if op == 0 { return 0 } return int(time.Second) / int(op) } // Returns the duration for a single read operation. func (r *BenchResults) ReadOpDuration() time.Duration { if r.ReadOps == 0 { return 0 } return r.ReadDuration / time.Duration(r.ReadOps) } // Returns average number of read operations that can be performed per second. func (r *BenchResults) ReadOpsPerSecond() int { var op = r.ReadOpDuration() if op == 0 { return 0 } return int(time.Second) / int(op) } func (e *PageError) Error() string { return fmt.Sprintf("page error: id=%d, err=%s", e.ID, e.Err) } // isPrintable returns true if the string is valid unicode and contains only printable runes. func isPrintable(s string) bool { if !utf8.ValidString(s) { return false } for _, ch := range s { if !unicode.IsPrint(ch) { return false } } return true } // ReadPage reads page info & full page data from a path. // This is not transactionally safe. func ReadPage(path string, pageID int) (*page, []byte, error) { // Find page size. pageSize, err := ReadPageSize(path) if err != nil { return nil, nil, fmt.Errorf("read page size: %s", err) } // Open database file. f, err := os.Open(path) if err != nil { return nil, nil, err } defer f.Close() // Read one block into buffer. buf := make([]byte, pageSize) if n, err := f.ReadAt(buf, int64(pageID*pageSize)); err != nil { return nil, nil, err } else if n != len(buf) { return nil, nil, io.ErrUnexpectedEOF } // Determine total number of blocks. p := (*page)(unsafe.Pointer(&buf[0])) overflowN := p.overflow // Re-read entire page (with overflow) into buffer. buf = make([]byte, (int(overflowN)+1)*pageSize) if n, err := f.ReadAt(buf, int64(pageID*pageSize)); err != nil { return nil, nil, err } else if n != len(buf) { return nil, nil, io.ErrUnexpectedEOF } p = (*page)(unsafe.Pointer(&buf[0])) return p, buf, nil } // ReadPageSize reads page size a path. // This is not transactionally safe. func ReadPageSize(path string) (int, error) { // Open database file. f, err := os.Open(path) if err != nil { return 0, err } defer f.Close() // Read 4KB chunk. buf := make([]byte, 4096) if _, err := io.ReadFull(f, buf); err != nil { return 0, err } // Read page size from metadata. m := (*meta)(unsafe.Pointer(&buf[PageHeaderSize])) return int(m.pageSize), nil } // atois parses a slice of strings into integers. func atois(strs []string) ([]int, error) { var a []int for _, str := range strs { i, err := strconv.Atoi(str) if err != nil { return nil, err } a = append(a, i) } return a, nil } // DO NOT EDIT. Copied from the "bolt" package. func (p *page) Type() string { if (p.flags & branchPageFlag) != 0 { return "branch" } else if (p.flags & leafPageFlag) != 0 { return "leaf" } else if (p.flags & metaPageFlag) != 0 { return "meta" } else if (p.flags & freelistPageFlag) != 0 { return "freelist" } return fmt.Sprintf("unknown<%02x>", p.flags) } // newCompactCommand returns a CompactCommand. func newCompactCommand(m *MainT) *CompactCommand { return &CompactCommand{ Stdin: m.Stdin, Stdout: m.Stdout, Stderr: m.Stderr, } } // Run executes the command. func (cmd *CompactCommand) Run(args ...string) (err error) { // Parse flags. fs := flag.NewFlagSet("", flag.ContinueOnError) fs.SetOutput(ioutil.Discard) fs.StringVar(&cmd.DstPath, "o", "", "") fs.Int64Var(&cmd.TxMaxSize, "tx-max-size", 65536, "") if err := fs.Parse(args); err == flag.ErrHelp { fmt.Fprintln(cmd.Stderr, cmd.Usage()) return ErrUsage } else if err != nil { return err } else if cmd.DstPath == "" { return fmt.Errorf("output file required") } // Require database paths. cmd.SrcPath = fs.Arg(0) if cmd.SrcPath == "" { return ErrPathRequired } // Ensure source file exists. fi, err := os.Stat(cmd.SrcPath) if os.IsNotExist(err) { return ErrFileNotFound } else if err != nil { return err } initialSize := fi.Size() // Open source database. src, err := Open(cmd.SrcPath) if err != nil { return err } defer src.Close() // Open destination database. dst, err := Open(cmd.DstPath) if err != nil { return err } defer dst.Close() // Run compaction. if err := cmd.compact(dst, src); err != nil { return err } // Report stats on new size. fi, err = os.Stat(cmd.DstPath) if err != nil { return err } else if fi.Size() == 0 { return fmt.Errorf("zero db size") } fmt.Fprintf(cmd.Stdout, "%d -> %d bytes (gain=%.2fx)\n", initialSize, fi.Size(), float64(initialSize)/float64(fi.Size())) return nil } func (cmd *CompactCommand) compact(dst, src *DB) error { // commit regularly, or we'll run out of memory for large datasets if using one transaction. var size int64 tx, err := dst.Begin(true) if err != nil { return err } defer tx.Rollback() if err := cmd.walk(src, func(keys [][]byte, k, v []byte, seq uint64) error { // On each key/value, check if we have exceeded tx size. sz := int64(len(k) + len(v)) if size+sz > cmd.TxMaxSize && cmd.TxMaxSize != 0 { // Commit previous transaction. if err := tx.Commit(); err != nil { return err } // Start new transaction. tx, err = dst.Begin(true) if err != nil { return err } size = 0 } size += sz // Create bucket on the root transaction if this is the first level. nk := len(keys) if nk == 0 { bkt, err := tx.CreateBucket(k) if err != nil { return err } if err := bkt.SetSequence(seq); err != nil { return err } return nil } // Create buckets on subsequent levels, if necessary. b := tx.Bucket(keys[0]) if nk > 1 { for _, k := range keys[1:] { b = b.Bucket(k) } } // If there is no value then this is a bucket call. if v == nil { bkt, err := b.CreateBucket(k) if err != nil { return err } if err := bkt.SetSequence(seq); err != nil { return err } return nil } // Otherwise treat it as a key/value pair. return b.Put(k, v) }); err != nil { return err } return tx.Commit() } // walk walks recursively the bolt database db, calling walkFn for each key it finds. func (cmd *CompactCommand) walk(db *DB, walkFn walkFunc) error { return db.View(func(tx *Tx) error { return tx.ForEach(func(name []byte, b *Bucket) error { return cmd.walkBucket(b, nil, name, nil, b.Sequence(), walkFn) }) }) } func (cmd *CompactCommand) walkBucket(b *Bucket, keypath [][]byte, k, v []byte, seq uint64, fn walkFunc) error { // Execute callback. if err := fn(keypath, k, v, seq); err != nil { return err } // If this is not a bucket then stop. if v != nil { return nil } // Iterate over each child key/value. keypath = append(keypath, k) return b.ForEach(func(k, v []byte) error { if v == nil { bkt := b.Bucket(k) return cmd.walkBucket(bkt, keypath, k, nil, bkt.Sequence(), fn) } return cmd.walkBucket(b, keypath, k, v, b.Sequence(), fn) }) } // Usage returns the help message. func (cmd *CompactCommand) Usage() string { return strings.TrimLeft(` usage: bolt compact [options] -o DST SRC Compact opens a database at SRC path and walks it recursively, copying keys as they are found from all buckets, to a newly created database at DST path. The original database is left untouched. Additional options include: -tx-max-size NUM Specifies the maximum size of individual transactions. Defaults to 64KB. `, "\n") }