Step 1 of 3 · Reading · ~3 min
SSTable Iterators: Seek & Next
Range Queries & Iterators
SSTable Iterators: Seek & Next
A single SSTable is a sorted, immutable file of key-value pairs. To support range scans and to be a building block for the multi-source merge iterator you'll write next, an SSTable needs to expose an iterator interface with two primitive operations: Seek(key) and Next(). This lesson is about implementing those primitives correctly and efficiently against sorted, static data.
The interface
Seek(k)— position an internal cursor at the first entry whose key is>= k. This is how a range scan[start, end]begins: seek tostart, then repeatedly callNextuntil you passend.Next()— advance the cursor by one entry and return the new current entry, or signal "exhausted" if there are no more entries.
Because the SSTable is sorted and immutable, Seek doesn't need to scan linearly — it can binary search the key array, which is exactly why SSTables store an in-memory (or block-level) sparse index of keys: it turns Seek into O(log n) instead of O(n).
bisect_left is the right primitive here: it returns the insertion point that keeps the list sorted and, critically, lands on the first occurrence of target if it's present — which matches the "first entry >= k" semantics Seek needs.
Cursor state and edge cases
A few edge cases separate a correct iterator from a subtly broken one:
- Seeking past the end. If
targetis greater than every key in the table,bisect_leftreturnslen(data)— the cursor is now "at end," and bothSeekand any subsequentNextmust report exhaustion rather than indexing out of bounds. Nextbefore anySeek. An iterator with no defined starting position shouldn't silently assume "start from the beginning" — that's an easy source of bugs where a caller forgets to seek first and gets data from the wrong logical position. Treat the cursor as invalid until explicitly positioned.- Repeated
Nextafter exhaustion. Once the cursor is past the last entry, furtherNextcalls must keep reporting "end" rather than wrapping around or throwing — callers (like the merge iterator later) will callNextin a loop and rely on this being stable. Scan(start, end)is just sugar over Seek + Next. A full range scan is: binary-search tostart, then walk forward withNext-like iteration until the key exceedsend. Implementing it directly (rather than literally calling yourSeek/Nextfunctions) is fine for the exercise, but notice it's the same logic — this is intentional, because in the real system,Scanis built on top ofSeek/Next.
Why this shape, and where it's going
Every SSTable in your LSM tree will expose exactly this iterator interface, and nothing above it — the merge logic in the next lesson, compaction's k-way merge, and any range query all consume SSTables purely through Seek/Next, never by reading the whole file into memory. That uniformity is what lets you plug an arbitrary number of SSTables (across levels, across compaction inputs) into the same merge algorithm without special-casing "is this the memtable or a file on disk." Get the single-source iterator right here, and the multi-source version is just a heap of these.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…