Repository: petermattis/pebble
Pull request #154 (2019-05-31 14:15:35)

Fix Iterator.{Next,Prev} boundary cases
When the iterator is invalid, a call to `{Next,Prev}` needs to consider
the lower and upper bounds. Previously it was ignoring them allowing the
iterator to return keys outside of the bounds.

petermattis: 

<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.png" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/petermattis/pebble/154)
<!-- Reviewable:end -->


Patch:
diff --git a/batch.go b/batch.go
index e38eb3a..0988639 100644
--- a/batch.go
+++ b/batch.go
@@ -937,8 +937,6 @@ func (b *flushableBatch) newIter(o *IterOptions) internalIterator {
 		offsets: b.offsets,
 		cmp:     b.cmp,
 		index:   -1,
-		lower:   o.GetLowerBound(),
-		upper:   o.GetUpperBound(),
 	}
 }
 
@@ -999,8 +997,6 @@ type flushableBatchIter struct {
 	index   int
 	key     InternalKey
 	err     error
-	lower   []byte
-	upper   []byte
 }
 
 // flushableBatchIter implements the internalIterator interface.
@@ -1015,10 +1011,6 @@ func (i *flushableBatchIter) SeekGE(key []byte) (*InternalKey, []byte) {
 		return nil, nil
 	}
 	i.key = i.getKey(i.index)
-	if i.upper != nil && i.cmp(i.key.UserKey, i.upper) >= 0 {
-		i.index = len(i.offsets)
-		return nil, nil
-	}
 	return &i.key, i.Value()
 }
 
@@ -1036,10 +1028,6 @@ func (i *flushableBatchIter) SeekLT(key []byte) (*InternalKey, []byte) {
 		return nil, nil
 	}
 	i.key = i.getKey(i.index)
-	if i.lower != nil && i.cmp(i.key.UserKey, i.lower) < 0 {
-		i.index = -1
-		return nil, nil
-	}
 	return &i.key, i.Value()
 }
 
@@ -1049,10 +1037,6 @@ func (i *flushableBatchIter) First() (*InternalKey, []byte) {
 	}
 	i.index = 0
 	i.key = i.getKey(i.index)
-	if i.upper != nil && i.cmp(i.key.UserKey, i.upper) >= 0 {
-		i.index = len(i.offsets)
-		return nil, nil
-	}
 	return &i.key, i.Value()
 }
 
@@ -1062,10 +1046,6 @@ func (i *flushableBatchIter) Last() (*InternalKey, []byte) {
 	}
 	i.index = len(i.offsets) - 1
 	i.key = i.getKey(i.index)
-	if i.lower != nil && i.cmp(i.key.UserKey, i.lower) < 0 {
-		i.index = -1
-		return nil, nil
-	}
 	return &i.key, i.Value()
 }
 
@@ -1078,10 +1058,6 @@ func (i *flushableBatchIter) Next() (*InternalKey, []byte) {
 		return nil, nil
 	}
 	i.key = i.getKey(i.index)
-	if i.upper != nil && i.cmp(i.key.UserKey, i.upper) >= 0 {
-		i.index = len(i.offsets)
-		return nil, nil
-	}
 	return &i.key, i.Value()
 }
 
@@ -1094,10 +1070,6 @@ func (i *flushableBatchIter) Prev() (*InternalKey, []byte) {
 		return nil, nil
 	}
 	i.key = i.getKey(i.index)
-	if i.lower != nil && i.cmp(i.key.UserKey, i.lower) < 0 {
-		i.index = -1
-		return nil, nil
-	}
 	return &i.key, i.Value()
 }
 
@@ -1134,6 +1106,6 @@ func (i *flushableBatchIter) Close() error {
 }
 
 func (i *flushableBatchIter) SetBounds(lower, upper []byte) {
-	i.lower = lower
-	i.upper = upper
+	// This should not be called as bounds are not used for this iterator.
+	panic("TODO(ryan): pebble: SetBounds unimplemented")
 }
diff --git a/compaction_picker.go b/compaction_picker.go
index 2d75ea0..f313fe0 100644
--- a/compaction_picker.go
+++ b/compaction_picker.go
@@ -49,14 +49,14 @@ func (p *compactionPicker) compactionNeeded() bool {
 func (p *compactionPicker) initLevelMaxBytes(v *version, opts *Options) {
 	// Determine the first non-empty level and the maximum size of any level.
 	firstNonEmptyLevel := -1
-	var bottomLevelSize int64
+	var maxLevelSize int64
 	for level := 1; level < numLevels; level++ {
 		levelSize := int64(totalSize(v.files[level]))
-		if levelSize > 0 {
-			if firstNonEmptyLevel == -1 {
-				firstNonEmptyLevel = level
-			}
-			bottomLevelSize = levelSize
+		if levelSize > 0 && firstNonEmptyLevel == -1 {
+			firstNonEmptyLevel = level
+		}
+		if maxLevelSize < levelSize {
+			maxLevelSize = levelSize
 		}
 	}
 
@@ -67,7 +67,7 @@ func (p *compactionPicker) initLevelMaxBytes(v *version, opts *Options) {
 		p.levelMaxBytes[level] = math.MaxInt64
 	}
 
-	if bottomLevelSize == 0 {
+	if maxLevelSize == 0 {
 		// No levels for L1 and up contain any data. Target L0 compactions for the
 		// last level.
 		p.baseLevel = numLevels - 1
@@ -79,15 +79,17 @@ func (p *compactionPicker) initLevelMaxBytes(v *version, opts *Options) {
 	baseBytesMax := opts.LBaseMaxBytes
 	baseBytesMin := int64(float64(baseBytesMax) / levelMultiplier)
 
-	curLevelSize := bottomLevelSize
+	curLevelSize := maxLevelSize
 	for level := numLevels - 2; level >= firstNonEmptyLevel; level-- {
 		curLevelSize = int64(float64(curLevelSize) / levelMultiplier)
 	}
 
+	var baseLevelSize int64
 	if curLevelSize <= baseBytesMin {
-		// If we make target size of last level to be bottomLevelSize, target size of
+		// If we make target size of last level to be maxLevelSize, target size of
 		// the first non-empty level would be smaller than baseBytesMin. We set it
 		// be baseBytesMin.
+		baseLevelSize = baseBytesMin + 1
 		p.baseLevel = firstNonEmptyLevel
 	} else {
 		// Compute base level (where L0 data is compacted to).
@@ -96,29 +98,23 @@ func (p *compactionPicker) initLevelMaxBytes(v *version, opts *Options) {
 			p.baseLevel--
 			curLevelSize = int64(float64(curLevelSize) / levelMultiplier)
 		}
+		if curLevelSize > baseBytesMax {
+			baseLevelSize = baseBytesMax
+		} else {
+			baseLevelSize = curLevelSize
+		}
 	}
 
-	var smoothedLevelMultiplier float64
-	if p.baseLevel < numLevels-1 {
-		smoothedLevelMultiplier = math.Pow(
-			float64(bottomLevelSize)/float64(baseBytesMax),
-			1.0/float64(numLevels-p.baseLevel-1))
-	} else {
-		smoothedLevelMultiplier = 1.0
-	}
-
-	levelSize := float64(baseBytesMax)
+	levelSize := baseLevelSize
 	for level := p.baseLevel; level < numLevels; level++ {
-		if level > p.baseLevel && levelSize > 0 {
-			levelSize *= smoothedLevelMultiplier
+		if level > p.baseLevel {
+			if levelSize > 0 && float64(math.MaxInt64/levelSize) >= levelMultiplier {
+				levelSize = int64(float64(levelSize) * levelMultiplier)
+			}
 		}
-		// Round the result since test cases use small target level sizes, which
-		// can be impacted by floating-point imprecision + integer truncation.
-		roundedLevelSize := math.Round(levelSize)
-		if roundedLevelSize > float64(math.MaxInt64) {
-			p.levelMaxBytes[level] = math.MaxInt64
-		} else {
-			p.levelMaxBytes[level] = int64(roundedLevelSize)
+		p.levelMaxBytes[level] = levelSize
+		if p.levelMaxBytes[level] < baseBytesMax {
+			p.levelMaxBytes[level] = baseBytesMax
 		}
 	}
 }
diff --git a/db.go b/db.go
index 6f1e522..deafb03 100644
--- a/db.go
+++ b/db.go
@@ -266,7 +266,7 @@ func (d *DB) getInternal(key []byte, b *Batch, s *Snapshot) ([]byte, error) {
 	i.readState = readState
 
 	defer i.Close()
-	if !i.Next() {
+	if !i.First() {
 		err := i.Error()
 		if err != nil {
 			return nil, err
diff --git a/iterator.go b/iterator.go
index c0947f4..22ea9e8 100644
--- a/iterator.go
+++ b/iterator.go
@@ -95,21 +95,20 @@ func (i *Iterator) findNextEntry() bool {
 }
 
 func (i *Iterator) nextUserKey() {
-	if i.iterKey != nil {
-		done := i.iterKey.SeqNum() == 0
-		if !i.valid {
-			i.keyBuf = append(i.keyBuf[:0], i.iterKey.UserKey...)
-			i.key = i.keyBuf
-		}
-		for {
-			i.iterKey, i.iterValue = i.iter.Next()
-			if done || i.iterKey == nil || !i.equal(i.key, i.iterKey.UserKey) {
-				break
-			}
-			done = i.iterKey.SeqNum() == 0
+	if i.iterKey == nil {
+		return
+	}
+	done := i.iterKey.SeqNum() == 0
+	if !i.valid {
+		i.keyBuf = append(i.keyBuf[:0], i.iterKey.UserKey...)
+		i.key = i.keyBuf
+	}
+	for {
+		i.iterKey, i.iterValue = i.iter.Next()
+		if done || i.iterKey == nil || !i.equal(i.key, i.iterKey.UserKey) {
+			break
 		}
-	} else {
-		i.iterKey, i.iterValue = i.iter.First()
+		done = i.iterKey.SeqNum() == 0
 	}
 }
 
@@ -189,21 +188,20 @@ func (i *Iterator) findPrevEntry() bool {
 }
 
 func (i *Iterator) prevUserKey() {
-	if i.iterKey != nil {
-		if !i.valid {
-			// If we're going to compare against the prev key, we need to save the
-			// current key.
-			i.keyBuf = append(i.keyBuf[:0], i.iterKey.UserKey...)
-			i.key = i.keyBuf
-		}
-		for {
-			i.iterKey, i.iterValue = i.iter.Prev()
-			if i.iterKey == nil || !i.equal(i.key, i.iterKey.UserKey) {
-				break
-			}
+	if i.iterKey == nil {
+		return
+	}
+	if !i.valid {
+		// If we're going to compare against the prev key, we need to save the
+		// current key.
+		i.keyBuf = append(i.keyBuf[:0], i.iterKey.UserKey...)
+		i.key = i.keyBuf
+	}
+	for {
+		i.iterKey, i.iterValue = i.iter.Prev()
+		if i.iterKey == nil || !i.equal(i.key, i.iterKey.UserKey) {
+			break
 		}
-	} else {
-		i.iterKey, i.iterValue = i.iter.Last()
 	}
 }
 
@@ -374,7 +372,17 @@ func (i *Iterator) Next() bool {
 		// TODO(peter): This is subtle. Perhaps we should provide a special version
 		// of nextUserKey which advances until a larger user key is found.
 		i.valid = false
-		i.nextUserKey()
+		if i.iterKey == nil {
+			// We're positioned before the first key. Need to reposition to point to
+			// the first key.
+			if lowerBound := i.opts.GetLowerBound(); lowerBound != nil {
+				i.iterKey, i.iterValue = i.iter.SeekGE(lowerBound)
+			} else {
+				i.iterKey, i.iterValue = i.iter.First()
+			}
+		} else {
+			i.nextUserKey()
+		}
 		i.nextUserKey()
 	case iterPosNext:
 	}
@@ -399,7 +407,17 @@ func (i *Iterator) Prev() bool {
 		// TODO(peter): This is subtle. Perhaps we should provide a special version
 		// of prevUserKey which advances until a smaller user key is found.
 		i.valid = false
-		i.prevUserKey()
+		if i.iterKey == nil {
+			// We're positioned after the last key. Need to reposition to point to
+			// the last key.
+			if upperBound := i.opts.GetUpperBound(); upperBound != nil {
+				i.iterKey, i.iterValue = i.iter.SeekLT(upperBound)
+			} else {
+				i.iterKey, i.iterValue = i.iter.Last()
+			}
+		} else {
+			i.prevUserKey()
+		}
 		i.prevUserKey()
 	case iterPosPrev:
 	}


Test patch:
diff --git a/batch_test.go b/batch_test.go
index b7243d0..cf0f1a4 100644
--- a/batch_test.go
+++ b/batch_test.go
@@ -285,7 +285,7 @@ func TestFlushableBatchIter(t *testing.T) {
 	})
 }
 
-func TestFlushableBatch(t *testing.T) {
+func TestFlushableBatchSeqNum(t *testing.T) {
 	var b *flushableBatch
 	datadriven.RunTest(t, "testdata/flushable_batch", func(d *datadriven.TestData) string {
 		switch d.Cmd {
@@ -307,26 +307,6 @@ func TestFlushableBatch(t *testing.T) {
 			b = newFlushableBatch(batch, DefaultComparer)
 			return ""
 
-		case "iter":
-			var opts IterOptions
-			for _, arg := range d.CmdArgs {
-				if len(arg.Vals) != 1 {
-					return fmt.Sprintf("%s: %s=<value>", d.Cmd, arg.Key)
-				}
-				switch arg.Key {
-				case "lower":
-					opts.LowerBound = []byte(arg.Vals[0])
-				case "upper":
-					opts.UpperBound = []byte(arg.Vals[0])
-				default:
-					return fmt.Sprintf("%s: unknown arg: %s", d.Cmd, arg.Key)
-				}
-			}
-
-			iter := b.newIter(&opts)
-			defer iter.Close()
-			return runInternalIterCmd(d, iter)
-
 		case "dump":
 			if len(d.CmdArgs) != 1 || len(d.CmdArgs[0].Vals) != 1 || d.CmdArgs[0].Key != "seq" {
 				return fmt.Sprintf("dump seq=<value>\n")
diff --git a/get_iter_test.go b/get_iter_test.go
index 0666511..91c437d 100644
--- a/get_iter_test.go
+++ b/get_iter_test.go
@@ -522,7 +522,7 @@ func TestGetIter(t *testing.T) {
 			i.iter = get
 
 			defer i.Close()
-			if !i.Next() {
+			if !i.First() {
 				err := i.Error()
 				if err != nil {
 					return nil, err
diff --git a/testdata/compaction_picker_level_max_bytes b/testdata/compaction_picker_level_max_bytes
index d610e5c..a6b74f4 100644
--- a/testdata/compaction_picker_level_max_bytes
+++ b/testdata/compaction_picker_level_max_bytes
@@ -11,13 +11,13 @@ init 1
 6: 2
 ----
 5: 1
-6: 2
+6: 1
 
 init 1
 6: 2
 ----
 5: 1
-6: 2
+6: 1
 
 init 1
 3: 1
@@ -26,9 +26,9 @@ init 1
 6: 1
 ----
 3: 1
-4: 1
-5: 1
-6: 1
+4: 10
+5: 100
+6: 1000
 
 init 1
 1: 1
@@ -39,11 +39,11 @@ init 1
 6: 1
 ----
 1: 1
-2: 1
-3: 1
-4: 1
-5: 1
-6: 1
+2: 10
+3: 100
+4: 1000
+5: 10000
+6: 100000
 
 init 1
 1: 1
@@ -100,45 +100,25 @@ init 1
 5: 10000
 6: 100000
 
-# Smoothing multiplier is
-# `(size(Lbottom)/size(Lbase))^(1/(Lbottom-Lbase)) = (1000000/1)^(1/(6-1)) = 1000000^(1/5)`
-#
-# size(L1) = size(Lbase) = 1
-# size(L2) = size(L1) * 1000000^(1/5) ~= 16
-# size(L3) = size(L2) * 1000000^(1/5) ~= 251
-# size(L4) = size(L3) * 1000000^(1/5) ~= 3981
-# size(L5) = size(L4) * 1000000^(1/5) ~= 63096
-# size(L6) = size(L5) * 1000000^(1/5) ~= 1000000
-
 init 1
 6: 1000000
 ----
 1: 1
-2: 16
-3: 251
-4: 3981
-5: 63096
-6: 1000000
-
-# Smoothing multiplier is
-# `(size(Lbottom)/size(Lbase))^(Lbottom-Lbase) = (64000000/64)^(1/(6-1)) = 1000000^(1/5)`
-#
-# size(L1) = size(Lbase) = 64
-# size(L2) = size(L1) * 1000000^(1/5) ~= 1014
-# size(L3) = size(L2) * 1000000^(1/5) ~= 16076
-# size(L4) = size(L3) * 1000000^(1/5) ~= 254789
-# size(L5) = size(L4) * 1000000^(1/5) ~= 4038127
-# size(L6) = size(L5) * 1000000^(1/5) ~= 64000000
+2: 10
+3: 100
+4: 1000
+5: 10000
+6: 100000
 
 init 64
 6: 64000000
 ----
 1: 64
-2: 1014
-3: 16076
-4: 254789
-5: 4038127
-6: 64000000
+2: 640
+3: 6400
+4: 64000
+5: 640000
+6: 6400000
 
 init 1
 0: 4
diff --git a/testdata/compaction_picker_target_level b/testdata/compaction_picker_target_level
index 3d22bd0..ab0df71 100644
--- a/testdata/compaction_picker_target_level
+++ b/testdata/compaction_picker_target_level
@@ -24,18 +24,11 @@ pick 1
 ----
 5: 2.0
 
-# Smoothing multiplier is
-# `(size(Lbottom)/size(Lbase))^(Lbottom-Lbase) = (30/1)^(1/(6-4)) = 30^(1/2)`
-#
-# size(L4) = size(Lbase) = 1
-# size(L5) = size(L4) * 30^(1/2) ~= 5
-# size(L6) = size(L5) * 30^(1/2) = 30
-
 pick 1
 5: 2
 6: 30
 ----
-5: 0.4
+5: 2.0
 
 pick 1
 4: 2
diff --git a/testdata/event_listener b/testdata/event_listener
index 28f3577..cb768a3 100644
--- a/testdata/event_listener
+++ b/testdata/event_listener
@@ -101,5 +101,5 @@ level__files____size___score______in__ingest____move____read___write___w-amp
     3      0     0 B    0.00     0 B     0 B     0 B     0 B     0 B     0.0
     4      0     0 B    0.00     0 B     0 B     0 B     0 B     0 B     0.0
     5      1   959 B    0.00     0 B   959 B     0 B     0 B     0 B     0.0
-    6      1   959 B    1.00   1.9 K     0 B     0 B   1.9 K   959 B     0.5
+    6      1   959 B    0.00   1.9 K     0 B     0 B   1.9 K   959 B     0.5
 total      2   1.9 K    0.00   1.0 K   959 B     0 B   1.9 K   3.8 K     3.8
diff --git a/testdata/flushable_batch b/testdata/flushable_batch
index 2f84099..315750c 100644
--- a/testdata/flushable_batch
+++ b/testdata/flushable_batch
@@ -36,107 +36,3 @@ dump seq=1000
 a#1002,1:1
 b#1001,1:1
 c#1000,1:1
-
-iter
-first
-next
-next
-next
-----
-a:1
-b:1
-c:1
-.
-
-iter
-last
-prev
-prev
-prev
-----
-c:1
-b:1
-a:1
-.
-
-iter lower=b
-seek-ge b
-prev
-----
-b:1
-.
-
-iter lower=c
-seek-lt b
-----
-.
-
-iter lower=e
-last
-----
-.
-
-iter upper=1
-first
-----
-.
-
-iter upper=b
-first
-next
-----
-a:1
-.
-
-iter upper=b
-seek-ge b
-----
-.
-
-iter
-set-bounds lower=b
-seek-ge b
-prev
-----
-b:1
-.
-
-iter
-set-bounds lower=c
-seek-lt b
-----
-.
-
-iter
-set-bounds lower=e
-last
-----
-.
-
-iter
-set-bounds upper=1
-first
-----
-.
-
-iter
-set-bounds upper=b
-first
-next
-----
-a:1
-.
-
-iter
-set-bounds upper=b
-seek-ge b
-----
-.
-
-iter
-first
-set-bounds upper=b
-seek-ge b
-----
-a:1
-.
diff --git a/testdata/iterator b/testdata/iterator
index f7b0ff6..3b13ea6 100644
--- a/testdata/iterator
+++ b/testdata/iterator
@@ -9,7 +9,7 @@ prev
 ----
 a:b
 .
-a:b
+.
 
 iter seq=2
 seek-ge b
@@ -34,7 +34,7 @@ prev
 ----
 a:b
 .
-a:b
+.
 
 iter seq=3
 seek-ge a
@@ -43,7 +43,7 @@ prev
 ----
 a:c
 .
-a:c
+.
 
 iter seq=2
 seek-prefix-ge a
@@ -52,7 +52,7 @@ prev
 ----
 a:b
 .
-a:b
+.
 
 iter seq=3
 seek-prefix-ge a
@@ -61,7 +61,7 @@ prev
 ----
 a:c
 .
-a:c
+.
 
 
 define
@@ -93,7 +93,7 @@ next
 ----
 a:b
 .
-a:b
+.
 
 iter seq=3
 seek-prefix-ge a
@@ -185,7 +185,7 @@ next
 ----
 a:a
 .
-a:a
+.
 
 iter seq=4
 seek-lt c
@@ -196,7 +196,7 @@ next
 b:b
 a:a
 .
-a:a
+.
 
 
 iter seq=4
@@ -210,7 +210,7 @@ c:c
 b:b
 a:a
 .
-a:a
+.
 
 iter seq=4
 seek-prefix-ge a
@@ -253,7 +253,7 @@ prev
 ----
 a:b
 .
-a:b
+.
 
 iter seq=2
 seek-ge b
@@ -272,7 +272,7 @@ next
 ----
 a:b
 .
-a:b
+.
 
 iter seq=2
 seek-lt c
@@ -281,7 +281,7 @@ next
 ----
 a:b
 .
-a:b
+.
 
 iter seq=2
 seek-prefix-ge a
@@ -290,7 +290,7 @@ prev
 ----
 a:b
 .
-a:b
+.
 
 iter seq=2
 seek-prefix-ge b
@@ -488,7 +488,7 @@ prev
 a:bcd
 b:ab
 .
-b:ab
+.
 
 iter seq=3
 seek-ge a
@@ -513,7 +513,7 @@ next
 b:ab
 a:bcd
 .
-a:bcd
+.
 
 iter seq=3
 seek-lt c
@@ -995,6 +995,46 @@ last
 .
 a:a
 
+iter seq=2
+last
+next
+set-bounds upper=c
+prev
+----
+d:d
+.
+.
+.
+
+iter seq=2
+first
+prev
+set-bounds lower=b
+next
+----
+a:a
+.
+.
+.
+
+iter seq=2
+set-bounds lower=b
+seek-lt c
+next
+----
+.
+b:b
+c:c
+
+iter seq=2
+set-bounds upper=d
+seek-ge c
+prev
+----
+.
+c:c
+b:b
+
 define
 a.SET.1:a
 aa.SET.1:aa
diff --git a/testdata/snapshot b/testdata/snapshot
index aa93e38..42a9661 100644
--- a/testdata/snapshot
+++ b/testdata/snapshot
@@ -14,20 +14,18 @@ prev
 ----
 a:1
 .
-a:1
+.
 
 iter snapshot=2
 first
 next
 next
 prev
-prev
 ----
 a:1
 b:2
 .
-b:2
-a:1
+.
 
 iter snapshot=3
 first
@@ -35,16 +33,12 @@ next
 next
 next
 prev
-prev
-prev
 ----
 a:1
 b:2
 c:3
 .
-c:3
-b:2
-a:1
+.
 
 define
 set a 1
@@ -62,7 +56,7 @@ prev
 ----
 a:1
 .
-a:1
+.
 
 iter snapshot=2
 first
@@ -71,7 +65,7 @@ prev
 ----
 a:2
 .
-a:2
+.
 
 iter snapshot=3
 first
@@ -80,7 +74,7 @@ prev
 ----
 a:3
 .
-a:3
+.
 
 define
 set a 1
@@ -99,7 +93,7 @@ prev
 ----
 a:1
 .
-a:1
+.
 
 iter snapshot=2
 first
@@ -108,7 +102,7 @@ prev
 ----
 a:2
 .
-a:2
+.
 
 iter snapshot=3
 first
@@ -117,7 +111,7 @@ prev
 ----
 a:3
 .
-a:3
+.
 
 define
 merge a 1
@@ -136,7 +130,7 @@ prev
 ----
 a:1
 .
-a:1
+.
 
 iter snapshot=2
 first
@@ -145,7 +139,7 @@ prev
 ----
 a:21
 .
-a:21
+.
 
 iter snapshot=3
 first
@@ -154,4 +148,4 @@ prev
 ----
 a:321
 .
-a:321
+.