Skip to content

Commit c6832df

Browse files
Dan Lainemaru-ava
authored andcommitted
Remove history btree (#1861)
1 parent cad2525 commit c6832df

File tree

2 files changed

+150
-129
lines changed

2 files changed

+150
-129
lines changed

x/merkledb/history.go

Lines changed: 126 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/google/btree"
1212

1313
"github.com/ava-labs/avalanchego/ids"
14+
"github.com/ava-labs/avalanchego/utils/buffer"
1415
"github.com/ava-labs/avalanchego/utils/maybe"
1516
)
1617

@@ -22,17 +23,18 @@ var (
2223
// stores previous trie states
2324
type trieHistory struct {
2425
// Root ID --> The most recent change resulting in [rootID].
25-
lastChanges map[ids.ID]*changeSummaryAndIndex
26+
lastChanges map[ids.ID]*changeSummaryAndInsertNumber
2627

2728
// Maximum number of previous roots/changes to store in [history].
2829
maxHistoryLen int
2930

3031
// Contains the history.
3132
// Sorted by increasing order of insertion.
3233
// Contains at most [maxHistoryLen] values.
33-
history *btree.BTreeG[*changeSummaryAndIndex]
34+
history buffer.Deque[*changeSummaryAndInsertNumber]
3435

35-
nextIndex uint64
36+
// Each change is tagged with this monotonic increasing number.
37+
nextInsertNumber uint64
3638
}
3739

3840
// Tracks the beginning and ending state of a value.
@@ -43,11 +45,11 @@ type change[T any] struct {
4345

4446
// Wrapper around a changeSummary that allows comparison
4547
// of when the change was made.
46-
type changeSummaryAndIndex struct {
48+
type changeSummaryAndInsertNumber struct {
4749
*changeSummary
48-
// Another changeSummaryAndIndex with a greater
49-
// [index] means that change was after this one.
50-
index uint64
50+
// Another changeSummaryAndInsertNumber with a greater
51+
// [insertNumber] means that change was after this one.
52+
insertNumber uint64
5153
}
5254

5355
// Tracks all of the node and value changes that resulted in the rootID.
@@ -67,13 +69,8 @@ func newChangeSummary(estimatedSize int) *changeSummary {
6769
func newTrieHistory(maxHistoryLookback int) *trieHistory {
6870
return &trieHistory{
6971
maxHistoryLen: maxHistoryLookback,
70-
history: btree.NewG(
71-
2,
72-
func(a, b *changeSummaryAndIndex) bool {
73-
return a.index < b.index
74-
},
75-
),
76-
lastChanges: make(map[ids.ID]*changeSummaryAndIndex),
72+
history: buffer.NewUnboundedDeque[*changeSummaryAndInsertNumber](maxHistoryLookback),
73+
lastChanges: make(map[ids.ID]*changeSummaryAndInsertNumber),
7774
}
7875
}
7976

@@ -88,39 +85,54 @@ func (th *trieHistory) getValueChanges(startRoot, endRoot ids.ID, start []byte,
8885
return newChangeSummary(maxLength), nil
8986
}
9087

91-
// Confirm there's a change resulting in [startRoot] before
92-
// a change resulting in [endRoot] in the history.
93-
// [lastEndRootChange] is the last change in the history resulting in [endRoot].
94-
lastEndRootChange, ok := th.lastChanges[endRoot]
88+
// [endRootChanges] is the last change in the history resulting in [endRoot].
89+
endRootChanges, ok := th.lastChanges[endRoot]
9590
if !ok {
9691
return nil, ErrRootIDNotPresent
9792
}
9893

99-
// [startRootChanges] is the last appearance of [startRoot]
94+
// Confirm there's a change resulting in [startRoot] before
95+
// a change resulting in [endRoot] in the history.
96+
// [startRootChanges] is the last appearance of [startRoot].
10097
startRootChanges, ok := th.lastChanges[startRoot]
10198
if !ok {
10299
return nil, ErrStartRootNotFound
103100
}
104101

105-
// startRootChanges is after the lastEndRootChange, but that is just the latest appearance of start root
106-
// there may be an earlier entry, so attempt to find an entry that comes before lastEndRootChange
107-
if startRootChanges.index > lastEndRootChange.index {
108-
th.history.DescendLessOrEqual(
109-
lastEndRootChange,
110-
func(item *changeSummaryAndIndex) bool {
111-
if item == lastEndRootChange {
112-
return true // Skip first iteration
113-
}
114-
if item.rootID == startRoot {
115-
startRootChanges = item
116-
return false
117-
}
118-
return true
119-
},
120-
)
121-
// There's no change resulting in [startRoot] before the latest change resulting in [endRoot].
122-
if startRootChanges.index > lastEndRootChange.index {
123-
return nil, ErrStartRootNotFound
102+
var (
103+
// The insert number of the last element in [th.history].
104+
mostRecentChangeInsertNumber = th.nextInsertNumber - 1
105+
106+
// The index within [th.history] of its last element.
107+
mostRecentChangeIndex = th.history.Len() - 1
108+
109+
// The difference between the last index in [th.history] and the index of [endRootChanges].
110+
endToMostRecentOffset = int(mostRecentChangeInsertNumber - endRootChanges.insertNumber)
111+
112+
// The index in [th.history] of the latest change resulting in [endRoot].
113+
endRootIndex = mostRecentChangeIndex - endToMostRecentOffset
114+
)
115+
116+
if startRootChanges.insertNumber > endRootChanges.insertNumber {
117+
// [startRootChanges] happened after [endRootChanges].
118+
// However, that is just the *latest* change resulting in [startRoot].
119+
// Attempt to find a change resulting in [startRoot] before [endRootChanges].
120+
//
121+
// Translate the insert number to the index in [th.history] so we can iterate
122+
// backward from [endRootChanges].
123+
for i := endRootIndex - 1; i >= 0; i-- {
124+
changes, _ := th.history.Index(i)
125+
126+
if changes.rootID == startRoot {
127+
// [startRootChanges] is now the last change resulting in
128+
// [startRoot] before [endRootChanges].
129+
startRootChanges = changes
130+
break
131+
}
132+
133+
if i == 0 {
134+
return nil, ErrStartRootNotFound
135+
}
124136
}
125137
}
126138

@@ -132,58 +144,56 @@ func (th *trieHistory) getValueChanges(startRoot, endRoot ids.ID, start []byte,
132144
},
133145
)
134146

135-
startPath := newPath(start)
136-
endPath := maybe.Bind(end, newPath)
147+
var (
148+
startPath = newPath(start)
149+
endPath = maybe.Bind(end, newPath)
150+
151+
// For each element in the history in the range between [startRoot]'s
152+
// last appearance (exclusive) and [endRoot]'s last appearance (inclusive),
153+
// add the changes to keys in [start, end] to [combinedChanges].
154+
// Only the key-value pairs with the greatest [maxLength] keys will be kept.
155+
combinedChanges = newChangeSummary(maxLength)
156+
157+
// The difference between the index of [startRootChanges] and [endRootChanges] in [th.history].
158+
startToEndOffset = int(endRootChanges.insertNumber - startRootChanges.insertNumber)
137159

138-
// For each element in the history in the range between [startRoot]'s
139-
// last appearance (exclusive) and [endRoot]'s last appearance (inclusive),
140-
// add the changes to keys in [start, end] to [combinedChanges].
141-
// Only the key-value pairs with the greatest [maxLength] keys will be kept.
142-
combinedChanges := newChangeSummary(maxLength)
160+
// The index of the last change resulting in [startRoot]
161+
// which occurs before [endRootChanges].
162+
startRootIndex = endRootIndex - startToEndOffset
163+
)
143164

144165
// For each change after [startRootChanges] up to and including
145-
// [lastEndRootChange], record the change in [combinedChanges].
146-
th.history.AscendGreaterOrEqual(
147-
startRootChanges,
148-
func(item *changeSummaryAndIndex) bool {
149-
if item == startRootChanges {
150-
// Start from the first change after [startRootChanges].
151-
return true
152-
}
153-
if item.index > lastEndRootChange.index {
154-
// Don't go past [lastEndRootChange].
155-
return false
166+
// [endRootChanges], record the change in [combinedChanges].
167+
for i := startRootIndex + 1; i <= endRootIndex; i++ {
168+
changes, _ := th.history.Index(i)
169+
170+
// Add the changes from this commit to [combinedChanges].
171+
for key, valueChange := range changes.values {
172+
// The key is outside the range [start, end].
173+
if (len(startPath) > 0 && key.Compare(startPath) < 0) ||
174+
(end.HasValue() && key.Compare(endPath.Value()) > 0) {
175+
continue
156176
}
157177

158-
// Add the changes from this commit to [combinedChanges].
159-
for key, valueChange := range item.values {
160-
// The key is outside the range [start, end].
161-
if (len(startPath) > 0 && key.Compare(startPath) < 0) ||
162-
(end.HasValue() && key.Compare(endPath.Value()) > 0) {
163-
continue
178+
// A change to this key already exists in [combinedChanges]
179+
// so update its before value with the earlier before value
180+
if existing, ok := combinedChanges.values[key]; ok {
181+
existing.after = valueChange.after
182+
if existing.before.HasValue() == existing.after.HasValue() &&
183+
bytes.Equal(existing.before.Value(), existing.after.Value()) {
184+
// The change to this key is a no-op, so remove it from [combinedChanges].
185+
delete(combinedChanges.values, key)
186+
sortedKeys.Delete(key)
164187
}
165-
166-
// A change to this key already exists in [combinedChanges]
167-
// so update its before value with the earlier before value
168-
if existing, ok := combinedChanges.values[key]; ok {
169-
existing.after = valueChange.after
170-
if existing.before.HasValue() == existing.after.HasValue() &&
171-
bytes.Equal(existing.before.Value(), existing.after.Value()) {
172-
// The change to this key is a no-op, so remove it from [combinedChanges].
173-
delete(combinedChanges.values, key)
174-
sortedKeys.Delete(key)
175-
}
176-
} else {
177-
combinedChanges.values[key] = &change[maybe.Maybe[[]byte]]{
178-
before: valueChange.before,
179-
after: valueChange.after,
180-
}
181-
sortedKeys.ReplaceOrInsert(key)
188+
} else {
189+
combinedChanges.values[key] = &change[maybe.Maybe[[]byte]]{
190+
before: valueChange.before,
191+
after: valueChange.after,
182192
}
193+
sortedKeys.ReplaceOrInsert(key)
183194
}
184-
// continue to next change list
185-
return true
186-
})
195+
}
196+
}
187197

188198
// Keep only the smallest [maxLength] items in [combinedChanges.values].
189199
for sortedKeys.Len() > maxLength {
@@ -207,41 +217,42 @@ func (th *trieHistory) getChangesToGetToRoot(rootID ids.ID, start []byte, end ma
207217
}
208218

209219
var (
210-
startPath = newPath(start)
211-
endPath = maybe.Bind(end, newPath)
212-
combinedChanges = newChangeSummary(defaultPreallocationSize)
220+
startPath = newPath(start)
221+
endPath = maybe.Bind(end, newPath)
222+
combinedChanges = newChangeSummary(defaultPreallocationSize)
223+
mostRecentChangeInsertNumber = th.nextInsertNumber - 1
224+
mostRecentChangeIndex = th.history.Len() - 1
225+
offset = int(mostRecentChangeInsertNumber - lastRootChange.insertNumber)
226+
lastRootChangeIndex = mostRecentChangeIndex - offset
213227
)
214228

215229
// Go backward from the most recent change in the history up to but
216230
// not including the last change resulting in [rootID].
217231
// Record each change in [combinedChanges].
218-
th.history.Descend(
219-
func(item *changeSummaryAndIndex) bool {
220-
if item == lastRootChange {
221-
return false
222-
}
223-
for key, changedNode := range item.nodes {
224-
combinedChanges.nodes[key] = &change[*node]{
225-
after: changedNode.before,
226-
}
232+
for i := mostRecentChangeIndex; i > lastRootChangeIndex; i-- {
233+
changes, _ := th.history.Index(i)
234+
235+
for key, changedNode := range changes.nodes {
236+
combinedChanges.nodes[key] = &change[*node]{
237+
after: changedNode.before,
227238
}
239+
}
228240

229-
for key, valueChange := range item.values {
230-
if (len(startPath) == 0 || key.Compare(startPath) >= 0) &&
231-
(endPath.IsNothing() || key.Compare(endPath.Value()) <= 0) {
232-
if existing, ok := combinedChanges.values[key]; ok {
233-
existing.after = valueChange.before
234-
} else {
235-
combinedChanges.values[key] = &change[maybe.Maybe[[]byte]]{
236-
before: valueChange.after,
237-
after: valueChange.before,
238-
}
241+
for key, valueChange := range changes.values {
242+
if (len(startPath) == 0 || key.Compare(startPath) >= 0) &&
243+
(endPath.IsNothing() || key.Compare(endPath.Value()) <= 0) {
244+
if existing, ok := combinedChanges.values[key]; ok {
245+
existing.after = valueChange.before
246+
} else {
247+
combinedChanges.values[key] = &change[maybe.Maybe[[]byte]]{
248+
before: valueChange.after,
249+
after: valueChange.before,
239250
}
240251
}
241252
}
242-
return true
243-
},
244-
)
253+
}
254+
}
255+
245256
return combinedChanges, nil
246257
}
247258

@@ -252,25 +263,27 @@ func (th *trieHistory) record(changes *changeSummary) {
252263
return
253264
}
254265

255-
for th.history.Len() == th.maxHistoryLen {
266+
if th.history.Len() == th.maxHistoryLen {
256267
// This change causes us to go over our lookback limit.
257268
// Remove the oldest set of changes.
258-
oldestEntry, _ := th.history.DeleteMin()
269+
oldestEntry, _ := th.history.PopLeft()
270+
259271
latestChange := th.lastChanges[oldestEntry.rootID]
260272
if latestChange == oldestEntry {
261273
// The removed change was the most recent resulting in this root ID.
262274
delete(th.lastChanges, oldestEntry.rootID)
263275
}
264276
}
265277

266-
changesAndIndex := &changeSummaryAndIndex{
278+
changesAndIndex := &changeSummaryAndInsertNumber{
267279
changeSummary: changes,
268-
index: th.nextIndex,
280+
insertNumber: th.nextInsertNumber,
269281
}
270-
th.nextIndex++
282+
th.nextInsertNumber++
271283

272284
// Add [changes] to the sorted change list.
273-
_, _ = th.history.ReplaceOrInsert(changesAndIndex)
285+
_ = th.history.PushRight(changesAndIndex)
286+
274287
// Mark that this is the most recent change resulting in [changes.rootID].
275288
th.lastChanges[changes.rootID] = changesAndIndex
276289
}

0 commit comments

Comments
 (0)