Skip to content
This repository was archived by the owner on Feb 6, 2026. It is now read-only.

Commit d4a78e7

Browse files
jhelwignickgerace
authored andcommitted
Implement DefinitionChecksum infrastructure and MaterializedView rebuild system
Establishes comprehensive type schema change detection system enabling selective MaterializedView rebuilds when only type definitions change, avoiding expensive full data rebuilds. **Core Infrastructure**: - Add DefinitionChecksum trait with static compilation-time checksum resolution - Create comprehensive DefinitionChecksum implementations for all MV-used types - Build DefinitionChecksum derive macro supporting recursive type handling - Add RebuildChangedDefinitionsRequest API type with versioned structure **MaterializedView Integration**: - Extend MaterializedView derive macro to generate definition checksums - Update MV infrastructure to detect and rebuild only definition-changed views - Implement compressed request handling for selective rebuild operations **Code Organization**: - Refactor frigg into deployment, kv_history, and workspace modules - Split si_frontend_mv_types::index into change_set and deployment submodules **Compression Algorithm Enhancements**: - Support RebuildChangedDefinitions requests in compression pipeline - Maintain contiguous update sequences while filtering definition rebuilds - Prioritize full rebuilds when mixed with selective definition rebuilds The system now distinguishes between data changes requiring full rebuilds and schema changes needing only definition updates, significantly improving rebuild efficiency for type system modifications. The change set/workspace level MvIndex will be considered "missing" until it has been upgraded to the V2 format, which will trigger full rebuilds for the change sets in question. The deployment level MvIndex will continue to ignore the checksum information for now, until we have had time to make sure it has been upgraded to the V2 format. Co-authored-by: Jacob Helwig <jacob@technosorcery.net> Signed-off-by: Nick Gerace <nick@systeminit.com>
1 parent b2f6fba commit d4a78e7

45 files changed

Lines changed: 4690 additions & 2434 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

lib/edda-core/src/api_types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod new_change_set_request;
2+
pub mod rebuild_changed_definitions_request;
23
pub mod rebuild_request;
34
pub mod update_request;
45

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
use acceptable::{
2+
AllVersions,
3+
Container,
4+
CurrentContainer,
5+
IntoContainer,
6+
UpgradeError,
7+
};
8+
use serde::Deserialize;
9+
10+
mod v1;
11+
12+
pub use self::v1::RebuildChangedDefinitionsRequestV1;
13+
14+
#[remain::sorted]
15+
#[derive(AllVersions, CurrentContainer, Clone, Debug, Deserialize, Eq, PartialEq)]
16+
#[serde(rename_all = "camelCase")]
17+
pub enum RebuildChangedDefinitionsRequestAllVersions {
18+
#[acceptable(current)]
19+
V1(RebuildChangedDefinitionsRequestV1),
20+
}
21+
22+
impl IntoContainer for RebuildChangedDefinitionsRequestAllVersions {
23+
type Container = RebuildChangedDefinitionsRequest;
24+
25+
fn into_container(self) -> Result<Self::Container, UpgradeError> {
26+
match self {
27+
Self::V1(inner) => Ok(Self::Container::new(inner)),
28+
}
29+
}
30+
}
31+
32+
#[cfg(test)]
33+
mod test {
34+
use std::{
35+
error::Error,
36+
fmt,
37+
fs::File,
38+
io::{
39+
self,
40+
BufRead as _,
41+
BufReader,
42+
Read as _,
43+
},
44+
path::Path,
45+
};
46+
47+
use serde::{
48+
Serialize,
49+
de::DeserializeOwned,
50+
};
51+
52+
/// Tests that a versioned object will always serialize to the same representation, no matter
53+
/// what future versions or changes to the object.
54+
///
55+
/// NOTE: It is imperative that incremental refactorings do not lead to a version-incompatible
56+
/// or serialize-incompatible change. If a test fails and it's because there is a diff to the
57+
/// commiteed `.snap` file, this should be considered a failed refactoring. The remediation is
58+
/// to *not* update the `.snap` file but rather to fix the code so that the `.snap` format is
59+
/// 100% preserved.
60+
pub fn assert_serialize(name: &str, version: u64, serialize: impl Serialize) {
61+
insta::with_settings!({
62+
snapshot_path => format!("rebuild_changed_definitions_request/snapshots-v{version}"),
63+
prepend_module_to_snapshot => false,
64+
omit_expression => true,
65+
description => concat!(
66+
"\n",
67+
"\n",
68+
"!!!\n",
69+
"!!! System Initiative Developers:\n",
70+
"!!!\n",
71+
"!!! IMPORTANT:\n",
72+
"!!!\n",
73+
"!!! The contents of this snapshot should *never* be modified as it\n",
74+
"!!! represents the serialization of a versioned Rust type. If a tests fails\n",
75+
"!!! with this warning, then something about a Rust type has changed\n",
76+
"!!! the wire serialization of this type and would represent a potential\n",
77+
"!!! production outage or data corruption.\n",
78+
"!!!\n",
79+
"!!! Consider this an erroneous behavioral change of the Rust code and *not*\n",
80+
"!!! an out-of-date snapshot or fixture!\n",
81+
"!!!\n",
82+
"\n",
83+
"\n",
84+
),
85+
}, {
86+
insta::assert_json_snapshot!(name, serialize);
87+
});
88+
}
89+
90+
pub fn assert_deserialize<T>(snapshot_name: &str, version: u64, expected: T)
91+
where
92+
T: fmt::Debug + DeserializeOwned + PartialEq,
93+
{
94+
let actual: T = read_from_snapshot(
95+
snapshot_name,
96+
&format!("rebuild_changed_definitions_request/snapshots-v{version}"),
97+
)
98+
.expect("failed to deserialize from snapshot");
99+
100+
assert_eq!(actual, expected);
101+
}
102+
103+
fn read_from_snapshot<T>(name: &str, path: &str) -> Result<T, Box<dyn Error>>
104+
where
105+
T: fmt::Debug + DeserializeOwned + PartialEq,
106+
{
107+
let glob = format!("{path}/{name}.snap");
108+
109+
let mut maybe_obj_result = None;
110+
insta::glob!(&glob, |path| {
111+
let bytes = read_snapshot_content(path).unwrap();
112+
113+
maybe_obj_result = Some(serde_json::from_slice(&bytes).map_err(Into::into));
114+
});
115+
116+
match maybe_obj_result {
117+
Some(object_result) => object_result,
118+
None => Err(Box::new(io::Error::other(format!(
119+
"snapshot not found: {glob}"
120+
)))),
121+
}
122+
}
123+
124+
// Implementation is adapted from the [`insta::Snapshot::from_file`] function.
125+
//
126+
// See: <https://github.com/mitsuhiko/insta/blob/62bb0a3/insta/src/snapshot.rs#L339-L421>
127+
fn read_snapshot_content(path: &Path) -> Result<Vec<u8>, Box<dyn Error>> {
128+
let mut f = BufReader::new(File::open(path)?);
129+
130+
// Skip through the snapshot metadata, that being the first YAML document, where YAML
131+
// documents are delimited with a `"---"` line
132+
{
133+
let mut buf = String::new();
134+
f.read_line(&mut buf)?;
135+
if buf.trim_end() == "---" {
136+
loop {
137+
let read = f.read_line(&mut buf)?;
138+
if read == 0 {
139+
break;
140+
}
141+
if buf[buf.len() - read..].trim_end() == "---" {
142+
break;
143+
}
144+
}
145+
}
146+
}
147+
148+
let mut buf = Vec::new();
149+
f.read_to_end(&mut buf)?;
150+
151+
Ok(buf)
152+
}
153+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
source: lib/edda-core/src/api_types/rebuild_changed_definitions_request.rs
3+
description: "\n\n!!!\n!!! System Initiative Developers:\n!!!\n!!! IMPORTANT:\n!!!\n!!! The contents of this snapshot should *never* be modified as it\n!!! represents the serialization of a versioned Rust type. If a tests fails\n!!! with this warning, then something about a Rust type has changed\n!!! the wire serialization of this type and would represent a potential\n!!! production outage or data corruption.\n!!!\n!!! Consider this an erroneous behavioral change of the Rust code and *not*\n!!! an out-of-date snapshot or fixture!\n!!!\n\n\n"
4+
---
5+
{
6+
"id": "01JQCVVDHXYX6S9YCV773R13MG"
7+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use acceptable::{
2+
RequestId,
3+
Versioned,
4+
};
5+
use serde::{
6+
Deserialize,
7+
Serialize,
8+
};
9+
10+
#[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq, Versioned)]
11+
#[serde(rename_all = "camelCase")]
12+
#[acceptable(version = 1)]
13+
// NOTE: **do not modify this datatype--it represents a historically stable, versioned request**
14+
pub struct RebuildChangedDefinitionsRequestV1 {
15+
pub id: RequestId,
16+
}
17+
18+
#[cfg(test)]
19+
mod tests {
20+
use super::{
21+
super::test::*,
22+
*,
23+
};
24+
25+
const SNAPSHOT_NAME: &str = "serialized";
26+
const VERSION: u64 = 1;
27+
28+
fn msg() -> RebuildChangedDefinitionsRequestV1 {
29+
RebuildChangedDefinitionsRequestV1 {
30+
id: "01JQCVVDHXYX6S9YCV773R13MG".parse().unwrap(),
31+
}
32+
}
33+
34+
#[test]
35+
fn serialize() {
36+
assert_serialize(SNAPSHOT_NAME, VERSION, msg());
37+
}
38+
39+
#[test]
40+
fn deserialize() {
41+
assert_deserialize(SNAPSHOT_NAME, VERSION, msg());
42+
}
43+
}

0 commit comments

Comments
 (0)