1use std::fmt;
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::{Arc, Mutex, OnceLock};
15use std::task::{Context, Poll};
16use std::thread::Thread;
17
18use anyhow::{Error, anyhow};
19use async_trait::async_trait;
20use differential_dataflow::trace::ExertionLogic;
21use mz_cluster_client::client::{TimelyConfig, TryIntoProtocolNonce};
22use mz_ore::metric;
23use mz_ore::metrics::{ComputedUIntGauge, MetricsRegistry};
24use mz_service::client::{GenericClient, Partitionable, Partitioned};
25use mz_service::local::LocalClient;
26use timely::WorkerConfig;
27use timely::communication::allocator::zero_copy::bytes_slab::BytesRefill;
28use timely::communication::initialize::WorkerGuards;
29use timely::execute::execute_from;
30use timely::worker::Worker as TimelyWorker;
31use tokio::runtime::Handle;
32use tokio::sync::mpsc;
33use tracing::{info, info_span};
34use uuid::Uuid;
35
36use crate::communication::initialize_networking;
37
38type PartitionedClient<C, R> = Partitioned<LocalClient<C, R>, C, R>;
39
40pub struct ClusterClient<C>
42where
43 C: ClusterSpec,
44 (C::Command, C::Response): Partitionable<C::Command, C::Response>,
45{
46 inner: Option<PartitionedClient<C::Command, C::Response>>,
48 timely_container: Arc<Mutex<TimelyContainer<C>>>,
50}
51
52pub struct TimelyContainer<C: ClusterSpec> {
54 client_txs: Vec<
56 mpsc::UnboundedSender<(
57 Uuid,
58 mpsc::UnboundedReceiver<C::Command>,
59 mpsc::UnboundedSender<C::Response>,
60 )>,
61 >,
62 worker_guards: WorkerGuards<()>,
64}
65
66impl<C: ClusterSpec> TimelyContainer<C> {
67 pub fn worker_threads(&self) -> Vec<Thread> {
69 self.worker_guards
70 .guards()
71 .iter()
72 .map(|h| h.thread().clone())
73 .collect()
74 }
75}
76
77pub struct GuestClusterClient<Cmd, Resp>
81where
82 (Cmd, Resp): Partitionable<Cmd, Resp>,
83{
84 client_txs: Arc<
86 Vec<
87 mpsc::UnboundedSender<(
88 Uuid,
89 mpsc::UnboundedReceiver<Cmd>,
90 mpsc::UnboundedSender<Resp>,
91 )>,
92 >,
93 >,
94 worker_threads: Vec<Thread>,
96 inner: Option<PartitionedClient<Cmd, Resp>>,
98}
99
100impl<Cmd, Resp> GuestClusterClient<Cmd, Resp>
101where
102 Cmd: fmt::Debug + Send + TryIntoProtocolNonce,
103 Resp: fmt::Debug + Send,
104 (Cmd, Resp): Partitionable<Cmd, Resp>,
105{
106 pub fn new(
108 client_txs: Arc<
109 Vec<
110 mpsc::UnboundedSender<(
111 Uuid,
112 mpsc::UnboundedReceiver<Cmd>,
113 mpsc::UnboundedSender<Resp>,
114 )>,
115 >,
116 >,
117 worker_threads: Vec<Thread>,
118 ) -> Self {
119 Self {
120 client_txs,
121 worker_threads,
122 inner: None,
123 }
124 }
125
126 fn connect(&mut self, nonce: Uuid) {
127 let mut command_txs = Vec::new();
128 let mut response_rxs = Vec::new();
129 for client_tx in self.client_txs.iter() {
130 let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
131 let (resp_tx, resp_rx) = mpsc::unbounded_channel();
132
133 client_tx
134 .send((nonce, cmd_rx, resp_tx))
135 .expect("worker not dropped");
136
137 command_txs.push(cmd_tx);
138 response_rxs.push(resp_rx);
139 }
140
141 self.inner = Some(LocalClient::new_partitioned(
142 response_rxs,
143 command_txs,
144 self.worker_threads.clone(),
145 ));
146 }
147}
148
149impl<Cmd, Resp> fmt::Debug for GuestClusterClient<Cmd, Resp>
150where
151 (Cmd, Resp): Partitionable<Cmd, Resp>,
152{
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 f.debug_struct("GuestClusterClient").finish_non_exhaustive()
155 }
156}
157
158impl<Cmd, Resp> GenericClient<Cmd, Resp> for GuestClusterClient<Cmd, Resp>
159where
160 Cmd: fmt::Debug + Send + TryIntoProtocolNonce,
161 Resp: fmt::Debug + Send,
162 (Cmd, Resp): Partitionable<Cmd, Resp>,
163{
164 async fn send(&mut self, cmd: Cmd) -> Result<(), Error> {
165 match cmd.try_into_protocol_nonce() {
166 Ok(nonce) => {
167 self.connect(nonce);
168 Ok(())
169 }
170 Err(cmd) => self.inner.as_mut().expect("initialized").send(cmd).await,
171 }
172 }
173
174 fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Result<Option<Resp>, Error>> {
175 match self.inner.as_mut() {
176 Some(client) => client.poll_recv(cx),
177 None => Poll::Pending,
178 }
179 }
180}
181
182impl<C> ClusterClient<C>
183where
184 C: ClusterSpec,
185 (C::Command, C::Response): Partitionable<C::Command, C::Response>,
186{
187 pub fn new(timely_container: Arc<Mutex<TimelyContainer<C>>>) -> Self {
189 Self {
190 timely_container,
191 inner: None,
192 }
193 }
194
195 fn connect(&mut self, nonce: Uuid) -> Result<(), Error> {
197 let timely = self.timely_container.lock().expect("poisoned");
198
199 let mut command_txs = Vec::new();
200 let mut response_rxs = Vec::new();
201 for client_tx in &timely.client_txs {
202 let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
203 let (resp_tx, resp_rx) = mpsc::unbounded_channel();
204
205 client_tx
206 .send((nonce, cmd_rx, resp_tx))
207 .expect("worker not dropped");
208
209 command_txs.push(cmd_tx);
210 response_rxs.push(resp_rx);
211 }
212
213 self.inner = Some(LocalClient::new_partitioned(
214 response_rxs,
215 command_txs,
216 timely.worker_threads(),
217 ));
218 Ok(())
219 }
220}
221
222impl<C> fmt::Debug for ClusterClient<C>
223where
224 C: ClusterSpec,
225 (C::Command, C::Response): Partitionable<C::Command, C::Response>,
226{
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 f.debug_struct("ClusterClient")
229 .field("inner", &self.inner)
230 .finish_non_exhaustive()
231 }
232}
233
234impl<C> GenericClient<C::Command, C::Response> for ClusterClient<C>
235where
236 C: ClusterSpec,
237 (C::Command, C::Response): Partitionable<C::Command, C::Response>,
238{
239 async fn send(&mut self, cmd: C::Command) -> Result<(), Error> {
240 tracing::debug!("ClusterClient send={:?}", &cmd);
242
243 match cmd.try_into_protocol_nonce() {
244 Ok(nonce) => self.connect(nonce),
245 Err(cmd) => self.inner.as_mut().expect("initialized").send(cmd).await,
246 }
247 }
248
249 fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Result<Option<C::Response>, Error>> {
250 match self.inner.as_mut() {
251 Some(client) => client.poll_recv(cx),
252 None => Poll::Pending,
253 }
254 }
255}
256
257#[async_trait]
262pub trait ClusterSpec: Clone + Send + Sync + 'static {
263 type Command: fmt::Debug + Send + TryIntoProtocolNonce;
265 type Response: fmt::Debug + Send;
267
268 const NAME: &str;
270
271 fn run_worker(
273 &self,
274 timely_worker: &mut TimelyWorker,
275 client_rx: mpsc::UnboundedReceiver<(
276 Uuid,
277 mpsc::UnboundedReceiver<Self::Command>,
278 mpsc::UnboundedSender<Self::Response>,
279 )>,
280 );
281
282 async fn build_cluster(
284 &self,
285 config: TimelyConfig,
286 tokio_executor: Handle,
287 ) -> Result<TimelyContainer<Self>, Error> {
288 info!("Building timely container with config {config:?}");
289 let (client_txs, client_rxs): (Vec<_>, Vec<_>) = (0..config.workers)
290 .map(|_| mpsc::unbounded_channel())
291 .unzip();
292 let client_rxs: Mutex<Vec<_>> = Mutex::new(client_rxs.into_iter().map(Some).collect());
293
294 let refill = if config.enable_zero_copy_lgalloc {
295 BytesRefill {
296 logic: Arc::new(|size| Box::new(alloc::lgalloc_refill(size))),
297 limit: config.zero_copy_limit,
298 }
299 } else {
300 BytesRefill {
301 logic: Arc::new(|size| Box::new(vec![0; size])),
302 limit: config.zero_copy_limit,
303 }
304 };
305
306 let (builders, other) = initialize_networking(
307 config.workers,
308 config.process,
309 config.addresses.clone(),
310 refill,
311 config.enable_zero_copy,
312 )
313 .await?;
314
315 let mut worker_config = WorkerConfig::default();
316
317 if config.arrangement_exert_proportionality > 0 {
321 let merge_effort = Some(1000);
322
323 let arc: ExertionLogic = Arc::new(move |layers| {
329 EXERT_POLICY_CALLS.fetch_add(1, Ordering::Relaxed);
330 let mut prop = config.arrangement_exert_proportionality;
331
332 let layers = layers
335 .iter()
336 .copied()
337 .skip_while(|(_idx, count, _len)| *count == 0);
338
339 let mut first = true;
340 for (_idx, count, len) in layers {
341 if count > 1 {
342 EXERT_POLICY_MERGE_GRANTS.fetch_add(1, Ordering::Relaxed);
344 return merge_effort;
345 }
346
347 if !first && prop > 0 && len > 0 {
348 EXERT_POLICY_CONSOLIDATION_GRANTS.fetch_add(1, Ordering::Relaxed);
351 return merge_effort;
352 }
353
354 first = false;
355 prop /= 2;
356 }
357
358 None
359 });
360 worker_config.set::<ExertionLogic>("differential/default_exert_logic".to_string(), arc);
361 }
362
363 let spec = self.clone();
364 let worker_guards = execute_from(builders, other, worker_config, move |timely_worker| {
365 let worker_idx = timely_worker.index();
366
367 let span = info_span!("timely", name = Self::NAME, worker_id = worker_idx);
369 let _span_guard = span.enter();
370
371 mz_ore::process::set_current_thread_name(&format!("{}:{worker_idx}", Self::NAME));
375
376 let _tokio_guard = tokio_executor.enter();
377 let client_rx = client_rxs.lock().unwrap()[worker_idx % config.workers]
378 .take()
379 .unwrap();
380 spec.run_worker(timely_worker, client_rx);
381 })
382 .map_err(|e| anyhow!(e))?;
383
384 Ok(TimelyContainer {
385 client_txs,
386 worker_guards,
387 })
388 }
389}
390
391static EXERT_POLICY_CALLS: AtomicU64 = AtomicU64::new(0);
398static EXERT_POLICY_MERGE_GRANTS: AtomicU64 = AtomicU64::new(0);
399static EXERT_POLICY_CONSOLIDATION_GRANTS: AtomicU64 = AtomicU64::new(0);
400
401pub fn register_exert_policy_metrics(registry: &MetricsRegistry) {
406 static REGISTERED: OnceLock<()> = OnceLock::new();
407 REGISTERED.get_or_init(|| {
408 let _: ComputedUIntGauge = registry.register_computed_gauge(
409 metric!(name: "mz_arrangement_exert_policy_calls_total", help: "Arrangement exertion policy evaluations."),
410 || EXERT_POLICY_CALLS.load(Ordering::Relaxed),
411 );
412 for (reason, grants) in [
413 ("active_merge", &EXERT_POLICY_MERGE_GRANTS),
414 ("consolidation", &EXERT_POLICY_CONSOLIDATION_GRANTS),
415 ] {
416 let _: ComputedUIntGauge = registry.register_computed_gauge(
417 metric!(name: "mz_arrangement_exert_policy_grants_total", help: "Arrangement exertion policy evaluations that returned effort, by reason.", const_labels: {"reason" => reason}),
418 move || grants.load(Ordering::Relaxed),
419 );
420 }
421 });
422}
423
424mod alloc {
425 pub(crate) fn lgalloc_refill(size: usize) -> LgallocHandle {
431 match lgalloc::allocate::<u8>(size) {
432 Ok((pointer, capacity, handle)) => {
433 let handle = Some(handle);
434 LgallocHandle {
435 handle,
436 pointer,
437 capacity,
438 }
439 }
440 Err(_) => {
441 let mut alloc = vec![0_u8; size];
443 alloc.shrink_to_fit();
445 let pointer = std::ptr::NonNull::new(alloc.as_mut_ptr()).unwrap();
447 std::mem::forget(alloc);
449 LgallocHandle {
450 handle: None,
451 pointer,
452 capacity: size,
453 }
454 }
455 }
456 }
457
458 pub(crate) struct LgallocHandle {
462 handle: Option<lgalloc::Handle>,
464 pointer: std::ptr::NonNull<u8>,
466 capacity: usize,
468 }
469
470 unsafe impl Send for LgallocHandle {}
476
477 impl std::ops::Deref for LgallocHandle {
478 type Target = [u8];
479 #[inline(always)]
480 fn deref(&self) -> &Self::Target {
481 unsafe { std::slice::from_raw_parts(self.pointer.as_ptr(), self.capacity) }
482 }
483 }
484
485 impl std::ops::DerefMut for LgallocHandle {
486 #[inline(always)]
487 fn deref_mut(&mut self) -> &mut Self::Target {
488 unsafe { std::slice::from_raw_parts_mut(self.pointer.as_ptr(), self.capacity) }
489 }
490 }
491
492 impl Drop for LgallocHandle {
493 fn drop(&mut self) {
494 if let Some(handle) = self.handle.take() {
496 lgalloc::deallocate(handle);
497 } else {
498 unsafe { Vec::from_raw_parts(self.pointer.as_ptr(), 0, self.capacity) };
499 }
500 self.pointer = std::ptr::NonNull::dangling();
502 self.capacity = 0;
503 }
504 }
505}
506
507#[cfg(test)]
508mod exert_policy_metrics_tests {
509 use super::*;
510
511 #[mz_ore::test]
516 fn registering_twice_does_not_panic() {
517 let registry = MetricsRegistry::new();
518 register_exert_policy_metrics(®istry);
519 register_exert_policy_metrics(®istry);
520 let names: Vec<_> = registry
521 .gather()
522 .into_iter()
523 .map(|family| family.name().to_string())
524 .filter(|name| name.starts_with("mz_arrangement_exert_policy"))
525 .collect();
526 assert!(matches!(names.len(), 0 | 2), "{names:?}");
527 }
528}