-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtree.go
More file actions
159 lines (137 loc) · 3.87 KB
/
Copy pathtree.go
File metadata and controls
159 lines (137 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
package dmt implements a wrapper around an immutable radix tree data structure.
A radix tree (also known as a radix trie or compact prefix tree) is a space-optimized
tree structure that is particularly efficient for string or byte slice keys. It compresses
common prefixes to save space and enables fast lookups, insertions, and prefix-based searches.
*/
package dmt
import (
"bytes"
"container/ring"
"sync"
"time"
iradix "github.com/hashicorp/go-immutable-radix/v2"
)
/*
Tree wraps an immutable radix tree implementation from hashicorp/go-immutable-radix.
It stores byte slices as both keys and values, providing efficient prefix-based operations.
The immutable nature ensures thread-safety and enables persistent data structures.
*/
type Tree struct {
root *iradix.Tree[[]byte]
updated bool
perfs *ring.Ring
persist *PersistentStore
term uint64
logIndex uint64
mu sync.RWMutex
}
/*
NewTree creates and returns a new empty Tree instance.
The underlying radix tree is initialized with no entries.
*/
func NewTree(persistDir string) (*Tree, error) {
var persist *PersistentStore
var err error
var term, index uint64
if persistDir != "" {
persist, err = NewPersistentStore(persistDir)
if err != nil {
return nil, err
}
term, index = persist.GetLastState()
}
return &Tree{
root: iradix.New[[]byte](),
perfs: ring.New(10),
persist: persist,
term: term,
logIndex: index,
}, nil
}
/*
Seek performs a prefix-based search in the tree, finding the first value whose key
is greater than or equal to the provided key in lexicographical order.
Returns the value and true if found, or nil and false if no such key exists.
*/
func (tree *Tree) Seek(key []byte) ([]byte, bool) {
t := time.Now()
it := tree.root.Root().Iterator()
it.SeekLowerBound(key)
for k, v, ok := it.Next(); ok; k, v, ok = it.Next() {
if bytes.Compare(k, key) >= 0 {
return v, true
}
}
tree.perfs.Value = time.Since(t).Nanoseconds()
tree.perfs = tree.perfs.Next()
return nil, false
}
/*
Insert adds or updates a key-value pair in the tree.
Due to the immutable nature of the tree, this operation creates a new version
of the tree rather than modifying the existing one.
Returns the updated tree and a boolean indicating if the tree was modified.
*/
func (tree *Tree) Insert(key []byte, value []byte) (*Tree, bool) {
tree.mu.Lock()
defer tree.mu.Unlock()
t := time.Now()
tree.root, _, tree.updated = tree.root.Insert(key, value)
if tree.updated {
tree.logIndex++
// Log to WAL if persistence is enabled
if tree.persist != nil {
if err := tree.persist.LogInsert(key, value, tree.term, tree.logIndex); err != nil {
// Log error but don't fail the operation
// TODO: Add proper error handling/logging
_ = err
}
}
}
tree.perfs.Value = time.Since(t).Nanoseconds()
tree.perfs = tree.perfs.Next()
return tree, tree.updated
}
/*
Get retrieves the value associated with the given key.
Returns the value and true if the key exists, or nil and false if it doesn't.
*/
func (tree *Tree) Get(key []byte) ([]byte, bool) {
t := time.Now()
v, ok := tree.root.Get(key)
tree.perfs.Value = time.Since(t).Nanoseconds()
tree.perfs = tree.perfs.Next()
return v, ok
}
/*
AVG returns the average performance of the tree in nanoseconds.
*/
func (tree *Tree) AVG() int64 {
var sum int64
tree.perfs.Do(func(v any) {
sum += v.(int64)
})
return sum / int64(tree.perfs.Len())
}
/*
Close closes the tree and persists any remaining data.
*/
func (tree *Tree) Close() error {
if tree.persist != nil {
return tree.persist.Close()
}
return nil
}
// UpdateTerm updates the current term number
func (tree *Tree) UpdateTerm(term uint64) {
tree.mu.Lock()
defer tree.mu.Unlock()
tree.term = term
}
// GetLogState returns the current term and log index
func (tree *Tree) GetLogState() (term, index uint64) {
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.term, tree.logIndex
}