pub struct Memory { /* private fields */ }Expand description
A budget shared by everything running against one database.
Cheap to clone, and a clone shares the total with the budget it came from, so two queries running
at once are held to one limit between them rather than to one each. That is what DuckDB’s
memory_limit means and it is the only reading that is any use: a limit that each query gets a
fresh copy of is not a limit on the process.
§What it counts
What an operator says it is holding. Nothing here hooks the allocator, so the number is the sum of what the buffering operators reserved and not the resident size of the process. The gap is real and it is in one direction, since an operator charges for what it asked for and never for more.
How large the gap is decides whether the limit is any use. Under reporting is the safe direction
only while it is small: a budget that is spent at two fifths of the real footprint is not a
conservative limit, it is a limit that lets a query take two and a half times what it was
allowed and get killed from outside anyway, which is exactly what it was there to prevent. #227
found the aggregate doing that and it is why the operators charge a container for its capacity
rather than its length and add ALLOCATION per block. Memory::peak is the accounted side
of that comparison, so the gap can be measured rather than assumed.
The operators that reserve are the ones that buffer without bound, which is sorting, grouping, duplicate elimination, joining, set operations and the result a query hands back. A streaming operator holds one chunk and gives it away again, so charging it would be counting the same megabyte once per level of the tree.
§Why a reservation rather than a pair of calls
Memory::reserve hands back a Reservation that releases what it took when it is dropped,
so an operator that fails halfway through, or a query stopped by an interrupt, gives its memory
back without anybody writing the release. A pair of take and give calls is the version where
the release is missed on the error path, and the error path here is the one that matters, since
running out of memory is itself an error and it unwinds through every operator below.
Implementations§
Source§impl Memory
impl Memory
Sourcepub fn unlimited() -> Self
pub fn unlimited() -> Self
A budget nothing is refused against, which still counts what is held.
The counting is kept because Memory::used is worth reading whether or not there is a
limit, and because a query that behaves differently depending on whether a limit is set is a
query whose limit cannot be tested by setting one.
Sourcepub fn with_limit(bytes: u64) -> Self
pub fn with_limit(bytes: u64) -> Self
A budget of this many bytes.
Sourcepub fn set_limit(&self, limit: Option<u64>)
pub fn set_limit(&self, limit: Option<u64>)
Changes the limit, for every query holding this budget.
A limit below what is already held is allowed and refuses the next reservation rather than stopping anything, which is what DuckDB does and is the only behaviour that does not turn a setting into a way of killing whatever happens to be running.
Sourcepub fn peak(&self) -> u64
pub fn peak(&self) -> u64
The most that was ever held at once since the last Memory::forget_peak.
Memory::used falls back to zero when a query ends, so it answers what is held and never
what was held, and what was held is the number worth knowing. It is what a query cost, it is
what has to be compared against the resident set to find out whether the accounting means
anything, and it is the one to print beside a benchmark row.
It is a property of the budget rather than of a query, so two queries running at once share one and it is the peak of the pair.
Sourcepub fn forget_peak(&self)
pub fn forget_peak(&self)
Puts the high water mark back to what is held right now.
Back to what is held rather than to zero, because a mark below the current total would be a number that says less was held than is held.
Sourcepub fn reservation(&self) -> Reservation
pub fn reservation(&self) -> Reservation
A reservation on this budget that is holding nothing yet.
What a buffering operator starts with, because it is built before it has read anything and its constructor has no error to report. It grows as the input arrives.
Sourcepub fn reserve(&self, bytes: u64) -> Result<Reservation>
pub fn reserve(&self, bytes: u64) -> Result<Reservation>
Takes bytes out of the budget, to be given back when the reservation is dropped.
Reserving nothing always works and is the way an operator gets a handle it can grow later.
§Errors
crate::ErrorCode::OutOfMemory when the limit is set and this would pass it. Nothing is
taken in that case, so a caller that carries on after catching it is holding what it held
before.