Skip to content

Commit bdaab6d

Browse files
committed
internal/base: add doc comment discussing TrySeekUsingNext
1 parent ccb837b commit bdaab6d

File tree

2 files changed

+102
-11
lines changed

2 files changed

+102
-11
lines changed

internal/base/doc.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2024 The LevelDB-Go and Pebble Authors. All rights reserved. Use
2+
// of this source code is governed by a BSD-style license that can be found in
3+
// the LICENSE file.
4+
5+
// Package base defines fundamental types used across Pebble, including keys,
6+
// iterators, etc.
7+
//
8+
// # Iterators
9+
//
10+
// The [InternalIterator] interface defines the iterator interface implemented
11+
// by all iterators over point keys. Internal iterators are composed to form an
12+
// "iterator stack," resulting in a single internal iterator (see mergingIter in
13+
// the pebble package) that yields a merged view of the LSM.
14+
//
15+
// The SeekGE and SeekPrefixGE positioning methods take a set of flags
16+
// [SeekGEFlags] allowing the caller to provide additional context to iterator
17+
// implementations. The TrySeekUsingNext flag is set when the caller has
18+
// knowledge that no action has been performed to move this iterator beyond the
19+
// first key that would be found if this iterator were to honestly do the
20+
// intended seek. This allows a class of optimizations where an internal
21+
// iterator may avoid a full naive repositioning if the iterator is already
22+
// at a proximate position. This also means every caller (including intermediary
23+
// internal iterators within the iterator stack) must preserve this
24+
// relationship.
25+
//
26+
// For example, if a range deletion deletes the remainder of a prefix, the
27+
// merging iterator may be able to elide a SeekPrefixGE on level iterators
28+
// beneath the range deletion. However in doing so, a TrySeekUsingNext flag
29+
// passed by the merging iterator's client no longer transitively holds for
30+
// subsequent seeks of child level iterators in all cases. The merging iterator
31+
// assumes responsibility for ensuring that SeekPrefixGE is propagated to its
32+
// consitutent iterators only when valid.
33+
//
34+
// Description of TrySeekUsingNext mechanics across the iterator stack:
35+
//
36+
// As the top-level entry point of user seeks, the [pebble.Iterator] is
37+
// responsible for detecting when consecutive seeks move monotonically forward.
38+
// It saves seek keys and compares consecutive seek keys to decide whether to
39+
// propagate the TrySeekUsingNext flag to its [InternalIterator].
40+
//
41+
// The [pebble.Iterator] also has its own SeekPrefixGE optimization: Above the
42+
// [InternalIterator] interface, the [pebble.Iterator]'s SeekGE method detects
43+
// consecutive seeks to monotonically increasing keys and examines the current
44+
// key. If the iterator is already positioned appropriately (at a key ≥ the seek
45+
// key), it elides the entire seek of the internal iterator.
46+
//
47+
// The pebble mergingIter does not perform any TrySeekUsingNext optimization
48+
// itself, but it must preserve the TrySeekUsingNext contract in its calls to
49+
// its child iterators because it passes the TrySeekUsingNext flag as-is to its
50+
// child iterators. It can do this because it always translates calls to its
51+
// SeekGE and SeekPrefixGE methods as equivalent calls to every child iterator.
52+
// However there are a few subtleties:
53+
//
54+
// - In some cases the calls made to child iterators may only be equivalent
55+
// within the context of the iterator's visible sequence number. For example,
56+
// if a range deletion tombstone is present on a level, seek keys propagated
57+
// to lower-levelled child iterators may be adjusted without violating the
58+
// transitivity of the TrySeekUsingNext flag and its invariants so long as
59+
// the mergingIter is always reading state at the same visible sequence
60+
// number.
61+
// - The mergingIter takes care to avoid ever advancing a child iterator that's
62+
// already positioned beyond the current iteration prefix.
63+
// - When propagating TrySeekUsingNext to its child iterators, the mergingIter
64+
// must propagate it to all child iterators or none. This is required because
65+
// of the mergingIter's handling of range deletions. Unequal application of
66+
// TrySeekUsingNext may cause range deletions that have already been skipped
67+
// over in a level to go unseen, despite being relevant to other levels that
68+
// do not use TrySeekUsingNext.
69+
//
70+
// The pebble levelIter makes use of the TrySeekUsingNext flag to avoid a naive
71+
// seek among a level's file metadatas. When TrySeekUsingNext is passed by the
72+
// caller, the relevant key must fall within the current file or later.
73+
//
74+
// In-memory iterators arenaskl.Iterator and batchskl.Iterator make use of the
75+
// TrySeekUsingNext flag, attempting a fixed number of Nexts before falling back
76+
// to performing a seek using skiplist structures.
77+
//
78+
// The sstable iterators use the TrySeekUsingNext flag to avoid naive seeks
79+
// through a table's index structures:
80+
// - If an iterator is already exhausted, either because there are no
81+
// subsequent point keys or because the upper bound has been reached, the
82+
// iterator uses TrySeekUsingNext to avoid any repositioning at all.
83+
// - Otherwise, a TrySeekUsingNext flag causes the sstable Iterator to Next
84+
// forward a capped number of times, stopping as soon as a key ≥ the seek key
85+
// is discovered.
86+
package base

internal/base/iterator.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -227,17 +227,22 @@ const (
227227
// SeekGEFlagsNone is the default value of SeekGEFlags, with all flags disabled.
228228
const SeekGEFlagsNone = SeekGEFlags(0)
229229

230-
// TrySeekUsingNext indicates whether a performance optimization was enabled
231-
// by a caller, indicating the caller has not done any action to move this
232-
// iterator beyond the first key that would be found if this iterator were to
233-
// honestly do the intended seek. For example, say the caller did a
234-
// SeekGE(k1...), followed by SeekGE(k2...) where k1 <= k2, without any
235-
// intermediate positioning calls. The caller can safely specify true for this
236-
// parameter in the second call. As another example, say the caller did do one
237-
// call to Next between the two Seek calls, and k1 < k2. Again, the caller can
238-
// safely specify a true value for this parameter. Note that a false value is
239-
// always safe. The callee is free to ignore the true value if its
240-
// implementation does not permit this optimization.
230+
// TODO(jackson): Rename TrySeekUsingNext to MonotonicallyForward or something
231+
// similar that avoids prescribing the implementation of the optimization but
232+
// instead focuses on the contract expected of the caller.
233+
234+
// TrySeekUsingNext is set when the caller has knowledge that no action has been
235+
// performed to move this iterator beyond the first key that would be found if
236+
// this iterator were to honestly do the intended seek. This enables a class of
237+
// performance optimizations within various internal iterator implementations.
238+
// For example, say the caller did a SeekGE(k1...), followed by SeekGE(k2...)
239+
// where k1 <= k2, without any intermediate positioning calls. The caller can
240+
// safely specify true for this parameter in the second call. As another
241+
// example, say the caller did do one call to Next between the two Seek calls,
242+
// and k1 < k2. Again, the caller can safely specify a true value for this
243+
// parameter. Note that a false value is always safe. If true, the callee should
244+
// not return a key less than the current iterator position even if a naive seek
245+
// would land there.
241246
//
242247
// We make the caller do this determination since a string comparison of k1, k2
243248
// is not necessarily cheap, and there may be many iterators in the iterator

0 commit comments

Comments
 (0)