Skip to content

Commit 5f889f6

Browse files
committed
implement unsaved fast iterator to be used in mutable tree
1 parent a7e7fb2 commit 5f889f6

File tree

4 files changed

+387
-72
lines changed

4 files changed

+387
-72
lines changed

iterator_test.go

Lines changed: 150 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package iavl
22

33
import (
4+
"math/rand"
45
"sort"
56
"testing"
67

@@ -32,6 +33,56 @@ func TestIterator_NewIterator_NilTree_Failure(t *testing.T) {
3233
performTest(t, itr)
3334
require.ErrorIs(t, errFastIteratorNilNdbGiven, itr.Error())
3435
})
36+
37+
t.Run("Unsaved Fast Iterator", func(t *testing.T) {
38+
itr := NewUnsavedFastIterator(start, end, ascending, nil, map[string]*FastNode{}, map[string]interface{}{})
39+
performTest(t, itr)
40+
require.ErrorIs(t, errFastIteratorNilNdbGiven, itr.Error())
41+
})
42+
}
43+
44+
func TestUnsavedFastIterator_NewIterator_NilAdditions_Failure(t *testing.T) {
45+
var start, end []byte = []byte{'a'}, []byte{'c'}
46+
ascending := true
47+
48+
performTest := func(t *testing.T, itr dbm.Iterator) {
49+
require.NotNil(t, itr)
50+
require.False(t, itr.Valid())
51+
actualsStart, actualEnd := itr.Domain()
52+
require.Equal(t, start, actualsStart)
53+
require.Equal(t, end, actualEnd)
54+
require.Error(t, itr.Error())
55+
}
56+
57+
t.Run("Nil additions given", func(t *testing.T) {
58+
tree, err := NewMutableTree(dbm.NewMemDB(), 0)
59+
require.NoError(t, err)
60+
itr := NewUnsavedFastIterator(start, end, ascending, tree.ndb, nil, tree.unsavedFastNodeRemovals)
61+
performTest(t, itr)
62+
require.ErrorIs(t, errUnsavedFastIteratorNilAdditionsGiven, itr.Error())
63+
})
64+
65+
t.Run("Nil removals given", func(t *testing.T) {
66+
tree, err := NewMutableTree(dbm.NewMemDB(), 0)
67+
require.NoError(t, err)
68+
itr := NewUnsavedFastIterator(start, end, ascending, tree.ndb, tree.unsavedFastNodeAdditions, nil)
69+
performTest(t, itr)
70+
require.ErrorIs(t, errUnsavedFastIteratorNilRemovalsGiven, itr.Error())
71+
})
72+
73+
t.Run("All nil", func(t *testing.T) {
74+
itr := NewUnsavedFastIterator(start, end, ascending, nil, nil, nil)
75+
performTest(t, itr)
76+
require.ErrorIs(t, errFastIteratorNilNdbGiven, itr.Error())
77+
})
78+
79+
t.Run("Additions and removals are nil", func(t *testing.T) {
80+
tree, err := NewMutableTree(dbm.NewMemDB(), 0)
81+
require.NoError(t, err)
82+
itr := NewUnsavedFastIterator(start, end, ascending, tree.ndb, nil, nil)
83+
performTest(t, itr)
84+
require.ErrorIs(t, errUnsavedFastIteratorNilAdditionsGiven, itr.Error())
85+
})
3586
}
3687

3788
func TestIterator_Empty_Invalid(t *testing.T) {
@@ -57,6 +108,11 @@ func TestIterator_Empty_Invalid(t *testing.T) {
57108
itr, mirror := setupFastIteratorAndMirror(t, config)
58109
performTest(t, itr, mirror)
59110
})
111+
112+
t.Run("Unsaved Fast Iterator", func(t *testing.T) {
113+
itr, mirror := setupUnsavedFastIterator(t, config)
114+
performTest(t, itr, mirror)
115+
})
60116
}
61117

62118
func TestIterator_Basic_Ranged_Ascending_Success(t *testing.T) {
@@ -89,6 +145,12 @@ func TestIterator_Basic_Ranged_Ascending_Success(t *testing.T) {
89145
require.True(t, itr.Valid())
90146
performTest(t, itr, mirror)
91147
})
148+
149+
t.Run("Unsaved Fast Iterator", func(t *testing.T) {
150+
itr, mirror := setupUnsavedFastIterator(t, config)
151+
require.True(t, itr.Valid())
152+
performTest(t, itr, mirror)
153+
})
92154
}
93155

94156
func TestIterator_Basic_Ranged_Descending_Success(t *testing.T) {
@@ -121,6 +183,12 @@ func TestIterator_Basic_Ranged_Descending_Success(t *testing.T) {
121183
require.True(t, itr.Valid())
122184
performTest(t, itr, mirror)
123185
})
186+
187+
t.Run("Unsaved Fast Iterator", func(t *testing.T) {
188+
itr, mirror := setupUnsavedFastIterator(t, config)
189+
require.True(t, itr.Valid())
190+
performTest(t, itr, mirror)
191+
})
124192
}
125193

126194
func TestIterator_Basic_Full_Ascending_Success(t *testing.T) {
@@ -133,9 +201,6 @@ func TestIterator_Basic_Full_Ascending_Success(t *testing.T) {
133201
}
134202

135203
performTest := func(t *testing.T, itr dbm.Iterator, mirror [][]string) {
136-
137-
require.Equal(t, 25, len(mirror))
138-
139204
actualStart, actualEnd := itr.Domain()
140205
require.Equal(t, config.startIterate, actualStart)
141206
require.Equal(t, config.endIterate, actualEnd)
@@ -148,12 +213,21 @@ func TestIterator_Basic_Full_Ascending_Success(t *testing.T) {
148213
t.Run("Iterator", func(t *testing.T) {
149214
itr, mirror := setupIteratorAndMirror(t, config)
150215
require.True(t, itr.Valid())
216+
require.Equal(t, 25, len(mirror))
151217
performTest(t, itr, mirror)
152218
})
153219

154220
t.Run("Fast Iterator", func(t *testing.T) {
155221
itr, mirror := setupFastIteratorAndMirror(t, config)
156222
require.True(t, itr.Valid())
223+
require.Equal(t, 25, len(mirror))
224+
performTest(t, itr, mirror)
225+
})
226+
227+
t.Run("Unsaved Fast Iterator", func(t *testing.T) {
228+
itr, mirror := setupUnsavedFastIterator(t, config)
229+
require.True(t, itr.Valid())
230+
require.Equal(t, 25 - 25 / 4 + 1, len(mirror)) // to account for removals
157231
performTest(t, itr, mirror)
158232
})
159233
}
@@ -168,8 +242,6 @@ func TestIterator_Basic_Full_Descending_Success(t *testing.T) {
168242
}
169243

170244
performTest := func(t *testing.T, itr dbm.Iterator, mirror [][]string) {
171-
require.Equal(t, 25, len(mirror))
172-
173245
actualStart, actualEnd := itr.Domain()
174246
require.Equal(t, config.startIterate, actualStart)
175247
require.Equal(t, config.endIterate, actualEnd)
@@ -181,12 +253,21 @@ func TestIterator_Basic_Full_Descending_Success(t *testing.T) {
181253

182254
t.Run("Iterator", func(t *testing.T) {
183255
itr, mirror := setupIteratorAndMirror(t, config)
256+
require.Equal(t, 25, len(mirror))
184257
require.True(t, itr.Valid())
185258
performTest(t, itr, mirror)
186259
})
187260

188261
t.Run("Fast Iterator", func(t *testing.T) {
189262
itr, mirror := setupFastIteratorAndMirror(t, config)
263+
require.Equal(t, 25, len(mirror))
264+
require.True(t, itr.Valid())
265+
performTest(t, itr, mirror)
266+
})
267+
268+
t.Run("Unsaved Fast Iterator", func(t *testing.T) {
269+
itr, mirror := setupUnsavedFastIterator(t, config)
270+
require.Equal(t, 25 - 25 / 4 + 1, len(mirror)) // to account for removals
190271
require.True(t, itr.Valid())
191272
performTest(t, itr, mirror)
192273
})
@@ -238,13 +319,21 @@ func TestIterator_WithDelete_Full_Ascending_Success(t *testing.T) {
238319
require.True(t, itr.Valid())
239320
assertIterator(t, itr, sortedMirror, config.ascending)
240321
})
322+
323+
t.Run("Unsaved Fast Iterator", func(t *testing.T) {
324+
itr := NewUnsavedFastIterator(config.startIterate, config.endIterate, config.ascending, immutableTree.ndb, tree.unsavedFastNodeAdditions, tree.unsavedFastNodeRemovals)
325+
require.True(t, itr.Valid())
326+
assertIterator(t, itr, sortedMirror, config.ascending)
327+
})
241328
}
242329

243330
func setupIteratorAndMirror(t *testing.T, config *iteratorTestConfig) (dbm.Iterator, [][]string) {
244331
tree, err := NewMutableTree(dbm.NewMemDB(), 0)
245332
require.NoError(t, err)
246333

247334
mirror := setupMirrorForIterator(t, config, tree)
335+
_, _, err = tree.SaveVersion()
336+
require.NoError(t, err)
248337

249338
immutableTree, err := tree.GetImmutable(tree.ndb.getLatestVersion())
250339
require.NoError(t, err)
@@ -258,7 +347,63 @@ func setupFastIteratorAndMirror(t *testing.T, config *iteratorTestConfig) (dbm.I
258347
require.NoError(t, err)
259348

260349
mirror := setupMirrorForIterator(t, config, tree)
350+
_, _, err = tree.SaveVersion()
351+
require.NoError(t, err)
261352

262353
itr := NewFastIterator(config.startIterate, config.endIterate, config.ascending, tree.ndb)
263354
return itr, mirror
264355
}
356+
357+
func setupUnsavedFastIterator(t *testing.T, config *iteratorTestConfig) (dbm.Iterator, [][]string) {
358+
tree, err := NewMutableTree(dbm.NewMemDB(), 0)
359+
require.NoError(t, err)
360+
361+
// For unsaved fast iterator, we would like to test the state where
362+
// there are saved fast nodes as well as some unsaved additions and removals.
363+
// So, we split the byte range in half where the first half is saved and the second half is unsaved.
364+
breakpointByte := (config.endByteToSet + config.startByteToSet) / 2
365+
366+
firstHalfConfig := *config
367+
firstHalfConfig.endByteToSet = breakpointByte // exclusive
368+
369+
secondHalfConfig := *config
370+
secondHalfConfig.startByteToSet = breakpointByte
371+
372+
firstHalfMirror := setupMirrorForIterator(t, &firstHalfConfig, tree)
373+
_, _, err = tree.SaveVersion()
374+
require.NoError(t, err)
375+
376+
// No unsaved additions or removals should be present after saving
377+
require.Equal(t, 0, len(tree.unsavedFastNodeAdditions))
378+
require.Equal(t, 0, len(tree.unsavedFastNodeRemovals))
379+
380+
// Ensure that there are unsaved additions and removals present
381+
secondHalfMirror := setupMirrorForIterator(t, &secondHalfConfig, tree)
382+
383+
require.True(t, len(tree.unsavedFastNodeAdditions) >= len(secondHalfMirror))
384+
require.Equal(t, 0, len(tree.unsavedFastNodeRemovals))
385+
386+
// Merge the two halves
387+
var mergedMirror [][]string
388+
if config.ascending {
389+
mergedMirror = append(firstHalfMirror, secondHalfMirror...)
390+
} else {
391+
mergedMirror = append(secondHalfMirror, firstHalfMirror...)
392+
}
393+
394+
if len(mergedMirror) > 0 {
395+
// Remove random keys
396+
for i := 0; i < len(mergedMirror) / 4; i++ {
397+
randIndex := rand.Intn(len(mergedMirror))
398+
keyToRemove := mergedMirror[randIndex][0]
399+
400+
_, removed := tree.Remove([]byte(keyToRemove))
401+
require.True(t, removed)
402+
403+
mergedMirror = append(mergedMirror[:randIndex], mergedMirror[randIndex+1:]...)
404+
}
405+
}
406+
407+
itr := NewUnsavedFastIterator(config.startIterate, config.endIterate, config.ascending, tree.ndb, tree.unsavedFastNodeAdditions, tree.unsavedFastNodeRemovals)
408+
return itr, mergedMirror
409+
}

mutable_tree.go

Lines changed: 5 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -167,79 +167,20 @@ func (t *MutableTree) Iterate(fn func(key []byte, value []byte) bool) (stopped b
167167
return t.ImmutableTree.Iterate(fn)
168168
}
169169

170-
// We need to ensure that we iterate over saved and unsaved state in order.
171-
// The strategy is to sort unsaved nodes, the fast node on disk are already sorted.
172-
// Then, we keep a pointer to both the unsaved and saved nodes, and iterate over them in order efficiently.
173-
unsavedFastNodesToSort := make([]string, 0, len(t.unsavedFastNodeAdditions))
174-
175-
for _, fastNode := range t.unsavedFastNodeAdditions {
176-
unsavedFastNodesToSort = append(unsavedFastNodesToSort, string(fastNode.key))
177-
}
178-
179-
sort.Strings(unsavedFastNodesToSort)
180-
181-
itr := t.ImmutableTree.Iterator(nil, nil, true)
182-
defer itr.Close()
183-
nextUnsavedIdx := 0
184-
for itr.Valid() && nextUnsavedIdx < len(unsavedFastNodesToSort) {
185-
diskKeyStr := string(itr.Key())
186-
187-
if t.unsavedFastNodeRemovals[string(diskKeyStr)] != nil {
188-
// If next fast node from disk is to be removed, skip it.
189-
itr.Next()
190-
continue
191-
}
192-
193-
nextUnsavedKey := unsavedFastNodesToSort[nextUnsavedIdx]
194-
nextUnsavedNode := t.unsavedFastNodeAdditions[nextUnsavedKey]
195-
196-
if diskKeyStr >= nextUnsavedKey {
197-
// Unsaved node is next
198-
199-
if diskKeyStr == nextUnsavedKey {
200-
// Unsaved update prevails over saved copy so we skip the copy from disk
201-
itr.Next()
202-
}
203-
204-
if fn(nextUnsavedNode.key, nextUnsavedNode.value) {
205-
return true
206-
}
207-
208-
nextUnsavedIdx++
209-
} else {
210-
// Disk node is next
211-
if fn(itr.Key(), itr.Value()) {
212-
return true
213-
}
214-
215-
itr.Next()
216-
}
217-
}
218-
219-
// if only nodes on disk are left, we can just iterate
220-
for itr.Valid() {
170+
itr := NewUnsavedFastIterator(nil, nil, true, t.ndb, t.unsavedFastNodeAdditions, t.unsavedFastNodeRemovals)
171+
for ; itr.Valid(); itr.Next() {
221172
if fn(itr.Key(), itr.Value()) {
222173
return true
223174
}
224-
itr.Next()
225-
}
226-
227-
// if only unsaved nodes are left, we can just iterate
228-
for ; nextUnsavedIdx < len(unsavedFastNodesToSort); nextUnsavedIdx++ {
229-
nextUnsavedKey := unsavedFastNodesToSort[nextUnsavedIdx]
230-
nextUnsavedNode := t.unsavedFastNodeAdditions[nextUnsavedKey]
231-
232-
if fn(nextUnsavedNode.key, nextUnsavedNode.value) {
233-
return true
234-
}
235175
}
236176

237177
return false
238178
}
239179

240-
// Iterator is not supported and is therefore invalid for MutableTree. Get an ImmutableTree instead for a valid iterator.
180+
// Iterator returns an iterator over the mutable tree.
181+
// CONTRACT: no updates are made to the tree while an iterator is active.
241182
func (t *MutableTree) Iterator(start, end []byte, ascending bool) dbm.Iterator {
242-
return NewIterator(start, end, ascending, nil) // this is an invalid iterator
183+
return NewUnsavedFastIterator(start, end, ascending, t.ndb, t.unsavedFastNodeAdditions, t.unsavedFastNodeRemovals)
243184
}
244185

245186
func (tree *MutableTree) set(key []byte, value []byte) (orphans []*Node, updated bool) {

testutils_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,12 +271,10 @@ func setupMirrorForIterator(t *testing.T, config *iteratorTestConfig, tree *Muta
271271
curByte--
272272
}
273273
}
274-
_, _, err := tree.SaveVersion()
275-
require.NoError(t, err)
276274
return mirror
277275
}
278276

279-
// assertIterator confirms that the iterato returns the expected values desribed by mirror in the same order.
277+
// assertIterator confirms that the iterator returns the expected values desribed by mirror in the same order.
280278
// mirror is a slice containing slices of the form [key, value]. In other words, key at index 0 and value at index 1.
281279
// mirror should be sorted in either ascending or descending order depending on the value of ascending parameter.
282280
func assertIterator(t *testing.T, itr dbm.Iterator, mirror [][]string, ascending bool) {

0 commit comments

Comments
 (0)