Skip to content

Commit e4f5289

Browse files
committed
Merge branch 'stack/native-766' into stack/native-761
2 parents 8e197aa + 4530670 commit e4f5289

2 files changed

Lines changed: 35 additions & 7 deletions

File tree

‎data_plane/tests/backend_process_e2e.rs‎

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -726,8 +726,9 @@ async fn production_control_plane_to_data_plane_otlp_to_promql() {
726726
.await
727727
.unwrap();
728728
assert_eq!(first_scalar(&still_warm), Some(value));
729-
// Retry the staged successor while queries are in flight. Each
730-
// request must retain a complete active snapshot through cutover.
729+
// Retry while queries are in flight. Old-generation reads may finish,
730+
// but the successor must stay cold until its own output is published.
731+
// Reusing the old payload would violate StoredOutputReference identity.
731732
request["activation_unix_ms"] = (std::time::SystemTime::now()
732733
.duration_since(std::time::UNIX_EPOCH)
733734
.unwrap()
@@ -750,11 +751,12 @@ async fn production_control_plane_to_data_plane_otlp_to_promql() {
750751
.json()
751752
.await
752753
.unwrap();
753-
assert_eq!(
754-
first_scalar(&response),
755-
Some(value),
756-
"torn serving snapshot: {response}"
757-
);
754+
if let Some(actual) = first_scalar(&response) {
755+
assert_eq!(actual, value, "incorrect old-generation result: {response}");
756+
} else {
757+
assert_eq!(response["status"], "error", "{response}");
758+
assert_eq!(response["error"], "No result for query", "{response}");
759+
}
758760
tokio::time::sleep(Duration::from_millis(10)).await;
759761
}
760762
});
@@ -777,6 +779,27 @@ async fn production_control_plane_to_data_plane_otlp_to_promql() {
777779
assert_eq!(activated["plan_version"], 2);
778780
let (successor, _collector_socket) = collector.await.unwrap();
779781
readers.await.unwrap();
782+
let cold_successor: serde_json::Value = client
783+
.get(format!("{data_base}/api/v1/query"))
784+
.query(&[
785+
("query", query.to_string()),
786+
("time", (window_end_ms as f64 / 1000.0).to_string()),
787+
])
788+
.send()
789+
.await
790+
.unwrap()
791+
.json()
792+
.await
793+
.unwrap();
794+
assert_eq!(cold_successor["status"], "error", "{cold_successor}");
795+
assert_eq!(
796+
cold_successor["error"], "No result for query",
797+
"{cold_successor}"
798+
);
799+
assert!(
800+
first_scalar(&cold_successor).is_none(),
801+
"successor reused old state"
802+
);
780803
let old_frame = client
781804
.post(format!("http://{otlp_http}/v1/metrics"))
782805
.header("content-type", "application/x-protobuf")

‎docs/design_docs/precompute-dag-execution.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,11 @@ or isolates incompatible old worker state before new work uses a different
398398
partition assignment. Changing worker count requires coordinated reassignment;
399399
changing a hash modulus while state is active is insufficient.
400400

401+
A newly activated version is cold until its own stored outputs are available.
402+
Queries must not substitute the previous version's payload to hide that gap.
403+
Continuous warm service across activation requires an explicit readiness or
404+
state-migration protocol; atomic plan publication alone does not provide it.
405+
401406
Recovery validates persisted record identities, definitions, formats and
402407
coverage before exposing them to readers. A matching definition alone does not
403408
authorize reuse across plan versions. Runtime caches and routing indexes are

0 commit comments

Comments
 (0)