rudb_exec/query.rs
1//! A built query: the pipelines it runs, in the order they have to run, and the queue the rows come
2//! out of.
3//!
4//! This is what replaced the pull tree. A plan used to become a tree of operators whose root was
5//! pulled from, and each pipeline breaker in it drained the tree below it on the first pull. The
6//! order that produced was right, because a breaker cannot answer until its input is finished, but
7//! it was an order the call stack happened to have rather than one anybody wrote down. Here it is
8//! written down: [`Query::run`] takes the pipelines in dependency order and runs each of them to
9//! completion.
10//!
11//! # Where the threads are
12//!
13//! Inside one pipeline and not across them. Each pipeline runs on as many threads as
14//! [`Pipeline::degree`] says, which is bounded by what the database's [`Pool`] will lend, by
15//! whether every operator in it will run as more than one instance, and by how many morsels its
16//! source has. Then the next one starts.
17//!
18//! Running two pipelines of one query at the same time is the other kind of parallelism and it is
19//! not here. The dependency edges say which pairs could overlap, so the information is already
20//! written down, and what is missing is a scheduler that holds several pipelines at once rather
21//! than a driver that is handed one. It is also worth much less: the shapes in ClickBench are a
22//! scan feeding an aggregate feeding a sort, which is a chain, and a chain has nothing to overlap.
23
24use std::sync::Arc;
25
26use rudb_common::{Cancel, Error, Result};
27use rudb_metrics::Driver;
28use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
29
30use rudb_pipeline::{Pipeline, Pool, RootReader, run_parallel};
31use rudb_vector::Chunk;
32
33use crate::schema::Schema;
34
35/// A plan that has been built and is ready to run.
36///
37/// It borrows the plan and the catalog it was built from, which is what `'a` is. A scan reads its
38/// rows out of the catalog's table rather than copying them and an expression reads its constants
39/// out of the plan's arena, so a query cannot outlive either.
40#[derive(Debug)]
41pub struct Query<'a> {
42 /// The pipelines, in an order where everything a pipeline waits for comes before it.
43 pipelines: Vec<Pipeline<'a>>,
44 /// The driver counters for each pipeline, in the same order.
45 drivers: Vec<Arc<Driver>>,
46 /// Where the last pipeline puts its rows.
47 reader: Option<RootReader>,
48 /// What the query produces.
49 schema: Schema,
50 /// CPU nanoseconds burned on threads other than the one that called [`Query::run`].
51 worker_cpu_ns: AtomicU64,
52 /// The most instances any one pipeline ran as.
53 widest: AtomicUsize,
54}
55
56impl<'a> Query<'a> {
57 /// A query over pipelines that are already in dependency order, each paired with its driver.
58 ///
59 /// # Errors
60 ///
61 /// [`ErrorCode::Internal`](rudb_common::ErrorCode::Internal) if a pipeline waits for one that
62 /// does not come before it. That is a builder bug rather than anything a query can cause, and it
63 /// is checked here because running the pipelines in the wrong order reads a buffer nobody has
64 /// filled yet and answers with no rows rather than failing.
65 pub(crate) fn new(
66 pipelines: Vec<Pipeline<'a>>,
67 drivers: Vec<Arc<Driver>>,
68 reader: Option<RootReader>,
69 schema: Schema,
70 ) -> Result<Self> {
71 for (at, pipeline) in pipelines.iter().enumerate() {
72 for waited in pipeline.depends_on() {
73 let before = pipelines[..at].iter().any(|earlier| earlier.id() == *waited);
74 if !before {
75 return Err(Error::internal(format!(
76 "{} waits for {waited}, which the builder did not put before it",
77 pipeline.id()
78 )));
79 }
80 }
81 }
82 Ok(Self {
83 pipelines,
84 drivers,
85 reader,
86 schema,
87 worker_cpu_ns: AtomicU64::new(0),
88 widest: AtomicUsize::new(0),
89 })
90 }
91
92 /// The columns this query produces.
93 #[must_use]
94 pub fn schema(&self) -> &Schema {
95 &self.schema
96 }
97
98 /// How many pipelines the query runs.
99 #[must_use]
100 pub fn pipelines(&self) -> usize {
101 self.pipelines.len()
102 }
103
104 /// Runs every pipeline, stopping at the first one that fails.
105 ///
106 /// Each one is timed against its own driver, which is the loop that runs a pipeline rather than
107 /// any operator in it. That time is not nothing: on a scan of ten million rows the loop goes
108 /// round ten thousand times, and none of it sits inside an operator's own span, so without a
109 /// driver it is time the metrics document cannot account for.
110 ///
111 /// The lease is taken per pipeline and given back at the end of it, so a query whose scan uses
112 /// nine threads and whose sort uses one holds nine for as long as the scan and one after that,
113 /// and the threads it is not using are there for whatever else the database is running.
114 ///
115 /// How many threads it borrows and how many instances it runs are two numbers. The instances
116 /// are what the source has work for. The borrow is the wider of that and what the sink says it
117 /// can finish on, because the finish happens on the same threads with every instance already
118 /// joined, and a hash aggregate merging a million groups is not the same width as the scan that
119 /// fed it.
120 ///
121 /// # Errors
122 ///
123 /// Whatever any operator reports, or [`ErrorCode::Interrupt`](rudb_common::ErrorCode::Interrupt)
124 /// if the token says to stop. The check is per chunk, in the driver, which is why no operator
125 /// here holds a token of its own except the join, whose nested loop can outlive a chunk.
126 pub fn run(&self, cancel: &Cancel, pool: &Pool) -> Result<()> {
127 for (pipeline, driver) in self.pipelines.iter().zip(&self.drivers) {
128 let lease = pool.lease(pipeline.lease_degree(pool.threads()));
129 let degree = pipeline.degree(pool.threads()).min(lease.degree());
130 let spread = {
131 let _running = driver.running();
132 run_parallel(pipeline, cancel, &lease, degree)?
133 };
134 driver.ran(degree, spread.worker_cpu_ns);
135 driver.waited(spread.slowest_ns, spread.slowest_cpu_ns, spread.finalize_ns);
136 self.worker_cpu_ns.fetch_add(spread.worker_cpu_ns, Ordering::Relaxed);
137 self.widest.fetch_max(degree, Ordering::Relaxed);
138 }
139 Ok(())
140 }
141
142 /// CPU nanoseconds this query burned on threads other than the one that ran it.
143 ///
144 /// A caller timing the execution reads its own thread's CPU clock, which is the only clock
145 /// there is that attributes work to the thread that did it, and which therefore cannot see the
146 /// workers. This is what it missed.
147 #[must_use]
148 pub fn worker_cpu_ns(&self) -> u64 {
149 self.worker_cpu_ns.load(Ordering::Relaxed)
150 }
151
152 /// The most instances any one pipeline of this query ran as.
153 ///
154 /// Not the setting and not an average. A query whose scan ran on nine threads and whose sort ran
155 /// on one reports nine, because the question this answers is what the query was able to use.
156 #[must_use]
157 pub fn widest(&self) -> usize {
158 self.widest.load(Ordering::Relaxed)
159 }
160
161 /// The next chunk of the answer, or `None` when there are no more.
162 ///
163 /// Only meaningful after [`Query::run`] has returned. The serial driver runs a pipeline to
164 /// completion, so everything the query produced is queued by then, and taking a chunk here
165 /// removes it from the queue rather than copying it out.
166 ///
167 /// # Errors
168 ///
169 /// [`ErrorCode::Internal`](rudb_common::ErrorCode::Internal) if a thread panicked while holding
170 /// the queue.
171 pub fn next_chunk(&self) -> Result<Option<Chunk>> {
172 let chunk = self
173 .reader
174 .as_ref()
175 .ok_or_else(|| Error::internal("a query built into a sink has no result reader"))?
176 .next_chunk()?;
177 if let Some(chunk) = &chunk {
178 chunk.validate_external()?;
179 }
180 Ok(chunk)
181 }
182
183 /// Runs the query and collects everything it produced.
184 ///
185 /// The convenience the tests and the simple callers want. A caller that cares about holding one
186 /// chunk at a time calls [`Query::run`] and [`Query::next_chunk`] itself.
187 ///
188 /// # Errors
189 ///
190 /// The same as [`Query::run`].
191 pub fn collect(&self, cancel: &Cancel, pool: &Pool) -> Result<Vec<Chunk>> {
192 self.run(cancel, pool)?;
193 let mut chunks = Vec::new();
194 while let Some(chunk) = self.next_chunk()? {
195 chunks.push(chunk);
196 }
197 Ok(chunks)
198 }
199}