Skip to content

unspecified attributes on load are no longer defaulted - #2688

Open
DanielLacina wants to merge 54 commits into
db_v4from
graph_config
Open

unspecified attributes on load are no longer defaulted#2688
DanielLacina wants to merge 54 commits into
db_v4from
graph_config

Conversation

@DanielLacina

@DanielLacina DanielLacina commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

What changes were proposed in this pull request?

Synced config to disk on Graph.load, replaced Config with ConfigArgs so program knows what attributes are specified
to help get rid of unspecified attributes on load are no longer defaulted undesirable behavior.
Also, added tests to test graph config.

Why are the changes needed?

Unspecified attributes on load are defaulted. It automatically overrides previous config attributes if they're not specified which is undesirable. Config should be synced to disk after load.

Does this PR introduce any user-facing change? If yes is this documented?

Yes. Changing Config with ConfigArgs causes API change.

How was this patch tested?

Tested Graph.load and Graph behavior with config specified and used assertions to verify the config is in the right state.

Are there any further changes required?

Maybe

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Rust Benchmark'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 2.

Benchmark suite Current: 3e65345 Previous: 9823ef7 Ratio
lotr_graph/num_edges 3 ns/iter (± 0) 0 ns/iter (± 0) +∞
lotr_graph/num_nodes 4 ns/iter (± 0) 1 ns/iter (± 0) 4
lotr_graph/graph_latest 3 ns/iter (± 0) 0 ns/iter (± 0) +∞
lotr_graph_materialise/materialize 7634180 ns/iter (± 41997) 1564816 ns/iter (± 35303) 4.88
lotr_graph_window_100/num_nodes 11 ns/iter (± 0) 5 ns/iter (± 0) 2.20
lotr_graph_window_100_materialise/materialize 7620110 ns/iter (± 107762) 1669150 ns/iter (± 10700) 4.57
lotr_graph_window_10_materialise/materialize 2987981 ns/iter (± 10377) 971980 ns/iter (± 4278) 3.07
lotr_graph_subgraph_10pc/num_nodes 17 ns/iter (± 0) 4 ns/iter (± 0) 4.25
lotr_graph_subgraph_10pc_materialise/materialize 1996701 ns/iter (± 23596) 334634 ns/iter (± 1287) 5.97
lotr_graph_subgraph_10pc_windowed_materialise/materialize 1175941 ns/iter (± 17638) 230399 ns/iter (± 2617) 5.10
lotr_graph_window_50_layered/has_node_existing 361 ns/iter (± 19) 129 ns/iter (± 12) 2.80
lotr_graph_window_50_layered_materialise/materialize 25670356 ns/iter (± 160777) 3488825 ns/iter (± 24948) 7.36
lotr_graph_persistent_window_50_layered/num_edges_temporal 516766 ns/iter (± 5048) 192686 ns/iter (± 1569) 2.68
lotr_graph_persistent_window_50_layered/has_node_existing 386 ns/iter (± 320) 174 ns/iter (± 83) 2.22
lotr_graph_persistent_window_50_layered_materialise/materialize 43449211 ns/iter (± 187043) 5298035 ns/iter (± 147912) 8.20

This comment was automatically generated by workflow using github-action-benchmark.

@DanielLacina DanielLacina changed the title made it so config values not specified are not overriden by default v… unspecified attributes on load are no longer defaulted Jul 27, 2026
@DanielLacina
DanielLacina requested a review from fabubaker July 27, 2026 17:12

@fabubaker fabubaker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most methods in raphtory-graphql should just take Config, since the server will parse the args and convert them into a Config on startup. The server exclusively uses MaterializedGraph which is not exposed in the python API, so the constructors for MaterializedGraph should also just take a Config.

In core raphtory, the user-facing constructor methods in Graph and PersistentGraph are the only ones that need to take a ConfigArgs.

TLDR; ConfigArgs are only needed at the end-user boundary which are the constructor methods exposed through python and the ServerArgs on raphtory-graphql.

@@ -521,33 +522,36 @@ impl ValidWriteableGraphFolder {
pub fn write_graph_data(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to changes in this PR, but can you add a newline above to separate methods?

Comment thread db4-storage/src/persist/strategy.rs Outdated

fn new(config: Self::Config, graph_dir: Option<&Path>) -> Result<Self, StorageError>;

fn load(graph_dir: &Path) -> Result<Self, StorageError>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than associate type ConfigArgs with PersistenceStrategy, I think it makes sense to move it to ConfigOps.

Comment thread db4-storage/src/persist/config.rs Outdated
}

pub trait ConfigOps: Serialize + DeserializeOwned + Args + Sized {
type NewConfigArgs: ConfigArgsOps;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To follow up on #2688 (comment), you would change this to type ConfigArgs and use () for BaseConfig and WriteAndMergeConfigArgs for WriteAndMergeConfig.

Comment thread raphtory/src/db/api/storage/storage.rs Outdated
pub fn load_with_config(path: impl AsRef<Path>, config: Config) -> Result<Self, GraphError> {
pub fn load_with_config(
path: impl AsRef<Path>,
config_args: ConfigArgs,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like Storage to take an already built Config (we seem to be doing that with new anyways).

In graph.rs, have ConfigArgs get converted into a Config and pass it down to the relevant storage. This is the right boundary for the conversion to happen.

You'll have to change PersistenceStrategy::load_with_config to also take in a Config. I recommend creating a ConfigArgs::load_from_path method that internally calls update and combines the passed-in args + args on disk. The caller can then call into_config and pass the generate Config into Storage.

) -> Result<MaterializedGraph, ClientError> {
let encoded = self.receive_graph(path).await?;
url_decode_graph(encoded, Config::default()).map_err(ClientError::from)
url_decode_graph(encoded, ConfigArgs::default()).map_err(ClientError::from)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should take a Config.

Comment thread raphtory-graphql/src/model/mod.rs Outdated
let folder_clone = folder.clone();
let g: MaterializedGraph = blocking_compute(move || {
url_decode_graph_at(graph, folder_clone.graph_folder(), config)
url_decode_graph_at(graph, folder_clone.graph_folder(), config.into_args())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a Config.

let server = block_on(GraphServer::new(
work_dir,
app_config,
ConfigArgs::default(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a Config and ServerArgs.graph_config should be a ConfigArgs.

Comment thread raphtory-graphql/src/data.rs Outdated
blocking_compute(move || {
let (is_dirty, new_graph) = writeable_folder.write_graph_data(graph, config)?;
let (is_dirty, new_graph) =
writeable_folder.write_graph_data(graph, config.into_args())?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should just take Config.

Comment thread raphtory-graphql/src/data.rs Outdated
&cache,
create_index,
config,
config_args,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should just take Config.

Comment thread db4-storage/src/persist/config.rs Outdated
Comment on lines +18 to +28
fn max_node_page_len(&self) -> Option<u32>;

fn max_edge_page_len(&self) -> Option<u32>;

fn node_types(&self) -> &[String];

fn with_max_node_page_len(self, page_len: u32) -> Self;

fn with_max_edge_page_len(self, page_len: u32) -> Self;

fn with_node_types(&self, node_types: impl IntoIterator<Item = impl AsRef<str>>) -> Self;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need these methods, all you need in this trait is into_config and load_from_path.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could simplify it even further, all this needs is the update method to apply the config args to an existing config (where that may be loaded from disk or default) with signature (I would probably call this apply instead of update as well) fn apply(&self, config: &mut Self::Config)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with that, we can have a default implementation for into_config which is simply

fn into_config(self) -> Self::Config {
let mut config = Self::Config::default();
self.apply(&mut config);
config
}

Comment thread db4-storage/src/persist/config.rs Outdated
}

fn update(&mut self, new: Self);
fn update(&mut self, new_args: Self::ConfigArgs);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should move this update method into ConfigArgs.

Comment thread db4-storage/src/persist/config.rs Outdated
max_edge_page_len: Option<u32>,
}

impl ConfigArgsOps for BaseConfigArgs {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can remove this, we only need to implement ConfigArgsOps on WriteAndMergeConfigArgs.

Comment thread db4-storage/src/persist/config.rs Outdated
}
}

impl ConfigArgsOps for () {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think either this or the BaseConfigArgs will be needed if load_with_config ends up existing in the OS raphtory apis

Comment thread db4-storage/src/lib.rs Outdated
Comment thread db4-storage/src/persist/config.rs Outdated
}

pub trait ConfigOps: Serialize + DeserializeOwned + Args + Sized {
type ConfigArgs: ConfigArgsOps;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we move load_from_dir and save_to_dir to ConfigArgs, it can be responsible for the full lifecycle of loading/saving config from disk.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep those here (they should always return/save a full config, not a ConfigArgs), see my comment on ConfigArgs, it would only have a way to update an existing, config, nothing else.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

once that is the case, this probably doesn't actually need the associated type at all

Comment thread db4-storage/src/persist/config.rs Outdated
fn update(&mut self, new: Self);
fn update(&mut self, new_args: Self::ConfigArgs);

fn into_args(self) -> Self::ConfigArgs;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think most places that call into_args should just pass a Config anyways?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could then get rid of into_args.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, this shouldn't be needed

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main current use for this is in the graphql server, maybe the better answer for that would be for the server to store config args instead of config?

pub fn load_with_config(
path: &(impl GraphPaths + ?Sized),
config: Config,
config_args: ConfigArgs,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a Config but you'll run into problems below with Graph::load_with_config and PersistentGraph::load_with_config since those take ConfigArgs.

The way around this is to create a Storage object which takes Config and then convert that into a Graph or PersistentGraph.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree on this one, this is an end-user facing function and should take ConfigArgs to update only the desired fields of the original graph on disk. This is used in rust when loading a graph where you want to preserve the type of the graph (Event or Persistent).

@ljeub-pometry ljeub-pometry left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit more cleanup required

pub fn load_with_config(
path: &(impl GraphPaths + ?Sized),
config: Config,
config_args: ConfigArgs,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree on this one, this is an end-user facing function and should take ConfigArgs to update only the desired fields of the original graph on disk. This is used in rust when loading a graph where you want to preserve the type of the graph (Event or Persistent).

Comment thread db4-storage/src/persist/config.rs Outdated
Comment on lines +18 to +28
fn max_node_page_len(&self) -> Option<u32>;

fn max_edge_page_len(&self) -> Option<u32>;

fn node_types(&self) -> &[String];

fn with_max_node_page_len(self, page_len: u32) -> Self;

fn with_max_edge_page_len(self, page_len: u32) -> Self;

fn with_node_types(&self, node_types: impl IntoIterator<Item = impl AsRef<str>>) -> Self;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could simplify it even further, all this needs is the update method to apply the config args to an existing config (where that may be loaded from disk or default) with signature (I would probably call this apply instead of update as well) fn apply(&self, config: &mut Self::Config)

Comment thread db4-storage/src/persist/config.rs Outdated
}

pub trait ConfigOps: Serialize + DeserializeOwned + Args + Sized {
type ConfigArgs: ConfigArgsOps;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep those here (they should always return/save a full config, not a ConfigArgs), see my comment on ConfigArgs, it would only have a way to update an existing, config, nothing else.

Comment thread db4-storage/src/persist/config.rs Outdated
fn update(&mut self, new: Self);
fn update(&mut self, new_args: Self::ConfigArgs);

fn into_args(self) -> Self::ConfigArgs;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, this shouldn't be needed

Comment thread db4-storage/src/persist/config.rs Outdated
}

pub trait ConfigOps: Serialize + DeserializeOwned + Args + Sized {
type ConfigArgs: ConfigArgsOps;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

once that is the case, this probably doesn't actually need the associated type at all

Comment thread db4-storage/src/persist/config.rs Outdated
}
}

impl ConfigArgsOps for () {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think either this or the BaseConfigArgs will be needed if load_with_config ends up existing in the OS raphtory apis

Comment thread raphtory-graphql/src/paths.rs Outdated
} else {
let new_graph = graph.materialize_at_with_config(self.graph_folder(), config)?;
let new_graph =
graph.materialize_at_with_config(self.graph_folder(), config.into_args())?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting one, one possibility is that the server should actually store a ConfigArgs so it can only override some of the fields and leave everything else as it was on the original graph. The other thing is that materialize_at_with_config should actually be able to override the page sizes as it is creating a new graph. If we want this to be as type safe as possible, we would need separate args for this case and the load case where the page sizes cannot be overridden. Alternatively, we could have separate apply methods for the two scenarios and just ignore/error at runtime for the page sizes when they cannot be changed.

Comment thread raphtory/src/serialise/parquet.rs Outdated
path: impl AsRef<Path>,
path_for_decoded_graph: Option<&Path>,
config: Config,
config_args: ConfigArgs,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will mean that all the decode methods can just take a config.

Comment thread db4-storage/src/persist/config.rs Outdated
Comment on lines +18 to +28
fn max_node_page_len(&self) -> Option<u32>;

fn max_edge_page_len(&self) -> Option<u32>;

fn node_types(&self) -> &[String];

fn with_max_node_page_len(self, page_len: u32) -> Self;

fn with_max_edge_page_len(self, page_len: u32) -> Self;

fn with_node_types(&self, node_types: impl IntoIterator<Item = impl AsRef<str>>) -> Self;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with that, we can have a default implementation for into_config which is simply

fn into_config(self) -> Self::Config {
let mut config = Self::Config::default();
self.apply(&mut config);
config
}

Comment thread db4-storage/src/persist/config.rs Outdated
fn update(&mut self, new: Self);
fn update(&mut self, new_args: Self::ConfigArgs);

fn into_args(self) -> Self::ConfigArgs;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main current use for this is in the graphql server, maybe the better answer for that would be for the server to store config args instead of config?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants