Documentation
¶
Overview ¶
Package fractaltree provides an in-memory B-epsilon-tree (fractal tree), a write-optimized ordered key-value data structure.
A B-epsilon-tree amortizes write cost by buffering mutations as messages in internal nodes and flushing them toward leaves in batches. The parameter epsilon controls the trade-off between write throughput and read latency.
Quick Start ¶
For keys that satisfy cmp.Ordered (int, string, float64, ...):
t, err := fractaltree.New[string, int]()
if err != nil {
log.Fatal(err)
}
t.Put("alice", 42)
v, ok := t.Get("alice") // v=42, ok=true
For composite or custom-ordered keys, supply a comparator:
type TenantKey struct {
Tenant string
ID int64
}
t, err := fractaltree.NewWithCompare[TenantKey, string](func(a, b TenantKey) int {
if c := cmp.Compare(a.Tenant, b.Tenant); c != 0 {
return c
}
return cmp.Compare(a.ID, b.ID)
})
Concurrency ¶
A BETree is safe for concurrent use by multiple goroutines. Reads acquire a shared lock; writes acquire an exclusive lock. Iterators hold a shared lock for their lifetime, providing snapshot-consistent traversal.
Epsilon Parameter ¶
Epsilon (set via WithEpsilon) must be in (0, 1] and defaults to 0.5. Given a block size B:
- Fanout (max children per internal node): B^epsilon
- Buffer capacity (max messages per node): B^(1-epsilon)
Lower epsilon favors write throughput; higher epsilon favors read latency.
Complexity ¶
- Point query: O(log_B N)
- Range query: O(log_B N + k/B) for k results
- Insert/Delete: O(log_B N / B^(1-epsilon)) amortized
Disk Extension ¶
BETree operates entirely in memory. DiskBETree extends it with pluggable persistence via the Flusher interface. Implement Flusher to control how and where nodes are serialized. Key and value serialization is handled by the Codec interface, with GobCodec provided as a default.
Index ¶
- Constants
- Variables
- type BETree
- func (t *BETree[K, V]) All() iter.Seq2[K, V]
- func (t *BETree[K, V]) Ascend() iter.Seq2[K, V]
- func (t *BETree[K, V]) Clear()
- func (t *BETree[K, V]) Close() error
- func (t *BETree[K, V]) Contains(key K) bool
- func (t *BETree[K, V]) Cursor() *Cursor[K, V]
- func (t *BETree[K, V]) Delete(key K) bool
- func (t *BETree[K, V]) DeleteRange(lo, hi K) int
- func (t *BETree[K, V]) Descend() iter.Seq2[K, V]
- func (t *BETree[K, V]) DescendRange(hi, lo K) iter.Seq2[K, V]
- func (t *BETree[K, V]) Get(key K) (V, bool)
- func (t *BETree[K, V]) Len() int
- func (t *BETree[K, V]) Put(key K, value V)
- func (t *BETree[K, V]) PutIfAbsent(key K, value V) bool
- func (t *BETree[K, V]) Range(lo, hi K) iter.Seq2[K, V]
- func (t *BETree[K, V]) Upsert(key K, fn UpsertFn[V])
- type Codec
- type Cursor
- type DiskBETree
- func (d *DiskBETree[K, V]) All() iter.Seq2[K, V]
- func (d *DiskBETree[K, V]) Ascend() iter.Seq2[K, V]
- func (d *DiskBETree[K, V]) Clear()
- func (d *DiskBETree[K, V]) Close() error
- func (d *DiskBETree[K, V]) Contains(key K) bool
- func (d *DiskBETree[K, V]) Cursor() *Cursor[K, V]
- func (d *DiskBETree[K, V]) Delete(key K) bool
- func (d *DiskBETree[K, V]) DeleteRange(lo, hi K) int
- func (d *DiskBETree[K, V]) Descend() iter.Seq2[K, V]
- func (d *DiskBETree[K, V]) DescendRange(hi, lo K) iter.Seq2[K, V]
- func (d *DiskBETree[K, V]) Get(key K) (V, bool)
- func (d *DiskBETree[K, V]) Len() int
- func (d *DiskBETree[K, V]) Put(key K, value V)
- func (d *DiskBETree[K, V]) PutIfAbsent(key K, v V) bool
- func (d *DiskBETree[K, V]) Range(lo, hi K) iter.Seq2[K, V]
- func (d *DiskBETree[K, V]) Upsert(key K, fn UpsertFn[V])
- type DiskOption
- type Flusher
- type GobCodec
- type Message
- type MsgKind
- type Option
- type Tree
- type UpsertFn
Examples ¶
- BETree.All
- BETree.Ascend
- BETree.Clear
- BETree.Close
- BETree.Contains
- BETree.Cursor
- BETree.Delete
- BETree.DeleteRange
- BETree.Descend
- BETree.DescendRange
- BETree.Get
- BETree.Len
- BETree.Put
- BETree.PutIfAbsent
- BETree.Range
- BETree.Upsert
- CompareAndSwap
- Cursor.Next
- Cursor.Prev
- Cursor.Seek
- GobCodec
- Increment
- New
- NewDisk
- NewWithCompare
- WithBlockSize
- WithEpsilon
Constants ¶
const ( // DefaultEpsilon controls the buffer-vs-fanout trade-off. // At 0.5, an internal node has sqrt(B) children and sqrt(B) buffer slots. DefaultEpsilon = 0.5 // DefaultBlockSize is the number of entries per node (leaf capacity). // With DefaultEpsilon, this yields fanout=64 and buffer capacity=64. DefaultBlockSize = 4096 )
Variables ¶
var ( // ErrClosed is returned when an operation is attempted on a closed tree. ErrClosed = errors.New("fractaltree: tree is closed") // ErrInvalidEpsilon is returned when epsilon is not in the valid range (0, 1]. ErrInvalidEpsilon = errors.New("fractaltree: epsilon must be in (0, 1]") // ErrNilCompare is returned when a nil comparator is passed to NewWithCompare. ErrNilCompare = errors.New("fractaltree: compare function must not be nil") // ErrInvalidBlockSize is returned when block size is less than 2. ErrInvalidBlockSize = errors.New("fractaltree: block size must be at least 2") )
Sentinel errors returned by the library.
Functions ¶
This section is empty.
Types ¶
type BETree ¶
BETree is an in-memory B-epsilon-tree. It is safe for concurrent use.
func New ¶
New creates a BETree for keys that satisfy cmp.Ordered. The comparator is derived automatically via cmp.Compare.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[string, int]()
t.Put("alice", 42)
t.Put("bob", 7)
v, ok := t.Get("alice")
fmt.Println(v, ok)
fmt.Println(t.Len())
}
Output: 42 true 2
func NewWithCompare ¶
NewWithCompare creates a BETree with a user-supplied comparator. The comparator must return a negative value when a < b, zero when a == b, and a positive value when a > b.
Example ¶
package main
import (
"cmp"
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
type Point struct {
X, Y int
}
comparator := func(a, b Point) int {
if c := cmp.Compare(a.X, b.X); c != 0 {
return c
}
return cmp.Compare(a.Y, b.Y)
}
t, _ := fractaltree.NewWithCompare[Point, string](comparator)
t.Put(Point{1, 2}, "a")
t.Put(Point{1, 3}, "b")
t.Put(Point{2, 1}, "c")
for k, v := range t.All() {
fmt.Printf("(%d,%d)=%s ", k.X, k.Y, v)
}
fmt.Println()
}
Output: (1,2)=a (1,3)=b (2,1)=c
func (*BETree[K, V]) All ¶
All returns an iterator over all key-value pairs in ascending order. The iterator operates on a snapshot taken at call time; concurrent mutations after All returns are not visible to the iterator.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
t.Put(3, "three")
t.Put(1, "one")
t.Put(2, "two")
for k, v := range t.All() {
fmt.Println(k, v)
}
}
Output: 1 one 2 two 3 three
func (*BETree[K, V]) Ascend ¶
Ascend returns an iterator over all key-value pairs in ascending order.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
t.Put(3, "three")
t.Put(1, "one")
t.Put(2, "two")
for k, v := range t.Ascend() {
fmt.Println(k, v)
}
}
Output: 1 one 2 two 3 three
func (*BETree[K, V]) Clear ¶
func (t *BETree[K, V]) Clear()
Clear removes all entries from the tree.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
t.Put(1, "one")
t.Put(2, "two")
t.Clear()
fmt.Println(t.Len())
fmt.Println(t.Contains(1))
}
Output: 0 false
func (*BETree[K, V]) Close ¶
Close marks the tree as closed. Subsequent operations will still work but this method exists to satisfy the Tree interface. For BETree it is effectively a no-op; DiskBETree uses it to flush and close storage.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
t.Put(1, "one")
err := t.Close()
fmt.Println(err)
}
Output: <nil>
func (*BETree[K, V]) Contains ¶
Contains reports whether key exists in the tree.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[string, int]()
t.Put("alice", 1)
fmt.Println(t.Contains("alice"))
fmt.Println(t.Contains("bob"))
}
Output: true false
func (*BETree[K, V]) Cursor ¶
Cursor returns a cursor for manual bidirectional iteration. The cursor operates on a snapshot taken at call time. It starts in an invalid state; call Next, Prev, or Seek to position it.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
t.Put(10, "ten")
t.Put(20, "twenty")
t.Put(30, "thirty")
c := t.Cursor()
defer c.Close()
c.Seek(15) // positions at first key >= 15
fmt.Println(c.Key(), c.Value())
c.Next()
fmt.Println(c.Key(), c.Value())
}
Output: 20 twenty 30 thirty
func (*BETree[K, V]) Delete ¶
Delete removes key from the tree. Returns true if the key existed.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
t.Put(1, "one")
t.Put(2, "two")
fmt.Println(t.Delete(1))
fmt.Println(t.Delete(99))
fmt.Println(t.Len())
}
Output: true false 1
func (*BETree[K, V]) DeleteRange ¶
DeleteRange removes all keys in [lo, hi). Returns the count removed. Keys in the range are collected via a read-only traversal, then buffered as individual MsgDelete entries at the root. This gives O(K) insertion time (K = keys in range) with the benefit of batched flush to leaves.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
for i := range 10 {
t.Put(i, "v")
}
removed := t.DeleteRange(3, 7) // removes keys 3, 4, 5, 6
fmt.Println("removed:", removed)
fmt.Println("len:", t.Len())
fmt.Println("contains 3:", t.Contains(3))
fmt.Println("contains 7:", t.Contains(7))
}
Output: removed: 4 len: 6 contains 3: false contains 7: true
func (*BETree[K, V]) Descend ¶
Descend returns an iterator over all key-value pairs in descending order.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
t.Put(1, "one")
t.Put(2, "two")
t.Put(3, "three")
for k, v := range t.Descend() {
fmt.Println(k, v)
}
}
Output: 3 three 2 two 1 one
func (*BETree[K, V]) DescendRange ¶
DescendRange returns an iterator over keys in (lo, hi] in descending order. Note: the first parameter is the high bound, the second is the low bound.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
for i := range 10 {
t.Put(i, fmt.Sprintf("val%d", i))
}
// DescendRange(hi=7, lo=3) yields keys in (3, 7] = {7, 6, 5, 4} descending.
for k, v := range t.DescendRange(7, 3) {
fmt.Println(k, v)
}
}
Output: 7 val7 6 val6 5 val5 4 val4
func (*BETree[K, V]) Get ¶
Get returns the value for key and true, or the zero value and false.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
t.Put(1, "one")
v, ok := t.Get(1)
fmt.Println(v, ok)
v, ok = t.Get(99)
fmt.Println(v, ok)
}
Output: one true false
func (*BETree[K, V]) Len ¶
Len returns the number of key-value pairs in the tree.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
fmt.Println(t.Len())
t.Put(1, "one")
t.Put(2, "two")
fmt.Println(t.Len())
}
Output: 0 2
func (*BETree[K, V]) Put ¶
func (t *BETree[K, V]) Put(key K, value V)
Put inserts or overwrites the value for key.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
t.Put(1, "one")
t.Put(2, "two")
t.Put(1, "ONE") // overwrite
v, _ := t.Get(1)
fmt.Println(v)
fmt.Println(t.Len())
}
Output: ONE 2
func (*BETree[K, V]) PutIfAbsent ¶
PutIfAbsent inserts value only if key does not exist. Returns true if inserted.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[string, string]()
fmt.Println(t.PutIfAbsent("key", "first"))
fmt.Println(t.PutIfAbsent("key", "second"))
v, _ := t.Get("key")
fmt.Println(v)
}
Output: true false first
func (*BETree[K, V]) Range ¶
Range returns an iterator over keys in [lo, hi) in ascending order.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
for i := range 10 {
t.Put(i, fmt.Sprintf("val%d", i))
}
// Range [3, 6) yields keys 3, 4, 5 in ascending order.
for k, v := range t.Range(3, 6) {
fmt.Println(k, v)
}
}
Output: 3 val3 4 val4 5 val5
func (*BETree[K, V]) Upsert ¶
Upsert applies fn atomically to the value at key. If the key exists, fn receives a pointer to the current value and exists=true. Otherwise fn receives nil and exists=false. The returned value is stored.
The function is buffered as a MsgUpsert message and resolved lazily when the message reaches a leaf during flush, matching the B-epsilon-tree design.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[string, int]()
t.Upsert("counter", fractaltree.Increment(1))
t.Upsert("counter", fractaltree.Increment(1))
t.Upsert("counter", fractaltree.Increment(1))
v, _ := t.Get("counter")
fmt.Println(v)
}
Output: 3
type Codec ¶
Codec defines how values of type T are serialized and deserialized. Implementations must be safe for concurrent use.
type Cursor ¶
Cursor provides manual bidirectional iteration over a BETree. It operates on a snapshot taken at creation time; subsequent tree mutations are not visible. A Cursor must be closed when no longer needed.
func (*Cursor[K, V]) Close ¶
func (c *Cursor[K, V]) Close()
Close releases resources held by the cursor. It is safe to call Close multiple times.
func (*Cursor[K, V]) Key ¶
func (c *Cursor[K, V]) Key() K
Key returns the key at the cursor's current position. The caller must check Valid before calling Key.
func (*Cursor[K, V]) Next ¶
Next advances the cursor to the next key-value pair. Returns true if the cursor is positioned at a valid entry after advancing.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
t.Put(1, "one")
t.Put(2, "two")
t.Put(3, "three")
c := t.Cursor()
defer c.Close()
// Next advances forward. First call positions at the beginning.
for c.Next() {
fmt.Println(c.Key(), c.Value())
}
}
Output: 1 one 2 two 3 three
func (*Cursor[K, V]) Prev ¶
Prev moves the cursor to the previous key-value pair. Returns true if the cursor is positioned at a valid entry after moving.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
t.Put(1, "one")
t.Put(2, "two")
t.Put(3, "three")
c := t.Cursor()
defer c.Close()
// Prev moves backward. First call positions at the end.
for c.Prev() {
fmt.Println(c.Key(), c.Value())
}
}
Output: 3 three 2 two 1 one
func (*Cursor[K, V]) Seek ¶
Seek positions the cursor at the first key >= the given key. Returns true if such a key exists.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[int, string]()
t.Put(10, "ten")
t.Put(20, "twenty")
t.Put(30, "thirty")
c := t.Cursor()
defer c.Close()
// Seek positions at the first key >= the target.
fmt.Println(c.Seek(20))
fmt.Println(c.Key(), c.Value())
// Non-existent key: positions at next greater.
fmt.Println(c.Seek(15))
fmt.Println(c.Key(), c.Value())
// Beyond max: returns false.
fmt.Println(c.Seek(100))
}
Output: true 20 twenty true 20 twenty false
type DiskBETree ¶
DiskBETree wraps a BETree with a Flusher for disk persistence. All tree operations are delegated to the in-memory BETree. The Flusher is called during Close to sync data. Full lazy-loading and flush-time persistence are extension points for future implementation.
func NewDisk ¶
func NewDisk[K cmp.Ordered, V any]( f Flusher[K, V], opts ...DiskOption[K, V], ) (*DiskBETree[K, V], error)
NewDisk creates a DiskBETree for keys that satisfy cmp.Ordered.
Example ¶
package main
import (
"fmt"
"sync"
"github.com/aalhour/fractaltree"
)
func main() {
f := &memFlusher[string, int]{nodes: make(map[uint64][]byte)}
t, _ := fractaltree.NewDisk[string, int](f)
t.Put("key", 42)
v, _ := t.Get("key")
fmt.Println(v)
fmt.Println(t.Close())
}
type memFlusher[K, V any] struct {
mu sync.Mutex
nodes map[uint64][]byte
}
func (m *memFlusher[K, V]) WriteNode(id uint64, data []byte) error {
m.mu.Lock()
defer m.mu.Unlock()
m.nodes[id] = append([]byte(nil), data...)
return nil
}
func (m *memFlusher[K, V]) ReadNode(id uint64) ([]byte, error) {
m.mu.Lock()
defer m.mu.Unlock()
data, ok := m.nodes[id]
if !ok {
return nil, fmt.Errorf("node %d not found", id)
}
return data, nil
}
func (m *memFlusher[K, V]) Sync() error { return nil }
func (m *memFlusher[K, V]) Close() error { return nil }
Output: 42 <nil>
func NewDiskWithCompare ¶
func NewDiskWithCompare[K any, V any]( compare func(K, K) int, f Flusher[K, V], opts ...DiskOption[K, V], ) (*DiskBETree[K, V], error)
NewDiskWithCompare creates a DiskBETree with a user-supplied comparator.
func (*DiskBETree[K, V]) All ¶
func (d *DiskBETree[K, V]) All() iter.Seq2[K, V]
All delegates to the underlying BETree.
func (*DiskBETree[K, V]) Ascend ¶
func (d *DiskBETree[K, V]) Ascend() iter.Seq2[K, V]
Ascend delegates to the underlying BETree.
func (*DiskBETree[K, V]) Clear ¶
func (d *DiskBETree[K, V]) Clear()
Clear delegates to the underlying BETree.
func (*DiskBETree[K, V]) Close ¶
func (d *DiskBETree[K, V]) Close() error
Close syncs and closes the flusher, then marks the tree as closed.
func (*DiskBETree[K, V]) Contains ¶
func (d *DiskBETree[K, V]) Contains(key K) bool
Contains delegates to the underlying BETree.
func (*DiskBETree[K, V]) Cursor ¶
func (d *DiskBETree[K, V]) Cursor() *Cursor[K, V]
Cursor delegates to the underlying BETree.
func (*DiskBETree[K, V]) Delete ¶
func (d *DiskBETree[K, V]) Delete(key K) bool
Delete delegates to the underlying BETree.
func (*DiskBETree[K, V]) DeleteRange ¶
func (d *DiskBETree[K, V]) DeleteRange(lo, hi K) int
DeleteRange delegates to the underlying BETree.
func (*DiskBETree[K, V]) Descend ¶
func (d *DiskBETree[K, V]) Descend() iter.Seq2[K, V]
Descend delegates to the underlying BETree.
func (*DiskBETree[K, V]) DescendRange ¶
func (d *DiskBETree[K, V]) DescendRange(hi, lo K) iter.Seq2[K, V]
DescendRange delegates to the underlying BETree.
func (*DiskBETree[K, V]) Get ¶
func (d *DiskBETree[K, V]) Get(key K) (V, bool)
Get delegates to the underlying BETree.
func (*DiskBETree[K, V]) Len ¶
func (d *DiskBETree[K, V]) Len() int
Len delegates to the underlying BETree.
func (*DiskBETree[K, V]) Put ¶
func (d *DiskBETree[K, V]) Put(key K, value V)
Put delegates to the underlying BETree.
func (*DiskBETree[K, V]) PutIfAbsent ¶
func (d *DiskBETree[K, V]) PutIfAbsent(key K, v V) bool
PutIfAbsent delegates to the underlying BETree.
func (*DiskBETree[K, V]) Range ¶
func (d *DiskBETree[K, V]) Range(lo, hi K) iter.Seq2[K, V]
Range delegates to the underlying BETree.
func (*DiskBETree[K, V]) Upsert ¶
func (d *DiskBETree[K, V]) Upsert(key K, fn UpsertFn[V])
Upsert delegates to the underlying BETree.
type DiskOption ¶
type DiskOption[K, V any] func(*diskOptions[K, V])
DiskOption configures a DiskBETree.
func WithDiskBlockSize ¶
func WithDiskBlockSize[K, V any](size int) DiskOption[K, V]
WithDiskBlockSize sets the block size for the underlying tree.
func WithDiskEpsilon ¶
func WithDiskEpsilon[K, V any](eps float64) DiskOption[K, V]
WithDiskEpsilon sets the epsilon parameter for the underlying tree.
func WithKeyCodec ¶
func WithKeyCodec[K, V any](c Codec[K]) DiskOption[K, V]
WithKeyCodec sets the codec used to serialize keys for disk storage. Defaults to GobCodec if not set.
func WithValueCodec ¶
func WithValueCodec[K, V any](c Codec[V]) DiskOption[K, V]
WithValueCodec sets the codec used to serialize values for disk storage. Defaults to GobCodec if not set.
type Flusher ¶
type Flusher[K, V any] interface { // WriteNode persists the serialized node data under the given ID. WriteNode(id uint64, data []byte) error // ReadNode retrieves the serialized node data for the given ID. ReadNode(id uint64) ([]byte, error) // Sync ensures all written data is durable. Sync() error // Close releases any resources held by the flusher. Close() error }
Flusher defines the storage backend for a DiskBETree. Implementations handle persisting and retrieving serialized node data.
type GobCodec ¶
type GobCodec[T any] struct{}
GobCodec is a Codec backed by encoding/gob. It works for any type that gob can handle (primitives, structs with exported fields, etc.).
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
c := fractaltree.GobCodec[string]{}
data, _ := c.Encode("hello")
v, _ := c.Decode(data)
fmt.Println(v)
fmt.Println(len(data), "bytes")
}
Output: hello 9 bytes
type Message ¶
type Message[K any, V any] struct { Kind MsgKind Key K // EndKey is the exclusive upper bound for MsgDeleteRange. Unused for other kinds. EndKey K // Value is the payload for MsgPut. Zero value for other kinds. Value V // Fn is the upsert function for MsgUpsert. Nil for other kinds. Fn UpsertFn[V] // contains filtered or unexported fields }
Message represents a buffered operation in a B-epsilon-tree node. Messages flow from root toward leaves during flushes.
type MsgKind ¶
type MsgKind uint8
MsgKind identifies the type of a buffered message in a B-epsilon-tree node.
type Option ¶
type Option func(*options)
Option configures a BETree.
func WithBlockSize ¶
WithBlockSize sets the logical node size measured in number of entries. Must be at least 2. Default is 4096.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
// Smaller block size = more frequent flushes and splits.
t, _ := fractaltree.New[int, string](fractaltree.WithBlockSize(64))
t.Put(1, "one")
fmt.Println(t.Len())
}
Output: 1
func WithEpsilon ¶
WithEpsilon sets the epsilon parameter that controls the trade-off between write throughput and read latency. Must be in (0, 1].
Lower epsilon means larger buffers (better write amortization) but higher fanout cost. Higher epsilon means smaller buffers but faster point queries.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
// Lower epsilon = larger buffers, better write amortization.
t, _ := fractaltree.New[int, string](fractaltree.WithEpsilon(0.3))
t.Put(1, "one")
fmt.Println(t.Len())
}
Output: 1
type Tree ¶
type Tree[K any, V any] interface { // Put inserts or overwrites the value for key. Put(key K, value V) // Get returns the value for key and true, or the zero value and false. Get(key K) (V, bool) // Delete removes key. Returns true if the key existed. Delete(key K) bool // DeleteRange removes all keys in [lo, hi). Returns the count removed. DeleteRange(lo, hi K) int // Contains reports whether key exists in the tree. Contains(key K) bool // Len returns the number of key-value pairs in the tree. Len() int // Clear removes all entries from the tree. Clear() // Upsert applies fn atomically to the value at key. Upsert(key K, fn UpsertFn[V]) // PutIfAbsent inserts value only if key does not exist. Returns true if inserted. PutIfAbsent(key K, value V) bool // All returns an iterator over all key-value pairs in ascending order. All() iter.Seq2[K, V] // Ascend returns an iterator over all key-value pairs in ascending order. Ascend() iter.Seq2[K, V] // Descend returns an iterator over all key-value pairs in descending order. Descend() iter.Seq2[K, V] // Range returns an iterator over keys in [lo, hi) in ascending order. Range(lo, hi K) iter.Seq2[K, V] // DescendRange returns an iterator over keys in (lo, hi] in descending order. DescendRange(hi, lo K) iter.Seq2[K, V] // Cursor returns a positioned cursor for manual bidirectional iteration. Cursor() *Cursor[K, V] // Close releases resources held by the tree. Close() error }
Tree is the interface implemented by both BETree (in-memory) and DiskBETree (with disk flush hooks).
type UpsertFn ¶
UpsertFn is called during upsert resolution. If the key exists, existing points to the current value and exists is true. Otherwise existing is nil and exists is false. The returned value is stored as the new value.
func CompareAndSwap ¶
func CompareAndSwap[V comparable](expected, newVal V) UpsertFn[V]
CompareAndSwap returns an UpsertFn that replaces the value with newVal only if the current value equals expected. If the key does not exist or the current value differs, the existing value is retained unchanged.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[string, string]()
t.Put("status", "pending")
// CAS only updates if the current value matches expected.
t.Upsert("status", fractaltree.CompareAndSwap("pending", "active"))
v, _ := t.Get("status")
fmt.Println(v)
// Mismatch: value stays unchanged.
t.Upsert("status", fractaltree.CompareAndSwap("pending", "closed"))
v, _ = t.Get("status")
fmt.Println(v)
}
Output: active active
func Increment ¶
Increment returns an UpsertFn that adds delta to the existing value. If the key does not exist, delta is used as the initial value.
Example ¶
package main
import (
"fmt"
"github.com/aalhour/fractaltree"
)
func main() {
t, _ := fractaltree.New[string, int]()
// Increment creates an UpsertFn that adds delta to the value.
// If the key is absent, delta becomes the initial value.
t.Upsert("hits", fractaltree.Increment(1))
t.Upsert("hits", fractaltree.Increment(5))
v, _ := t.Get("hits")
fmt.Println(v)
}
Output: 6
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
basic
command
Basic demonstrates Put, Get, Delete, Contains, Len, and Clear.
|
Basic demonstrates Put, Get, Delete, Contains, Len, and Clear. |
|
bulkimport
command
Bulkimport benchmarks inserting 1M keys to demonstrate the write amortization benefit of the B-epsilon-tree's message buffering.
|
Bulkimport benchmarks inserting 1M keys to demonstrate the write amortization benefit of the B-epsilon-tree's message buffering. |
|
comparator
command
Comparator demonstrates NewWithCompare with a composite key struct.
|
Comparator demonstrates NewWithCompare with a composite key struct. |
|
concurrent
command
Concurrent demonstrates safe concurrent reads and writes.
|
Concurrent demonstrates safe concurrent reads and writes. |
|
disktree-binary
command
Disktree-binary demonstrates DiskBETree with a codec backed by encoding/binary for fixed-size types (int32 keys, float64 values).
|
Disktree-binary demonstrates DiskBETree with a codec backed by encoding/binary for fixed-size types (int32 keys, float64 values). |
|
disktree-gob
command
Disktree-gob demonstrates DiskBETree with the default GobCodec.
|
Disktree-gob demonstrates DiskBETree with the default GobCodec. |
|
disktree-varint
command
Disktree-varint demonstrates DiskBETree with a hand-rolled codec that encodes strings as a varint length prefix followed by raw UTF-8 bytes.
|
Disktree-varint demonstrates DiskBETree with a hand-rolled codec that encodes strings as a varint length prefix followed by raw UTF-8 bytes. |
|
eviction
command
Eviction demonstrates LRU-style cache eviction using a composite key that orders by access time, allowing Ascend to find the oldest entries.
|
Eviction demonstrates LRU-style cache eviction using a composite key that orders by access time, allowing Ascend to find the oldest entries. |
|
leaderboard
command
Leaderboard demonstrates using Descend to maintain a live top-K leaderboard.
|
Leaderboard demonstrates using Descend to maintain a live top-K leaderboard. |
|
mergejoin
command
Mergejoin demonstrates using two cursors to perform a merge join between two trees — finding keys that exist in both.
|
Mergejoin demonstrates using two cursors to perform a merge join between two trees — finding keys that exist in both. |
|
prefixscan
command
Prefixscan demonstrates scanning all keys with a common prefix using Range.
|
Prefixscan demonstrates scanning all keys with a common prefix using Range. |
|
range
command
Range demonstrates Range queries, Descend, and Cursor usage.
|
Range demonstrates Range queries, Descend, and Cursor usage. |
|
ratelimiter
command
Ratelimiter demonstrates a sliding-window rate limiter using Increment to count requests per time bucket and DeleteRange to expire old buckets.
|
Ratelimiter demonstrates a sliding-window rate limiter using Increment to count requests per time bucket and DeleteRange to expire old buckets. |
|
timeseries
command
Timeseries demonstrates using Range to query events within a time window.
|
Timeseries demonstrates using Range to query events within a time window. |
|
upsert
command
Upsert demonstrates Upsert, PutIfAbsent, Increment, and CompareAndSwap.
|
Upsert demonstrates Upsert, PutIfAbsent, Increment, and CompareAndSwap. |