Expand description
The run of values behind a flat vector, and the seam the buffer manager arrives through.
spec/engine/03-data-plane.md section 3.8. Today every vector owns its payload and every scan
allocates, which is the right place to start and is not where this ends. At layer three the scan
reads a page out of the buffer manager and the vector wants to point into that page rather than
copy out of it, and the copy it avoids is the largest single copy in the system, because it is
every byte of every column every query reads.
The type that supports both is one enum holding either an owned run or a borrowed one, and the part that has to be decided correctly the first time is how the borrow is expressed, because that shows up in every signature that mentions a vector.
§Why the pin is a handle and not a lifetime
The obvious way to express a borrow in Rust is a lifetime parameter, and it is the wrong one
here. A lifetime on Buffer is a lifetime on Data, which is a lifetime on
Vector, which is a lifetime on Chunk, which is a lifetime on
every operator’s state, on every trait object in the pipeline, and on every queue a chunk is put
into for another thread to pick up. The scheduler is exactly that last thing, so the borrow would
have to outlive a hand off between threads that the compiler has no way to see the end of. The
two ways out of that are unsafe code and a copy at the boundary, and the copy at the boundary is
the thing the borrow existed to avoid.
So the pin is a Pin, a reference counted handle the buffer holds, and the page stays alive
because the handle is alive rather than because a region ends. It costs one atomic increment per
vector construction, which is not measurable next to reading the page it is protecting, and the
ownership story stays uniform: a chunk is Send, always, whatever its columns are pointing at.
§Why it lands now with one variant
There is no buffer manager, so there is nothing to borrow from, and writing the borrowed variant
now would be writing an interface against an imaginary caller. What lands now is the enum, with
only the owned variant in it, so that adding the second variant at layer three is a change inside
this crate rather than a change to every signature in the workspace. The reader side of that
migration is already done by the Deref below: everything outside this crate reads a slice, and
a slice is what both variants will hand back.
The writer side is Buffer::to_mut, which is the one function that has to grow a case. A write
through a borrowed buffer has to copy the page into an owned run first, which is what Cow does
and for the same reason, and having the call site named now means that day is a change to one
function rather than a search for every push.
Structs§
- Buffer
- A run of values of one physical type.
Type Aliases§
- Pin
- What keeps a page alive for as long as a buffer points into it.