Improve RBF documentation (#2034)

This commit is contained in:
Ben Johnson 2022-04-25 08:13:54 -06:00 • committed by GitHub
parent 19613a3048
commit 8702fac11d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 70 additions and 14 deletions

View file

@ -16,9 +16,15 @@ const (
BitmapN = (1 << 16) / 64
)
// Cursor represents an object for traversing forward and backward in the
// keyspace. Users should initialize a cursor with either First(), Last(), or
// Seek() and then move forward or backward using Next() or Prev(), respectively.
//
// If you mutate the b-tree that a cursor is attached to then you'll need to
// re-initialize the cursor. This may be changed in the future though.
type Cursor struct {
tx *Tx
buffered bool
buffered bool // if true, Next() and Prev() do not move the cursor position
// buffers
array [ArrayMaxSize + 1]uint16
@ -30,7 +36,7 @@ type Cursor struct {
}
type searchStack struct {
top int
top int // current depth in elems
elems [32]stackElem
}
@ -118,6 +124,7 @@ func maybeConvertOversizedRunToBitmap(runs []roaring.Interval16, bitN int, key u
}
// Add sets a bit on the underlying bitmap.
// If changed is true, this cursor will need to be reinitialized before use.
func (c *Cursor) Add(v uint64) (changed bool, err error) {
hi, lo := highbits(v), lowbits(v)
// Move cursor to the key of the container.
@ -188,6 +195,7 @@ func (c *Cursor) Add(v uint64) (changed bool, err error) {
}
// Remove unsets a bit on the underlying bitmap.
// If changed is true, the cursor will need to be reinitialized before use.
func (c *Cursor) Remove(v uint64) (changed bool, err error) {
hi, lo := highbits(v), lowbits(v)
@ -354,6 +362,21 @@ func toPgno(val []byte) uint32 {
return binary.LittleEndian.Uint32(val)
}
// putLeafCell inserts or updates a cell into the currently position leaf page
// in the stack. If in.Key exists in the page, the cell is updated. Otherwise,
// cell is inserted.
//
// This method has two paths. First, a fast path is used if we know the insert
// will not cause the page to grow past the max page size. This path simply
// shifts bytes around in the page to make room for the new cell. The second
// path is the slow path where a page may split into two pages. This causes the
// entire page to be deserialized so that pages can more easily be split.
//
// The slow path may cause a cascade of changes to parent pages as a page split
// creates a new entry in the parent. If that entry overflows the parent then
// the parent branch page will split which could cascade up to the root. If the
// root page splits, a new branch page is created but retains the original root
// page number so that the root records do not need to be updated.
func (c *Cursor) putLeafCell(in leafCell) (err error) {
elem := &c.stack.elems[c.stack.top]
leafPage, isHeap, err := c.tx.readPage(elem.pgno) // the last read leaf page
@ -610,6 +633,9 @@ func (c *Cursor) putLeafCellFast(in leafCell, isInsert bool) (err error) {
}
// deleteLeafCell removes a cell from the currently positioned page & index.
// If the removal causes the leaf page to have no more elements then its entry
// is removed from the parent. If the removal changes the first entry in the
// leaf page then the entry will be updated in the parent branch page.
func (c *Cursor) deleteLeafCell(key uint64) (err error) {
elem := &c.stack.elems[c.stack.top]
leafPage, _, err := c.tx.readPage(elem.pgno)
@ -661,7 +687,9 @@ func (c *Cursor) deleteLeafCell(key uint64) (err error) {
return nil
}
// putBranchCells updates a branch page with one or more cells.
// putBranchCells updates a branch page with one or more cells. If the cells
// cause the branch page to split then a new entry will be created in the
// parent page. If this branch page is a root then a new root will be created.
func (c *Cursor) putBranchCells(stackIndex int, newCells []branchCell) (err error) {
elem := &c.stack.elems[stackIndex]
@ -725,8 +753,6 @@ func (c *Cursor) putBranchCells(stackIndex int, newCells []branchCell) (err erro
}
}
// TODO(BBJ): Check if key on page changes and update parent if so.
// TODO(BBJ): Update page in buffer & cursor stack.
// If this is not a split, then exit now.
@ -745,7 +771,8 @@ func (c *Cursor) putBranchCells(stackIndex int, newCells []branchCell) (err erro
return c.putBranchCells(stackIndex-1, parents)
}
// updateBranchCell updates the key for cell in the branch.
// updateBranchCell updates the key for cell in the branch. Branch elements
// are fixed size so this cannot cause a page split.
func (c *Cursor) updateBranchCell(stackIndex int, newKey uint64) (err error) {
elem := &c.stack.elems[stackIndex]
@ -781,7 +808,10 @@ func (c *Cursor) updateBranchCell(stackIndex int, newKey uint64) (err error) {
return nil
}
// deleteBranchCell removes a cell from a branch page.
// deleteBranchCell removes a cell from a branch page. If no more elements
// exist in a branch page then it is removed. If an empty branch page is the
// root page then it is converted to an empty leaf page as branch pages are not
// allowed to be empty.
func (c *Cursor) deleteBranchCell(stackIndex int, key uint64) (err error) {
elem := &c.stack.elems[stackIndex]
@ -859,6 +889,8 @@ func (c *Cursor) deleteBranchCell(stackIndex int, key uint64) (err error) {
}
// writeRoot writes a new branch page at the root with the given cells.
// The root of a b-tree should always stay the same, even in the case of a
// split. This allows us to not rewrite the root record for the b-tree.
func (c *Cursor) writeRoot(pgno uint32, cells []branchCell) error {
var buf [PageSize]byte
writePageNo(buf[:], pgno)
@ -940,7 +972,9 @@ func pageKeyAt(page []byte, index int) uint64 {
return *(*uint64)(unsafe.Pointer(&page[offset]))
}
// First moves to the first element of the btree.
// First moves to the first element of the btree. Returns io.EOF if no elements
// exist. The first call to Next() will not move the position but subsequent
// calls will move the position forward until it reaches the end and return io.EOF.
func (c *Cursor) First() error {
c.buffered = true
@ -980,7 +1014,10 @@ func (c *Cursor) First() error {
}
}
// Last moves to the last element of the btree.
// Last moves to the last element of the btree. Returns io.EOF if there are no
// elements. The first call to Prev() will not move the position but subsequent
// calls will move the position backward until it reaches the beginning and
// return io.EOF.
func (c *Cursor) Last() error {
// c.stack.elems[0].pgno = c.root
c.buffered = true
@ -1101,7 +1138,8 @@ func (c *Cursor) Next() error {
return c.goNextPage()
}
// Prev moves to the previous element of the btree.
// Prev moves to the previous element of the btree. Returns io.EOF if at the
// beginning of the b-tree.
func (c *Cursor) Prev() error {
if c.buffered {
c.buffered = false
@ -1211,6 +1249,9 @@ var _ = (&stackElem{}).clear
var _ = (&stackElem{}).String
var _ = (&stackElem{}).equal
// goNextPage moves up the stack until an element has additional elements
// available after the current position. It then traverses back down the stack
// to position at the next leaf page element.
func (c *Cursor) goNextPage() error {
for c.stack.top--; c.stack.top >= 0; c.stack.top-- {
elem := &c.stack.elems[c.stack.top]

View file

@ -19,7 +19,10 @@ import (
var _ = txkey.ToString
// Tx represents a transaction.
// Tx represents an RBF transaction. Transactions provide guarantees such as
// atomicity for all writes that occur as well as serializable isolation.
// Transactions can be obtained by calling DB.Begin() and provide a snapshot
// view at the point-in-time they are started.
type Tx struct {
mu sync.RWMutex
db *DB // parent db
@ -71,11 +74,13 @@ type Tx struct {
stack []byte
}
// DBPath returns the path to the directory that holds the parent database.
func (tx *Tx) DBPath() string {
return tx.db.Path
}
// Writable returns true if the transaction can mutate data.
// Writable returns true if the transaction can mutate data. Using transaction
// methods that attempt to write will return ErrTxNotWritable.
func (tx *Tx) Writable() bool {
return tx.writable
}
@ -95,7 +100,12 @@ func (tx *Tx) PageN() int {
return int(readMetaPageN(tx.meta[:]))
}
// Commit completes the transaction and persists data changes.
// Commit completes the transaction and persists data changes. If this method
// fails, changes may or may not have been persisted to disk. If no changes have
// been made during the transaction, this functions the same as a rollback.
//
// Attempting to commit an already committed or rolled back transaction will
// return an ErrTxClosed error.
func (tx *Tx) Commit() error {
tx.mu.Lock()
defer tx.mu.Unlock()
@ -193,6 +203,9 @@ func (tx *Tx) truncateLastFreePage() (truncated bool, outErr error) {
return true, nil
}
// Rollback discards any changes that have been made by the transaction.
// A commit or rollback must always be called after a transaction finishes.
// If this is a writable transaction, the write lock will be released on the DB.
func (tx *Tx) Rollback() { tx.rollback(false) }
func (tx *Tx) rollback(hasDBLock bool) {
@ -225,11 +238,13 @@ func (tx *Tx) Root(name string) (uint32, error) {
}
func (tx *Tx) root(name string) (uint32, error) {
// Fetch list of roots (or retrieve from cache).
records, err := tx.RootRecords()
if err != nil {
return 0, err
}
// Lookup record by bitmap name and return its root page.
pgno, ok := records.Get(name)
if !ok {
return 0, ErrBitmapNotFound
@ -260,7 +275,7 @@ func (tx *Tx) BitmapNames() ([]string, error) {
return a, nil
}
// BitmapExist returns true if bitmap exists.
// BitmapExist returns true if bitmap exists. Returns an error if name is empty.
func (tx *Tx) BitmapExists(name string) (bool, error) {
tx.mu.Lock()
defer tx.mu.Unlock()