Skip to content

Commit 5911208

Browse files
antiguruclaude
andcommitted
compute: publish maintained indexes into the sharing registry
Both export paths now publish their `oks`/`errs` arrangements into the per-process registry when the runtime's role publishes. A re-export arm has no streams of its own, so it re-imports the shared traces under its own id and publishes those. That import gives the re-export's dataflow operators, and `mz_compute_error_counts` forwards a dependency's counts only to a re-export whose dataflow has none, so the arm also logs the re-export's error counts from the imported errors. Logging indexes publish the same way, gated strictly on `Maintenance`: an interactive runtime reads maintenance's slot, and its own copy would clobber it, while `Solo` has no registry peer at all. `ComputeRuntimeRole::Interactive` stops being test-only. Nothing constructs it yet, but `publishes()` has to name it, and `pub mod server` keeps the variant reachable so dead-code analysis is satisfied without an attribute. The stale `owns_process_globals` note claiming every constructible role owns the globals goes with it. Carrying the role and the registry to the render path is what the rest of this change is: `Config` and `Worker` gain both, `ComputeState` stores them and exposes `role()`, and clusterd builds one registry per process. Per process, not per runtime, because a reader on one runtime looks up the slot a publisher on another filled. No behavior change. `Solo` is the only role anything constructs and it does not publish, so every added block is skipped and no dataflow gains an operator. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDm7opomJLxbNUEP3r9BLk
1 parent 287fc37 commit 5911208

8 files changed

Lines changed: 266 additions & 17 deletions

File tree

src/clusterd/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use mz_build_info::{BuildInfo, build_info};
2222
use mz_cloud_resources::AwsExternalIdPrefix;
2323
use mz_cluster_client::client::TimelyConfig;
2424
use mz_compute::server::{ComputeInstanceContext, ComputeRuntimeRole};
25+
use mz_compute::sharing::ArrangementSharingRegistry;
2526
use mz_http_util::DynamicFilterTarget;
2627
use mz_orchestrator_tracing::{StaticTracingConfig, TracingCliArgs};
2728
use mz_ore::cli::{self, CliConfig};
@@ -473,11 +474,16 @@ async fn run(args: Args) -> Result<(), anyhow::Error> {
473474
);
474475

475476
// Start compute server.
477+
//
478+
// The sharing registry is per process rather than per runtime: a reader on one runtime looks up
479+
// the slot a publisher on another runtime filled, so both must hold the same registry.
480+
let sharing_registry = ArrangementSharingRegistry::new();
476481
let compute_client_builder = mz_compute::server::serve(
477482
compute_timely_config,
478483
ComputeRuntimeRole::Solo,
479484
&metrics_registry,
480485
persist_clients,
486+
sharing_registry,
481487
txns_ctx,
482488
tracing_handle,
483489
ComputeInstanceContext {

src/compute/src/arrangement/manager.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,20 @@ where
228228
}
229229
}
230230

231+
impl<Tr> PaddedTrace<TraceAgent<Tr>>
232+
where
233+
Tr: TraceReader + 'static,
234+
{
235+
/// Imports the trace into `scope` as a live arrangement named `name`.
236+
pub fn import_named<'scope>(
237+
&self,
238+
scope: Scope<'scope, Tr::Time>,
239+
name: &str,
240+
) -> Arranged<'scope, TraceAgent<Tr>> {
241+
self.trace.clone().import_named(scope, name)
242+
}
243+
}
244+
231245
/// Bundles together traces for the successful computations (`oks`), the
232246
/// failed computations (`errs`), additional tokens that should share
233247
/// the lifetime of the bundled traces (`to_drop`).
@@ -287,6 +301,21 @@ impl TraceBundle {
287301
(&mut self.oks, &mut self.errs)
288302
}
289303

304+
/// Imports both traces into `scope` as live arrangements, for publishers to attach to.
305+
pub fn import_named<'scope>(
306+
&self,
307+
scope: Scope<'scope, Timestamp>,
308+
name: &str,
309+
) -> (
310+
Arranged<'scope, RowRowAgent<Timestamp, Diff>>,
311+
Arranged<'scope, ErrAgent<Timestamp, Diff>>,
312+
) {
313+
(
314+
self.oks.import_named(scope.clone(), &format!("{name} oks")),
315+
self.errs.import_named(scope, &format!("{name} errs")),
316+
)
317+
}
318+
290319
/// Returns a reference to the `to_drop` tokens.
291320
pub fn to_drop(&self) -> &Option<Rc<dyn Any>> {
292321
&self.to_drop

src/compute/src/compute_state.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ use crate::logging::compute::{CollectionLogging, ComputeEvent, PeekEvent};
7777
use crate::logging::initialize::LoggingTraces;
7878
use crate::metrics::{CollectionMetrics, WorkerMetrics};
7979
use crate::render::{LinearJoinSpec, StartSignal};
80-
use crate::server::{ComputeInstanceContext, ResponseSender};
80+
use crate::server::{ComputeInstanceContext, ComputeRuntimeRole, ResponseSender};
81+
use crate::sharing::ArrangementSharingRegistry;
8182

8283
mod error_scan;
8384
mod peek_budget;
@@ -214,6 +215,11 @@ pub struct ComputeState {
214215
/// A process-global cache of (blob_uri, consensus_uri) -> PersistClient.
215216
/// This is intentionally shared between workers.
216217
pub persist_clients: Arc<PersistClientCache>,
218+
/// A per-process registry of published index arrangements.
219+
///
220+
/// Intentionally shared between all workers of the process, each of which publishes into its own
221+
/// worker-ordinal slot. `Clone` shares the same underlying map.
222+
pub sharing_registry: ArrangementSharingRegistry,
217223
/// Context necessary for rendering txn-wal operators.
218224
pub txns_ctx: TxnsContext,
219225
/// History of commands received by this workers and all its peers.
@@ -295,6 +301,12 @@ pub struct ComputeState {
295301

296302
/// The storage worker forwards its introspection logs to the compute worker.
297303
pub storage_log_reader: Option<crate::server::StorageTimelyLogReader>,
304+
305+
/// Which of the process's compute runtimes this state belongs to.
306+
///
307+
/// Only the maintenance runtime runs the non-idempotent process-global initializers. The
308+
/// interactive runtime shares the same process and inherits those globals.
309+
role: ComputeRuntimeRole,
298310
}
299311

300312
impl ComputeState {
@@ -308,7 +320,9 @@ impl ComputeState {
308320

309321
/// Construct a new `ComputeState`.
310322
pub fn new(
323+
role: ComputeRuntimeRole,
311324
persist_clients: Arc<PersistClientCache>,
325+
sharing_registry: ArrangementSharingRegistry,
312326
txns_ctx: TxnsContext,
313327
metrics: WorkerMetrics,
314328
tracing_handle: Arc<TracingHandle>,
@@ -334,6 +348,7 @@ impl ComputeState {
334348
peek_stash_persist_location: None,
335349
compute_logger: None,
336350
persist_clients,
351+
sharing_registry,
337352
txns_ctx,
338353
command_history,
339354
max_result_size: u64::MAX,
@@ -353,9 +368,15 @@ impl ComputeState {
353368
init_system_time: mz_ore::now::SYSTEM_TIME(),
354369
replica_expiration: Antichain::default(),
355370
storage_log_reader,
371+
role,
356372
}
357373
}
358374

375+
/// Which of the process's compute runtimes this state serves.
376+
pub(crate) fn role(&self) -> ComputeRuntimeRole {
377+
self.role
378+
}
379+
359380
/// Return a mutable reference to the identified collection.
360381
///
361382
/// Panics if the collection doesn't exist.
@@ -1033,6 +1054,8 @@ impl<'a> ActiveComputeState<'a> {
10331054
Rc::clone(&self.compute_state.worker_config),
10341055
self.compute_state.workers_per_process,
10351056
storage_log_reader,
1057+
self.compute_state.role(),
1058+
self.compute_state.sharing_registry.clone(),
10361059
);
10371060

10381061
let dataflow_index = Rc::new(dataflow_index);

src/compute/src/compute_state/peek_sweep_tests.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use tokio::sync::mpsc;
2323

2424
use crate::metrics::ComputeMetrics;
2525
use crate::server::ComputeRuntimeRole;
26+
use crate::sharing::ArrangementSharingRegistry;
2627

2728
use super::index_peek_tests::{
2829
TARGET_ID, cancelling_errors, index_peek_with_uuid, rows_answer, trace_bundle, wide_ok_rows,
@@ -104,7 +105,9 @@ impl Harness {
104105
};
105106

106107
let state = ComputeState::new(
108+
ComputeRuntimeRole::Solo,
107109
Arc::new(PersistClientCache::new_no_metrics()),
110+
ArrangementSharingRegistry::new(),
108111
TxnsContext::default(),
109112
metrics,
110113
Arc::new(TracingHandle::disabled()),

src/compute/src/logging/initialize.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use differential_dataflow::logging::{DifferentialEvent, DifferentialEventBuilder
1616
use mz_compute_client::logging::{LogVariant, LoggingConfig};
1717
use mz_dyncfg::ConfigSet;
1818
use mz_ore::metrics::MetricsRegistry;
19-
use mz_repr::{Diff, Timestamp};
19+
use mz_repr::{Diff, GlobalId, Timestamp};
2020
use mz_storage_operators::persist_source::Subtime;
2121
use mz_timely_util::columnar::Column;
2222
use mz_timely_util::columnar::builder::ColumnBuilder;
@@ -35,7 +35,9 @@ use crate::extensions::arrange::{KeyCollection, MzArrange};
3535
use crate::logging::compute::{ComputeEvent, ComputeEventBuilder};
3636
use crate::logging::{BatchLogger, EventQueue, SharedLoggingState};
3737
use crate::render::errors::DataflowErrorSer;
38-
use crate::typedefs::{ErrBatcher, ErrBuilder};
38+
use crate::server::ComputeRuntimeRole;
39+
use crate::sharing::ArrangementSharingRegistry;
40+
use crate::typedefs::{ErrAgent, ErrBatcher, ErrBuilder, RowRowAgent};
3941

4042
/// Initialize logging dataflows.
4143
///
@@ -48,6 +50,8 @@ pub fn initialize(
4850
worker_config: Rc<ConfigSet>,
4951
workers_per_process: usize,
5052
storage_log_reader: Option<crate::server::StorageTimelyLogReader>,
53+
role: ComputeRuntimeRole,
54+
sharing_registry: ArrangementSharingRegistry,
5155
) -> LoggingTraces {
5256
let interval_ms = std::cmp::max(1, config.interval.as_millis());
5357

@@ -74,6 +78,8 @@ pub fn initialize(
7478
worker_config,
7579
workers_per_process,
7680
storage_log_reader,
81+
role,
82+
sharing_registry,
7783
};
7884

7985
// Depending on whether we should log the creation of the logging dataflows, we register the
@@ -114,6 +120,11 @@ struct LoggingContext<'a> {
114120
workers_per_process: usize,
115121
/// Optional reader for storage timely logging events.
116122
storage_log_reader: Option<crate::server::StorageTimelyLogReader>,
123+
/// This runtime's role. Only `Maintenance` publishes its logging indexes into the sharing
124+
/// registry.
125+
role: ComputeRuntimeRole,
126+
/// The per-process registry maintenance publishes its logging indexes into.
127+
sharing_registry: ArrangementSharingRegistry,
117128
}
118129

119130
pub(crate) struct LoggingTraces {
@@ -206,6 +217,20 @@ impl LoggingContext<'_> {
206217
let traces = collections
207218
.into_iter()
208219
.map(|(log, collection)| {
220+
// Publish maintenance's logging index into the sharing registry so the
221+
// interactive runtime serves introspection peeks from it. Gated on the
222+
// Maintenance role inside the helper, so this is a no-op (adds no operators) on
223+
// Interactive and Solo.
224+
if let Some(&id) = self.config.index_logs.get(&log) {
225+
publish_logging_index(
226+
self.role,
227+
&self.sharing_registry,
228+
&scope,
229+
id,
230+
&collection.trace,
231+
&errs,
232+
);
233+
}
209234
let bundle = TraceBundle::new(collection.trace, errs.clone())
210235
.with_drop(collection.token);
211236
(log, bundle)
@@ -353,3 +378,34 @@ impl ExtractTimestamp for (Timestamp, Subtime) {
353378
self.0
354379
}
355380
}
381+
382+
/// Publishes a maintenance logging index's `oks`/`errs` arrangements into the sharing registry so
383+
/// the interactive runtime serves introspection peeks from them.
384+
///
385+
/// Gated on the `Maintenance` role. Interactive reads maintenance's slot, and its own empty copy
386+
/// would clobber it. Solo has no registry peer.
387+
fn publish_logging_index(
388+
role: ComputeRuntimeRole,
389+
registry: &ArrangementSharingRegistry,
390+
scope: &timely::dataflow::Scope<'_, Timestamp>,
391+
id: GlobalId,
392+
oks_trace: &RowRowAgent<Timestamp, Diff>,
393+
errs_trace: &ErrAgent<Timestamp, Diff>,
394+
) {
395+
if role != ComputeRuntimeRole::Maintenance {
396+
return;
397+
}
398+
399+
// The arrange streams are consumed inside the per-log construction regions, so only the trace
400+
// handles survive here. Re-import them to give the publishers a live stream to attach to.
401+
let oks = oks_trace
402+
.clone()
403+
.import_named(scope.clone(), &format!("PublishLog({id})"));
404+
let errs = errs_trace
405+
.clone()
406+
.import_named(scope.clone(), &format!("PublishLogErr({id})"));
407+
registry.publish(id, &oks, &errs);
408+
}
409+
410+
#[cfg(test)]
411+
mod tests;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright Materialize, Inc. and contributors. All rights reserved.
2+
//
3+
// Use of this software is governed by the Business Source License
4+
// included in the LICENSE file.
5+
//
6+
// As of the Change Date specified in that file, in accordance with
7+
// the Business Source License, use of this software will be governed
8+
// by the Apache License, Version 2.0.
9+
10+
use differential_dataflow::input::Input;
11+
use mz_repr::{Diff, GlobalId, Row, Timestamp};
12+
use mz_row_spine::{RowRowBatcher, RowRowBuilder};
13+
use mz_timely_util::columnation::ColumnationChunker;
14+
15+
use crate::extensions::arrange::{KeyCollection, MzArrange};
16+
use crate::render::errors::DataflowErrorSer;
17+
use crate::server::ComputeRuntimeRole;
18+
use crate::sharing::ArrangementSharingRegistry;
19+
use crate::typedefs::{ErrBatcher, ErrBuilder, ErrSpine, RowRowSpine};
20+
21+
use super::publish_logging_index;
22+
23+
/// A logging/introspection index is a `RowRow` `oks` arrangement plus an (empty) `errs`
24+
/// arrangement, published into the sharing registry only by the maintenance runtime. Interactive
25+
/// and Solo must not publish: interactive reads maintenance's slot rather than clobbering it with
26+
/// its own empty copy, and Solo has no registry peer.
27+
///
28+
/// Builds real `RowRow`/`Err` arrangements (the exact types the logging path produces) and drives
29+
/// [`publish_logging_index`] for each role, asserting only maintenance ends up published.
30+
#[mz_ore::test]
31+
fn maintenance_publishes_logging_index_others_do_not() {
32+
for (role, expect_published) in [
33+
(ComputeRuntimeRole::Maintenance, true),
34+
(ComputeRuntimeRole::Interactive, false),
35+
(ComputeRuntimeRole::Solo, false),
36+
] {
37+
let id = GlobalId::System(1);
38+
let registry = ArrangementSharingRegistry::new();
39+
let registry_in = registry.clone();
40+
41+
timely::execute_directly(move |worker| {
42+
worker.dataflow::<Timestamp, _, _>(|scope| {
43+
let (mut oks_input, oks_collection) = scope.new_collection::<(Row, Row), Diff>();
44+
let oks = oks_collection.mz_arrange::<
45+
ColumnationChunker<_>,
46+
RowRowBatcher<_, _>,
47+
RowRowBuilder<_, _>,
48+
RowRowSpine<_, _>,
49+
>("test log oks");
50+
51+
let (mut errs_input, errs_collection) =
52+
scope.new_collection::<DataflowErrorSer, Diff>();
53+
let errs = KeyCollection::from(errs_collection).mz_arrange::<
54+
ColumnationChunker<_>,
55+
ErrBatcher<_, _>,
56+
ErrBuilder<_, _>,
57+
ErrSpine<_, _>,
58+
>("test log errs");
59+
60+
publish_logging_index(
61+
role,
62+
&registry_in,
63+
&scope.clone(),
64+
id,
65+
&oks.trace,
66+
&errs.trace,
67+
);
68+
69+
oks_input.advance_to(Timestamp::from(1_u64));
70+
oks_input.flush();
71+
errs_input.advance_to(Timestamp::from(1_u64));
72+
errs_input.flush();
73+
});
74+
});
75+
76+
assert_eq!(
77+
registry.handles(&id, 0).is_some(),
78+
expect_published,
79+
"role {role:?} publication mismatch"
80+
);
81+
}
82+
}

0 commit comments

Comments
 (0)