Skip to content

Commit 6d76ee5

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 registers its id as an alias of the arrangement's existing publication point, which leaves the re-export's dataflow without operators, as it is on a runtime that does not publish. A reader that bound the re-export's id before the render holds its own unbacked point, which only a publisher into it can back, so that case re-imports the shared traces and publishes them under the new id, and logs the imported errors because `mz_compute_error_counts` forwards a dependency's counts only to a re-export whose dataflow has no operators. 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. An alias shares its target's frontiers while the target lives: the alias dataflow imports the target, so the controller never advances the target's `since` past an alias's, and the target's frontier bounds every reader of the shared point. Once the target drops, the meet of the remaining aliases' frontiers governs the point, since the shared trace then compacts to exactly that meet. Seal notifications fan out from the target to its aliases, since a reader waits under the id it imported. `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 6d76ee5

10 files changed

Lines changed: 559 additions & 24 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)