rudb_common/memory.rs
1//! How much memory a query is allowed to hold.
2
3use std::sync::Arc;
4use std::sync::atomic::{AtomicU64, Ordering};
5
6use crate::error::{Error, Result};
7
8/// A budget shared by everything running against one database.
9///
10/// Cheap to clone, and a clone shares the total with the budget it came from, so two queries running
11/// at once are held to one limit between them rather than to one each. That is what DuckDB's
12/// `memory_limit` means and it is the only reading that is any use: a limit that each query gets a
13/// fresh copy of is not a limit on the process.
14///
15/// # What it counts
16///
17/// What an operator says it is holding. Nothing here hooks the allocator, so the number is the sum
18/// of what the buffering operators reserved and not the resident size of the process. The gap is
19/// real and it is in one direction, since an operator charges for what it asked for and never for
20/// more.
21///
22/// How large the gap is decides whether the limit is any use. Under reporting is the safe direction
23/// only while it is small: a budget that is spent at two fifths of the real footprint is not a
24/// conservative limit, it is a limit that lets a query take two and a half times what it was
25/// allowed and get killed from outside anyway, which is exactly what it was there to prevent. #227
26/// found the aggregate doing that and it is why the operators charge a container for its capacity
27/// rather than its length and add [`ALLOCATION`] per block. [`Memory::peak`] is the accounted side
28/// of that comparison, so the gap can be measured rather than assumed.
29///
30/// The operators that reserve are the ones that buffer without bound, which is sorting, grouping,
31/// duplicate elimination, joining, set operations and the result a query hands back. A streaming
32/// operator holds one chunk and gives it away again, so charging it would be counting the same
33/// megabyte once per level of the tree.
34///
35/// # Why a reservation rather than a pair of calls
36///
37/// [`Memory::reserve`] hands back a [`Reservation`] that releases what it took when it is dropped,
38/// so an operator that fails halfway through, or a query stopped by an interrupt, gives its memory
39/// back without anybody writing the release. A pair of `take` and `give` calls is the version where
40/// the release is missed on the error path, and the error path here is the one that matters, since
41/// running out of memory is itself an error and it unwinds through every operator below.
42#[derive(Debug, Clone)]
43pub struct Memory {
44 inner: Arc<Budget>,
45}
46
47#[derive(Debug)]
48struct Budget {
49 used: AtomicU64,
50 /// The limit, with [`NO_LIMIT`] meaning there is none.
51 ///
52 /// Atomic rather than plain, because `SET memory_limit` changes it while queries are running
53 /// and the budget is shared by every one of them. A query that is already holding more than a
54 /// new limit allows is not stopped: it keeps what it has and is refused the next time it asks
55 /// for more, which is what DuckDB does and is the only behaviour that does not turn a setting
56 /// into a way of killing whatever happens to be running.
57 limit: AtomicU64,
58 /// The most that has ever been held at once, which nothing gives back.
59 ///
60 /// Added for #227, where the question was how far the accounting is from what the process
61 /// actually takes, and the only way to ask it was to run a query under `/usr/bin/time -v` and
62 /// compare by hand. Now the accounted side of that comparison is a number the database will
63 /// say, so a test can assert on it and a benchmark can print it beside the resident set.
64 peak: AtomicU64,
65}
66
67/// What the limit holds when there is no limit.
68///
69/// A sentinel rather than an `Option`, because an `Option<u64>` is not atomic and a lock around the
70/// limit would be a lock taken on every reservation.
71const NO_LIMIT: u64 = u64::MAX;
72
73/// What the allocator takes on top of a block, for every block handed out.
74///
75/// Every general purpose allocator keeps a header beside the block and rounds the size up to an
76/// alignment, and none of them will say by how much. Sixteen is glibc's, an eight byte header and a
77/// sixteen byte alignment, and it is a floor rather than an average, so a caller that adds this per
78/// allocation is still under reporting and is under reporting by much less than one that adds
79/// nothing.
80///
81/// It matters because the things this budget counts are made of small allocations. A hash table of
82/// seventeen million groups is seventeen million blocks, and sixteen bytes apiece is a quarter of a
83/// gigabyte that was invisible before #227.
84pub const ALLOCATION: u64 = 16;
85
86impl Default for Memory {
87 fn default() -> Self {
88 Self::unlimited()
89 }
90}
91
92impl Memory {
93 /// A budget nothing is refused against, which still counts what is held.
94 ///
95 /// The counting is kept because [`Memory::used`] is worth reading whether or not there is a
96 /// limit, and because a query that behaves differently depending on whether a limit is set is a
97 /// query whose limit cannot be tested by setting one.
98 #[must_use]
99 pub fn unlimited() -> Self {
100 Self::new(None)
101 }
102
103 /// A budget of this many bytes.
104 #[must_use]
105 pub fn with_limit(bytes: u64) -> Self {
106 Self::new(Some(bytes))
107 }
108
109 /// A budget of this many bytes, or no limit at all.
110 #[must_use]
111 pub fn new(limit: Option<u64>) -> Self {
112 let limit = AtomicU64::new(limit.unwrap_or(NO_LIMIT));
113 Self { inner: Arc::new(Budget { used: AtomicU64::new(0), limit, peak: AtomicU64::new(0) }) }
114 }
115
116 /// The limit, if there is one.
117 #[must_use]
118 pub fn limit(&self) -> Option<u64> {
119 match self.inner.limit.load(Ordering::Relaxed) {
120 NO_LIMIT => None,
121 limit => Some(limit),
122 }
123 }
124
125 /// Changes the limit, for every query holding this budget.
126 ///
127 /// A limit below what is already held is allowed and refuses the next reservation rather than
128 /// stopping anything, which is what DuckDB does and is the only behaviour that does not turn a
129 /// setting into a way of killing whatever happens to be running.
130 pub fn set_limit(&self, limit: Option<u64>) {
131 self.inner.limit.store(limit.unwrap_or(NO_LIMIT), Ordering::Relaxed);
132 }
133
134 /// How many bytes are held right now.
135 #[must_use]
136 pub fn used(&self) -> u64 {
137 self.inner.used.load(Ordering::Relaxed)
138 }
139
140 /// The most that was ever held at once since the last [`Memory::forget_peak`].
141 ///
142 /// [`Memory::used`] falls back to zero when a query ends, so it answers what is held and never
143 /// what was held, and what was held is the number worth knowing. It is what a query cost, it is
144 /// what has to be compared against the resident set to find out whether the accounting means
145 /// anything, and it is the one to print beside a benchmark row.
146 ///
147 /// It is a property of the budget rather than of a query, so two queries running at once share
148 /// one and it is the peak of the pair.
149 #[must_use]
150 pub fn peak(&self) -> u64 {
151 self.inner.peak.load(Ordering::Relaxed)
152 }
153
154 /// Puts the high water mark back to what is held right now.
155 ///
156 /// Back to what is held rather than to zero, because a mark below the current total would be a
157 /// number that says less was held than is held.
158 pub fn forget_peak(&self) {
159 self.inner.peak.store(self.used(), Ordering::Relaxed);
160 }
161
162 /// A reservation on this budget that is holding nothing yet.
163 ///
164 /// What a buffering operator starts with, because it is built before it has read anything and
165 /// its constructor has no error to report. It grows as the input arrives.
166 #[must_use]
167 pub fn reservation(&self) -> Reservation {
168 Reservation { memory: self.clone(), bytes: 0 }
169 }
170
171 /// Takes `bytes` out of the budget, to be given back when the reservation is dropped.
172 ///
173 /// Reserving nothing always works and is the way an operator gets a handle it can grow later.
174 ///
175 /// # Errors
176 ///
177 /// [`crate::ErrorCode::OutOfMemory`] when the limit is set and this would pass it. Nothing is
178 /// taken in that case, so a caller that carries on after catching it is holding what it held
179 /// before.
180 pub fn reserve(&self, bytes: u64) -> Result<Reservation> {
181 self.take(bytes)?;
182 Ok(Reservation { memory: self.clone(), bytes })
183 }
184
185 /// Adds to the total, or reports that it cannot.
186 ///
187 /// The loop is a compare and exchange rather than a fetch and add with a check afterwards,
188 /// because a fetch and add that has to be undone is a window in which another thread sees a
189 /// total that was never allowed and refuses a query that would have fit.
190 fn take(&self, bytes: u64) -> Result<()> {
191 let Some(limit) = self.limit() else {
192 let was = self.inner.used.fetch_add(bytes, Ordering::Relaxed);
193 self.inner.peak.fetch_max(was + bytes, Ordering::Relaxed);
194 return Ok(());
195 };
196 let mut used = self.inner.used.load(Ordering::Relaxed);
197 loop {
198 let wanted = used.saturating_add(bytes);
199 if wanted > limit {
200 return Err(Error::out_of_memory(format!(
201 "could not allocate {} ({}/{} used)",
202 human(bytes),
203 human(used),
204 human(limit)
205 )));
206 }
207 match self.inner.used.compare_exchange_weak(
208 used,
209 wanted,
210 Ordering::Relaxed,
211 Ordering::Relaxed,
212 ) {
213 Ok(_) => {
214 self.inner.peak.fetch_max(wanted, Ordering::Relaxed);
215 return Ok(());
216 }
217 Err(now) => used = now,
218 }
219 }
220 }
221
222 /// Gives bytes back.
223 fn give(&self, bytes: u64) {
224 self.inner.used.fetch_sub(bytes, Ordering::Relaxed);
225 }
226}
227
228/// Memory one operator is holding, given back when this is dropped.
229///
230/// It starts at whatever [`Memory::reserve`] was asked for and grows from there, which is the shape
231/// a buffering operator wants: it does not know how much it will hold until it has read its input,
232/// and it wants to be told as soon as the answer is too much rather than after the last row.
233#[derive(Debug)]
234pub struct Reservation {
235 memory: Memory,
236 bytes: u64,
237}
238
239impl Reservation {
240 /// How much this reservation is holding.
241 #[must_use]
242 pub fn bytes(&self) -> u64 {
243 self.bytes
244 }
245
246 /// Takes another `bytes` out of the same budget.
247 ///
248 /// # Errors
249 ///
250 /// [`crate::ErrorCode::OutOfMemory`] when the limit is set and this would pass it. The
251 /// reservation is unchanged in that case and still releases what it already held.
252 pub fn grow(&mut self, bytes: u64) -> Result<()> {
253 self.memory.take(bytes)?;
254 self.bytes += bytes;
255 Ok(())
256 }
257
258 /// Gives `bytes` of it back, or everything if that is more than this is holding.
259 ///
260 /// For an operator that charged several things against one reservation and has dropped one of
261 /// them. The aggregate charges its hash table, its accumulators and its distinct sets together,
262 /// and then hands the keys out of the table into the rows it is building, at which point the
263 /// table is gone and the accumulators are not. Waiting for the whole reservation would charge a
264 /// table that no longer exists for the whole of the conversion, which is exactly the moment the
265 /// operator is holding the most.
266 pub fn shrink(&mut self, bytes: u64) {
267 let given = bytes.min(self.bytes);
268 self.memory.give(given);
269 self.bytes -= given;
270 }
271
272 /// Gives everything back now rather than at the end of the scope.
273 ///
274 /// For an operator that has finished with its buffer and is about to hand out what it built
275 /// from it, where waiting for the drop would hold two copies against the limit at once.
276 pub fn release(&mut self) {
277 self.memory.give(self.bytes);
278 self.bytes = 0;
279 }
280}
281
282impl Drop for Reservation {
283 fn drop(&mut self) {
284 self.memory.give(self.bytes);
285 }
286}
287
288/// A size the way an error message says one.
289///
290/// The shape DuckDB prints, which is one decimal place and the binary units, so that
291/// `9.2 MiB/9.2 MiB used` in a message from here reads as the same sentence as the one from there.
292///
293/// It truncates rather than rounding, which was measured rather than chosen. 9,751,000 bytes is
294/// 9.2993 mebibytes and the pin prints `9.2 MiB` for it, both in an out of memory message and in
295/// `SELECT current_setting('memory_limit')`, so the digit it drops is dropped and not carried. The
296/// arithmetic is in whole tenths for the same reason: a division by 1024 in floating point puts a
297/// number that is exactly 9.3 a hair under it often enough to matter at one decimal place.
298///
299/// This is not the formatter `--print-config` uses. A configuration dump prints a size with a unit
300/// only when the byte count divides by it exactly, because somebody reading a dump is comparing it
301/// against what they set.
302pub fn human(bytes: u64) -> String {
303 let mut divisor = 1u128;
304 for unit in ["bytes", "KiB", "MiB", "GiB", "TiB", "PiB"] {
305 if u128::from(bytes) < divisor * 1024 || unit == "PiB" {
306 if unit == "bytes" {
307 return format!("{bytes} bytes");
308 }
309 let tenths = u128::from(bytes) * 10 / divisor;
310 return format!("{}.{} {unit}", tenths / 10, tenths % 10);
311 }
312 divisor *= 1024;
313 }
314 unreachable!("the loop returns on its last unit")
315}
316
317#[cfg(test)]
318mod tests {
319 use super::{Memory, human};
320
321 #[test]
322 fn the_peak_remembers_what_used_forgets() {
323 let memory = Memory::with_limit(1 << 20);
324 {
325 let _held = memory.reserve(1000).expect("room for the first");
326 let _more = memory.reserve(2000).expect("room for the second");
327 assert_eq!(memory.used(), 3000);
328 assert_eq!(memory.peak(), 3000);
329 }
330 assert_eq!(memory.used(), 0);
331 assert_eq!(memory.peak(), 3000, "what was held is the number worth knowing");
332 let held = memory.reserve(500).expect("room again");
333 assert_eq!(memory.peak(), 3000, "a smaller total does not move the mark down");
334 memory.forget_peak();
335 assert_eq!(memory.peak(), 500, "forgetting goes back to what is held, not to zero");
336 drop(held);
337 }
338
339 #[test]
340 fn a_budget_with_no_limit_still_has_a_peak() {
341 // The unlimited path is a plain add rather than the compare and exchange loop, so it is a
342 // second place the mark has to be moved and a second place to forget to.
343 let memory = Memory::unlimited();
344 let held = memory.reserve(4096).expect("nothing is refused");
345 drop(held);
346 assert_eq!(memory.used(), 0);
347 assert_eq!(memory.peak(), 4096);
348 }
349
350 #[test]
351 fn a_shrink_gives_back_part_and_never_more_than_it_holds() {
352 let memory = Memory::with_limit(1000);
353 let mut held = memory.reserve(800).expect("room for this");
354 held.shrink(300);
355 assert_eq!(held.bytes(), 500);
356 assert_eq!(memory.used(), 500, "the budget got the difference back");
357 memory.reserve(400).expect("which is room for something else");
358 // A reservation that is asked for more than it has gives what it has. The alternative is an
359 // arithmetic overflow in an operator that miscounted, and the operator that miscounted is
360 // the one that would never find out.
361 held.shrink(u64::MAX);
362 assert_eq!(held.bytes(), 0);
363 assert_eq!(memory.used(), 0);
364 }
365
366 #[test]
367 fn a_refused_reservation_does_not_move_the_mark() {
368 let memory = Memory::with_limit(1000);
369 let held = memory.reserve(900).expect("room for this");
370 memory.reserve(200).expect_err("no room for that");
371 assert_eq!(memory.peak(), 900, "what was refused was never held");
372 drop(held);
373 }
374
375 #[test]
376 fn an_unlimited_budget_refuses_nothing_and_still_counts() {
377 let memory = Memory::unlimited();
378 assert_eq!(memory.limit(), None);
379 let held = memory.reserve(1 << 30).expect("nothing is refused");
380 assert_eq!(memory.used(), 1 << 30);
381 assert_eq!(held.bytes(), 1 << 30);
382 }
383
384 #[test]
385 fn a_reservation_gives_its_bytes_back_when_it_is_dropped() {
386 let memory = Memory::with_limit(1024);
387 {
388 let _held = memory.reserve(1000).expect("a thousand of a thousand and twenty four");
389 assert_eq!(memory.used(), 1000);
390 }
391 assert_eq!(memory.used(), 0);
392 memory.reserve(1000).expect("the room is back");
393 }
394
395 #[test]
396 fn passing_the_limit_is_an_out_of_memory_error_that_says_the_numbers() {
397 let memory = Memory::with_limit(10 * 1024 * 1024);
398 let _held = memory.reserve(9 * 1024 * 1024).expect("nine of ten");
399 let error = memory.reserve(2 * 1024 * 1024).expect_err("eleven of ten");
400 assert_eq!(error.code().duckdb_name(), "Out of Memory Error");
401 assert_eq!(error.message(), "could not allocate 2.0 MiB (9.0 MiB/10.0 MiB used)");
402 }
403
404 #[test]
405 fn a_refused_reservation_takes_nothing() {
406 let memory = Memory::with_limit(100);
407 memory.reserve(200).expect_err("twice the limit");
408 assert_eq!(memory.used(), 0);
409 memory.reserve(100).expect("the limit is still all there");
410 }
411
412 #[test]
413 fn a_reservation_grows_until_it_cannot() {
414 let memory = Memory::with_limit(100);
415 let mut held = memory.reserve(0).expect("nothing is always available");
416 held.grow(60).expect("sixty of a hundred");
417 held.grow(40).expect("and the other forty");
418 held.grow(1).expect_err("there is no more");
419 assert_eq!(held.bytes(), 100, "the refused growth changed nothing");
420 assert_eq!(memory.used(), 100);
421 }
422
423 #[test]
424 fn releasing_early_frees_the_room_before_the_scope_ends() {
425 let memory = Memory::with_limit(100);
426 let mut held = memory.reserve(100).expect("all of it");
427 held.release();
428 assert_eq!(memory.used(), 0);
429 assert_eq!(held.bytes(), 0);
430 // And the drop that follows does not take the total below zero.
431 drop(held);
432 assert_eq!(memory.used(), 0);
433 }
434
435 #[test]
436 fn two_handles_on_one_budget_are_held_to_it_between_them() {
437 // A limit each query gets a fresh copy of is not a limit on the process.
438 let memory = Memory::with_limit(100);
439 let other = memory.clone();
440 let _held = memory.reserve(60).expect("sixty");
441 other.reserve(60).expect_err("the other sixty does not fit beside it");
442 }
443
444 #[test]
445 fn a_size_reads_the_way_duckdb_writes_one() {
446 assert_eq!(human(0), "0 bytes");
447 assert_eq!(human(512), "512 bytes");
448 assert_eq!(human(256 * 1024), "256.0 KiB");
449 assert_eq!(human(10 * 1024 * 1024), "10.0 MiB");
450 // The two the pin prints, read off it with a memory limit set to each number and then with
451 // a query that runs out of it. 9,751,000 bytes is 9.2993 mebibytes and the answer is 9.2,
452 // so the tenth is truncated and not rounded.
453 assert_eq!(human(9_751_000), "9.2 MiB");
454 assert_eq!(human(10_000_000), "9.5 MiB");
455 assert_eq!(human(1_000_000_000), "953.6 MiB");
456 assert_eq!(human(3 * 1024 * 1024 * 1024), "3.0 GiB");
457 assert_eq!(human(5 * 1024u64.pow(5)), "5.0 PiB");
458 // The last unit runs off the end rather than there being a unit past it, because a size
459 // that big is a bug in whatever asked for it and not a number anybody reads.
460 assert_eq!(human(u64::MAX), "16383.9 PiB");
461 }
462}