Skip to content

DiffConfig: replace serde-based impl with explicit impl#7294

Merged
xzfc merged 3 commits intodevfrom
diffconfig-macro
Sep 29, 2025
Merged

DiffConfig: replace serde-based impl with explicit impl#7294
xzfc merged 3 commits intodevfrom
diffconfig-macro

Conversation

@xzfc
Copy link
Member

@xzfc xzfc commented Sep 23, 2025

This PR replaces serde-based DiffConfig implementation with explicit implementation. Depends on #7293.

Issue

In dev, we have this type coercion that copies fields from *ConfigDiff structs into *Config structs:

/// Hacky way to update configuration structures with diff-updates.
/// Intended to only be used in non critical for speed places.
/// TODO: replace with proc macro
pub fn update_config<T: DeserializeOwned + Serialize, Y: DeserializeOwned + Serialize + Merge>(
    config: &T,
    update: Y,
) -> CollectionResult<T> {
    let mut config_values = serde_json::to_value(config)?;
    let diff_values = serde_json::to_value(&update)?;
    merge_level_0(&mut config_values, diff_values);
    let res = serde_json::from_value(config_values)?;
    Ok(res)
}

It's hacky indeed. It's error-prone: missing/wrong field names not caught by a compiler). And it's makes the code hard to explore: rust-analyzer "find usages" feature will newer help you find out that HnswConfig::copy_vectors can be populated through HnswConfigDiff::copy_vectors.

Solution

This PR replaces this coercion-via-json with a few hand-rolled implementations of impl DiffConfig and impl From. Additionally, DiffConfig trait methods are infallible now (as there no JSON serialization/serialization errors possible).

Discarded approaches

  • Q: What about generating *ConfigDiff from *Config definition?
    A: I decided to keep them explicitly hand-rolled as these structures have some subtle differences, e.g., see the following diff. While it's possible encode these differences via proc macro and attributes, I find it less maintainable. And probably macro_rules give better LSP experience than proc macros.

    -pub struct OptimizersConfig {
    +pub struct OptimizersConfigDiff {
         …
         /// Max number of threads (jobs) for running optimizations per shard.
         /// Note: each optimization job will also use `max_indexing_threads` threads by itself for index building.
    -    /// If "auto" - have no limit and choose dynamically to saturate CPU.
    +    /// If null - have no limit and choose dynamically to saturate CPU.
         /// If 0 - no optimization threads, optimizations will be disabled.
         #[serde(default)]
    -    pub max_optimization_threads: Option<usize>,
    +    pub max_optimization_threads: Option<MaxOptimizationThreads>,
     }
  • Previous version of this PR were using macro_rules.

    old description

    This PR replaces this coercion-via-json with a impl_diff_config that macro that produces the conversion boilerplate. Any missing field will result in a compile error; and "find usages" on HnswConfig fields will find this definition. Additionally, DiffConfig trait methods are infallible now (as there no JSON serialization/serialization errors possible). Also, the macro syntax mimics real Rust syntax, thus rustfmt will format it correctly.

    impl_diff_config!(
        {
            impl DiffConfig<HnswConfigDiff> for HnswConfig {}
            impl DiffConfig<HnswConfigDiff> for HnswConfigDiff {}
            impl From<HnswConfig> for HnswConfigDiff {}
        },
        common_fields(
            m,
            ef_construct,
            full_scan_threshold,
            max_indexing_threads,
            on_disk,
            payload_m,
            copy_vectors,
        ),
        config_only_fields()
    );

@xzfc xzfc requested review from generall and timvisee September 23, 2025 10:30
@xzfc
Copy link
Member Author

xzfc commented Sep 23, 2025

@coderabbitai review

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
lib/collection/src/operations/types.rs (1)

1539-1540: Empty-diff check: semantics shift OK; minor readability nit.

This directly treats None or Some(default) as empty, matching the new macro-based Diff semantics. Optionally, this reads a bit clearer with matches!.

Please confirm HnswConfigDiff derives Default + PartialEq so Some(default()) reliably represents “no fields set”.

-fn is_hnsw_diff_empty(hnsw_config: &Option<HnswConfigDiff>) -> bool {
-    hnsw_config.is_none() || *hnsw_config == Some(HnswConfigDiff::default())
-}
+fn is_hnsw_diff_empty(hnsw_config: &Option<HnswConfigDiff>) -> bool {
+    matches!(hnsw_config.as_ref(), None | Some(d) if d == &HnswConfigDiff::default())
+}
lib/collection/src/collection/collection_ops.rs (1)

190-190: Borrowed update for strict mode LGTM.

Naming nit: strict_mode_diff is a full StrictModeConfig, not a diff type, but that’s existing and non-blocking.

lib/collection/src/config.rs (1)

450-464: Avoid cloning VectorsConfigDiff entries (optional).

You can iterate by reference and clone only when needed, reducing intermediate allocations.

Apply this refactor:

-        for (vector_name, update_params) in update_vectors_diff.0.iter() {
-            let vector_params = self.get_vector_params_mut(vector_name)?;
-            let VectorParamsDiff {
-                hnsw_config,
-                quantization_config,
-                on_disk,
-            } = update_params.clone();
+        for (vector_name, update_params) in &update_vectors_diff.0 {
+            let vector_params = self.get_vector_params_mut(vector_name)?;
+            let VectorParamsDiff {
+                hnsw_config,
+                quantization_config,
+                on_disk,
+            } = update_params;
 
-            if let Some(hnsw_diff) = hnsw_config {
-                if let Some(existing_hnsw) = &vector_params.hnsw_config {
-                    vector_params.hnsw_config = Some(existing_hnsw.update(&hnsw_diff));
-                } else {
-                    vector_params.hnsw_config = Some(hnsw_diff);
-                }
+            if let Some(hnsw_diff) = hnsw_config.as_ref() {
+                vector_params.hnsw_config = Some(
+                    vector_params
+                        .hnsw_config
+                        .as_ref()
+                        .map(|existing| existing.update(hnsw_diff))
+                        .unwrap_or_else(|| hnsw_diff.clone()),
+                );
             }
 
-            if let Some(quantization_diff) = quantization_config {
+            if let Some(quantization_diff) = quantization_config.as_ref() {
                 vector_params.quantization_config = match quantization_diff.clone() {
                     QuantizationConfigDiff::Scalar(scalar) => {
                         Some(QuantizationConfig::Scalar(scalar))
                     }
                     QuantizationConfigDiff::Product(product) => {
                         Some(QuantizationConfig::Product(product))
                     }
                     QuantizationConfigDiff::Binary(binary) => {
                         Some(QuantizationConfig::Binary(binary))
                     }
                     QuantizationConfigDiff::Disabled(_) => None,
                 }
             }
 
-            if let Some(on_disk) = on_disk {
+            if let Some(on_disk) = on_disk.as_ref() {
                 vector_params.on_disk = Some(on_disk);
             }
         }
lib/collection/src/operations/config_diff.rs (1)

392-397: Optional fields cannot be explicitly cleared via diff

impl DiffConfigMerge<T> for Option<T> treats None in diff as “no-op”. There’s no way to set a config field of type Option<T> to None through a diff. If clearing is required, consider a wrapper enum (e.g., Set(T)/Clear) or Option<Option<T>> in the diff type.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 429e180 and fd2cfdf.

📒 Files selected for processing (8)
  • lib/collection/src/collection/collection_ops.rs (4 hunks)
  • lib/collection/src/collection/mod.rs (3 hunks)
  • lib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rs (2 hunks)
  • lib/collection/src/collection_manager/optimizers/segment_optimizer.rs (1 hunks)
  • lib/collection/src/config.rs (2 hunks)
  • lib/collection/src/operations/config_diff.rs (11 hunks)
  • lib/collection/src/operations/types.rs (2 hunks)
  • lib/storage/src/content_manager/toc/create_collection.rs (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: rust-tests (windows-latest)
  • GitHub Check: rust-tests (ubuntu-latest)
  • GitHub Check: rust-tests (macos-latest)
  • GitHub Check: integration-tests-consensus
  • GitHub Check: rust-tests-no-rocksdb (ubuntu-latest)
  • GitHub Check: e2e-tests
  • GitHub Check: test-consistency
  • GitHub Check: test-shard-snapshot-api-s3-minio
  • GitHub Check: integration-tests
  • GitHub Check: storage-compat-test
  • GitHub Check: test-consensus-compose
  • GitHub Check: lint
🔇 Additional comments (19)
lib/collection/src/operations/types.rs (1)

52-52: Import tweak looks correct.

Localizing ClockTag import removes the unused config_diff import coupling.

lib/collection/src/collection_manager/optimizers/segment_optimizer.rs (1)

251-251: API alignment with borrowed diffs.

Switch to update_opt(param_hnsw.as_ref()) correctly follows the new DiffConfig API and avoids needless cloning.

lib/collection/src/collection/collection_ops.rs (3)

37-37: Borrowed-diff update for params LGTM.

Infallible update(&params_diff) is consistent with the macro-generated DiffConfig.


55-55: Borrowed-diff update for HNSW config LGTM.


107-107: Borrowed-diff update for optimizers LGTM.

lib/storage/src/content_manager/toc/create_collection.rs (4)

167-167: WAL config: update_opt(...as_ref()) LGTM.


169-173: Optimizers config: update_opt(...as_ref()) LGTM.


174-177: HNSW config: update_opt(...as_ref()) LGTM.


196-197: Strict mode: borrowed update LGTM.

Confirm that StrictModeConfig::update(&StrictModeConfig) is intended (no separate Diff type).

lib/collection/src/config.rs (1)

285-288: Validation path uses borrowed per‑vector HNSW diff correctly.

update_opt(vector_config.hnsw_config.as_ref()) matches the new API and preserves behavior.

lib/collection/src/collection/mod.rs (3)

135-136: Borrowed-diff optimizer overwrite in new() LGTM.


256-257: Borrowed-diff optimizer overwrite in load() LGTM.


848-849: Borrowed-diff in effective_optimizers_config() LGTM.

lib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rs (2)

119-121: Good switch to borrowed diff with as_ref()

Passing Option<&HnswConfigDiff> into update_opt is correct and aligns with the new API.


582-582: Tests updated to new update(&Diff) signature

Using hnsw_config_collection.update(&hnsw_config_vectorN) is correct with the borrow-based API.

Also applies to: 587-587

lib/collection/src/operations/config_diff.rs (4)

18-31: Borrow-based DiffConfig API looks good

Trait shape and update_opt default are sound. Call sites can now pass &Diff or Option<&Diff> without allocations.


326-352: Confirm StrictModeConfig is intended as its own Diff type

This macro arm implements DiffConfig<StrictModeConfig> for StrictModeConfig. This works if all listed fields are already Option<...> (diff semantics). If some are plain values, the merge semantics will treat diff.$field as a plain value, not an Option, which does not match DiffConfigMerge’s expected Option<T>. Please confirm the field shapes or adjust to a dedicated StrictModeConfigDiff.


354-378: Manual From<OptimizersConfig> mapping looks correct

Covers special handling for max_optimization_threads and deprecated memmap_threshold. Matches the tests.


461-466: Tests correctly migrated to borrowed-diff API

Using update(&diff) across types looks consistent and validates the new merge behavior, including MaxOptimizationThreads.

Also applies to: 472-474, 490-492, 513-517, 523-525

Comment on lines +221 to +231
impl DiffConfig<$TDiff> for $TSelf {
fn update(&self, diff: &$TDiff) -> Self {
let $TDiff {
$($field: _,)*
} = diff; // Make sure that we did not miss any field
$TSelf {
$($field: DiffConfigMerge::merge(&self.$field, &diff.$field),)*
$($ignored_field: self.$ignored_field.clone(),)*
}
}
}

This comment was marked as off-topic.

Comment on lines +247 to +257
impl From<$TSelf> for $TDiff {
fn from(config: $TSelf) -> Self {
let $TSelf {
$($field: _,)*
$($ignored_field: _,)*
} = config; // Make sure that we did not miss any field
$TDiff {
$($field: Option::from(config.$field),)*
}
}
}

This comment was marked as off-topic.

@qdrant qdrant deleted a comment from coderabbitai bot Sep 23, 2025
@qdrant qdrant deleted a comment from coderabbitai bot Sep 23, 2025
Copy link
Member

@timvisee timvisee left a comment

Choose a reason for hiding this comment

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

🔥 I like this simply because it makes mistakes harder.

I'm also interested in Andrey's opinion on this one.

It is crazy to me that rust-analyzer still resolves all field names correctly, even within the macro invocation itself. 👏

@generall
Copy link
Member

I understand the benefit of having explicit approach with compile-time errors if we forget something.

But why can't we just write the code instead of macro? Please don't tell me that macros are simpler.

@xzfc
Copy link
Member Author

xzfc commented Sep 23, 2025

But why can't we just write the code instead of macro?

Sure.

Copy link
Member

@generall generall left a comment

Choose a reason for hiding this comment

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

It is even less boilerplate than I expected, thanks!

@xzfc xzfc changed the title Replace serde-based DiffConfig implementation with macro DiffConfig: replace serde-based impl with explicit impl Sep 23, 2025
@timvisee
Copy link
Member

I liked the macro more 🥲

But let's merge it like this!

@xzfc xzfc force-pushed the configuration_status branch from 879b85f to 47cf1fc Compare September 25, 2025 01:11
@xzfc xzfc force-pushed the diffconfig-macro branch 2 times, most recently from d2cfcd6 to 95979ee Compare September 25, 2025 09:05
@xzfc xzfc force-pushed the configuration_status branch 5 times, most recently from 47da93e to 0693595 Compare September 29, 2025 17:55
Base automatically changed from configuration_status to dev September 29, 2025 18:17
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 29, 2025

📝 Walkthrough

Walkthrough

This PR removes the merge crate dependency from workspace crates and refactors config-diff handling to use explicit, reference-based update semantics. The DiffConfig trait is redesigned: update now takes &Diff and returns Self (no Result), update_opt takes Option<&Diff>, and Merge-derived behavior and helpers are removed. Call sites are updated accordingly across collection, optimizers, and storage code. Several structs drop Merge derives; tests and logic now pass references. StrictModeSparse gains a new optional max_length field with validation. Error propagation from config updates is eliminated where update now returns plain values.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • timvisee
  • agourlay

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "DiffConfig: replace serde-based impl with explicit impl" accurately describes the main change in the changeset. The PR removes the serde/JSON-based configuration merging approach (which used serialization, the merge crate, and deserialization) and replaces it with explicit implementations of the DiffConfig trait for various config types. The title clearly identifies the component being modified (DiffConfig) and the nature of the change (replacing serde-based implementation with explicit implementation), which aligns with the substantial changes across multiple files removing Merge trait dependencies, changing update method signatures from taking owned values to references, and adding explicit DiffConfig implementations.
Description Check ✅ Passed The pull request description is clearly related to the changeset and provides substantial detail about the motivation and implementation. The description explains the problem with the existing serde-based approach, describes the solution of using hand-rolled implementations, and discusses discarded alternatives including proc macros and macro_rules approaches. The description directly addresses the changes visible in the diff, including the removal of the merge crate dependency, the change to DiffConfig trait signatures to use references and become infallible, and the addition of explicit implementations for various config types.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch diffconfig-macro

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
lib/collection/src/collection/mod.rs (1)

844-852: LGTM! Consider simplifying return type in future refactor.

The update call correctly passes &optimizers_overwrite by reference and wraps the infallible result in Ok(), consistent with the new DiffConfig::update signature.

Since update is now infallible, this function could potentially return OptimizersConfig directly instead of CollectionResult<OptimizersConfig> to better reflect that no error can occur. However, keeping the Result type may be intentional for API consistency or future extensibility.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 780d406 and ee24c30.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • Cargo.toml (0 hunks)
  • lib/collection/Cargo.toml (0 hunks)
  • lib/collection/src/collection/collection_ops.rs (4 hunks)
  • lib/collection/src/collection/mod.rs (3 hunks)
  • lib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rs (2 hunks)
  • lib/collection/src/collection_manager/optimizers/segment_optimizer.rs (1 hunks)
  • lib/collection/src/config.rs (2 hunks)
  • lib/collection/src/operations/config_diff.rs (10 hunks)
  • lib/collection/src/operations/types.rs (3 hunks)
  • lib/segment/Cargo.toml (0 hunks)
  • lib/segment/src/types.rs (3 hunks)
  • lib/storage/src/content_manager/toc/create_collection.rs (2 hunks)
💤 Files with no reviewable changes (3)
  • lib/collection/Cargo.toml
  • Cargo.toml
  • lib/segment/Cargo.toml
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (.github/review-rules.md)

**/*.rs: Prefer explicit SomeType::from(x) over implicit x.into() in Rust code
Do not use transmute_from_u8, transmute_to_u8, transmute_from_u8_to_slice, transmute_from_u8_to_mut_slice, transmute_to_u8_slice in new code; use bytemuck or zerocopy instead

Files:

  • lib/collection/src/collection/collection_ops.rs
  • lib/collection/src/config.rs
  • lib/collection/src/collection/mod.rs
  • lib/collection/src/collection_manager/optimizers/segment_optimizer.rs
  • lib/storage/src/content_manager/toc/create_collection.rs
  • lib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rs
  • lib/segment/src/types.rs
  • lib/collection/src/operations/types.rs
  • lib/collection/src/operations/config_diff.rs
**/src/**/*.rs

📄 CodeRabbit inference engine (.github/review-rules.md)

**/src/**/*.rs: Prefer exhaustive match arms over a catch-all _ arm to avoid missing new enum variants (except in tests/benchmarks or when provably safe)
Prefer explicit field ignoring with : _ over .. in struct patterns (except in tests/benchmarks or when provably safe)

Files:

  • lib/collection/src/collection/collection_ops.rs
  • lib/collection/src/config.rs
  • lib/collection/src/collection/mod.rs
  • lib/collection/src/collection_manager/optimizers/segment_optimizer.rs
  • lib/storage/src/content_manager/toc/create_collection.rs
  • lib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rs
  • lib/segment/src/types.rs
  • lib/collection/src/operations/types.rs
  • lib/collection/src/operations/config_diff.rs
🧬 Code graph analysis (2)
lib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rs (1)
lib/collection/src/operations/config_diff.rs (7)
  • update (23-23)
  • update (207-227)
  • update (231-251)
  • update (255-279)
  • update (283-295)
  • update (299-318)
  • update (322-376)
lib/collection/src/operations/config_diff.rs (2)
lib/collection/src/config.rs (1)
  • from (52-63)
lib/storage/src/content_manager/collection_meta_ops.rs (4)
  • from (85-87)
  • from (91-93)
  • from (97-99)
  • from (431-472)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: e2e-tests
  • GitHub Check: test-consistency
  • GitHub Check: test-shard-snapshot-api-s3-minio
  • GitHub Check: test-consensus-compose
  • GitHub Check: integration-tests-consensus
  • GitHub Check: integration-tests
  • GitHub Check: lint
  • GitHub Check: rust-tests (windows-latest)
  • GitHub Check: rust-tests (ubuntu-latest)
  • GitHub Check: rust-tests (macos-latest)
  • GitHub Check: storage-compat-test
  • GitHub Check: rust-tests-no-rocksdb (ubuntu-latest)
🔇 Additional comments (33)
lib/collection/src/operations/types.rs (3)

52-52: LGTM: Import simplified correctly.

The import was simplified from super::{ClockTag, config_diff::*} to just super::ClockTag. The config_diff items are imported at line 55, so this change is correct.


1872-1872: LGTM: Merge trait removed as intended.

The Merge trait has been correctly removed from the derive list for VectorParamsDiff, consistent with the PR's goal to replace serde-based implementations with explicit ones.


1535-1537: Verification complete: the default comparison is correct.

The HnswConfigDiff struct derives Default (line 34 in lib/collection/src/operations/config_diff.rs) and all its fields are Option types (m, ef_construct, full_scan_threshold, max_indexing_threads, on_disk, etc.). When Default::default() is called on HnswConfigDiff, all Option fields are initialized to None, which correctly represents an empty diff with no changes. The comparison *hnsw_config == Some(HnswConfigDiff::default()) accurately identifies empty diffs.

lib/collection/src/collection/collection_ops.rs (4)

31-41: LGTM: Update method now uses reference semantics.

The call to config.params.update() now correctly passes &params_diff instead of an owned value, consistent with the new DiffConfig trait design where update takes &Diff and returns Self infallibly.


48-58: LGTM: HNSW config update uses reference semantics.

The update call correctly passes &hnsw_config_diff, aligning with the new reference-based DiffConfig API.


101-111: LGTM: Optimizer config update uses reference semantics.

The update call correctly passes &optimizer_config_diff, consistent with the new DiffConfig trait signature.


182-203: LGTM: Strict mode config update uses reference semantics.

The update call at line 189 correctly passes &strict_mode_diff instead of an owned value. The pattern correctly updates the existing config in-place when present, or inserts the diff as a new config when absent.

lib/storage/src/content_manager/toc/create_collection.rs (4)

167-167: LGTM: WAL config update uses reference via as_ref().

The call to update_opt now correctly passes wal_config_diff.as_ref() to convert Option<WalConfigDiff> to Option<&WalConfigDiff>, consistent with the new reference-based DiffConfig API.


169-172: LGTM: Optimizer config update uses reference via as_ref().

The call to update_opt correctly passes optimizers_config_diff.as_ref(), aligning with the reference-based update semantics.


174-177: LGTM: HNSW config update uses reference via as_ref().

The call to update_opt correctly passes hnsw_config_diff.as_ref(). The reformatting to multiple lines improves readability while maintaining correctness.


188-204: LGTM: Strict mode config update uses reference semantics.

At line 196, the update call correctly passes &diff instead of an owned value, consistent with the new DiffConfig trait design. The logic properly merges the diff with default config when provided.

lib/collection/src/operations/config_diff.rs (13)

18-31: LGTM: Trait signature aligns with infallible, reference-based update semantics.

The removal of Result and the switch to borrowing &Diff are consistent with the PR's goal of eliminating JSON serialization errors and making updates infallible.


33-161: LGTM: Merge derive removed as intended.

Removal of Merge derives aligns with the PR's shift to explicit implementations, eliminating reliance on the merge crate.


206-228: LGTM: Field-wise update logic is correct.

The destructuring ensures exhaustive field coverage. The use of unwrap_or for required fields and .or for optional fields correctly merges the diff with the base config.


230-252: LGTM: Diff-on-diff update logic is correct.

Using .or for all fields is appropriate for merging two diff structs, where all fields are Option<T>.


254-280: LGTM: Optimizer config update logic is correct.

Field destructuring is exhaustive. The use of unwrap_or for required fields, .or for optional fields, and map_or with From::from for max_optimization_threads correctly handles the merge and type conversion.


282-296: LGTM: WalConfig update logic is correct.

All fields are destructured and merged using unwrap_or, which is appropriate for the required fields.


298-319: LGTM: CollectionParams update logic is correct.

The diff fields are merged correctly, and immutable fields (shard_number, sharding_method, sparse_vectors, vectors) are preserved from the base config. Cloning of sparse_vectors and vectors is necessary.


321-377: LGTM: StrictModeConfig update logic is correct.

All fields use .or to merge optional settings. The pattern .as_ref().or(self.X.as_ref()).cloned() correctly handles Option<T> fields where T: Clone.


379-401: LGTM: HnswConfig to HnswConfigDiff conversion is correct.

Exhaustive destructuring ensures all fields are accounted for. Required fields are wrapped in Some, and optional fields are passed through as-is.


403-417: LGTM: WalConfig to WalConfigDiff conversion is correct.

All fields are destructured and wrapped in Some to create the diff representation.


419-439: LGTM: CollectionParams to CollectionParamsDiff conversion is correct.

Fields that are part of the diff are correctly wrapped in Some or passed through. Fields not part of the diff are explicitly ignored with _, which satisfies the coding guideline to prefer explicit field ignoring.


441-467: LGTM: OptimizersConfig to OptimizersConfigDiff conversion is correct.

All fields are handled appropriately. The mapping of max_optimization_threads from Option<usize> to Option<MaxOptimizationThreads> using map(MaxOptimizationThreads::Threads) is correct.


501-591: LGTM: Tests updated to match new signature.

All test calls to .update() now pass references to diffs, which is consistent with the new trait signature.

lib/collection/src/config.rs (2)

284-286: LGTM: Reference passed correctly to update_opt.

The use of .as_ref() to convert Option<HnswConfig> to Option<&HnswConfig> aligns with the new signature of update_opt.


454-454: LGTM: Reference passed correctly to update.

The call to .update(&hnsw_diff) correctly passes a reference to the diff, consistent with the new trait signature.

lib/collection/src/collection_manager/optimizers/segment_optimizer.rs (1)

257-257: LGTM: Reference passed correctly to update_opt.

The use of .as_ref() correctly converts Option<HnswConfig> to Option<&HnswConfig> for the new update_opt signature.

lib/collection/src/collection_manager/optimizers/config_mismatch_optimizer.rs (2)

119-121: LGTM! Reference-based update aligns with DiffConfig refactor.

The use of as_ref() correctly borrows the hnsw_config instead of consuming it, which aligns with the new DiffConfig::update_opt signature that takes Option<&Diff>. This preserves ownership of the original config in vector_params.


582-587: LGTM! Test assertions correctly use reference-based update.

The test assertions now pass references (&hnsw_config_vector1, &hnsw_config_vector2) to the update method, which aligns with the new DiffConfig::update signature that takes &Diff. The test logic remains sound: it verifies that the segment's effective HNSW config matches the expected result of merging the collection-level config with vector-specific overrides.

lib/segment/src/types.rs (2)

868-874: LGTM! New max_length field is properly validated.

The addition of the max_length field to StrictModeSparse is well-implemented with appropriate validation (range(min = 1)), serde skip configuration, and consistent with similar fields in the codebase.


868-882: Removal of Merge trait is correct and complete.

The Merge trait has been successfully removed from StrictModeSparse, StrictModeSparseConfig, StrictModeMultivector, StrictModeMultivectorConfig, and StrictModeConfig. The explicit DiffConfig implementation for StrictModeConfig (at lib/collection/src/operations/config_diff.rs:321-375) properly handles the nested configuration structs using Option::or() logic, replacing them atomically rather than merging field-by-field. No individual DiffConfig implementations are needed for the nested structs, as they are treated as complete units during configuration updates.

lib/collection/src/collection/mod.rs (2)

132-136: LGTM! Refactor aligns with infallible DiffConfig trait.

The update call now passes &optimizers_overwrite by reference and assigns the result directly without error propagation, consistent with the new infallible DiffConfig::update signature.


253-257: LGTM! Refactor aligns with infallible DiffConfig trait.

The update call now passes &optimizers_overwrite by reference and assigns the result directly without error propagation, consistent with the new infallible DiffConfig::update signature.

@xzfc xzfc merged commit 23a0a0e into dev Sep 29, 2025
16 checks passed
@xzfc xzfc deleted the diffconfig-macro branch September 29, 2025 18:55
timvisee pushed a commit that referenced this pull request Nov 14, 2025
* refactor: replace serde-based DiffConfig implementation with macro

* refactor: macroexpand impl_diff_config

* refactor: drop `merge` dependency as unused
@coderabbitai coderabbitai bot mentioned this pull request Jan 4, 2026
3 tasks
@coderabbitai coderabbitai bot mentioned this pull request Mar 3, 2026
9 tasks
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.

4 participants