Skip to main content

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}