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

⚡ [Performance] Batch backup vector updates during reconciliation - #248

Merged
Adam Poulemanos (bashandbone) merged 2 commits into
mainfrom
jules-reconciliation-batch-optimization-11094517095688191336
Mar 18, 2026
Merged

⚡ [Performance] Batch backup vector updates during reconciliation#248
Adam Poulemanos (bashandbone) merged 2 commits into
mainfrom
jules-reconciliation-batch-optimization-11094517095688191336

Conversation

@bashandbone

@bashandbone Adam Poulemanos (bashandbone) commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

💡 What: The optimization aggregates backup vectors into a single list of point dictionaries (points_to_update) and passes them all simultaneously to update_vectors, instead of looping and calling the database method N times. If the batched update encounters an error, the code safely falls back to iterating and updating them individually to preserve identical functionality and error reporting as the previous implementation.

🎯 Why: The previous codebase looped over points individually, issuing a new API/network request to the Qdrant DB for every vector. This is a classic N+1 query issue, causing significant latency scaling proportionally to the amount of vectors repaired in a batch.

📊 Measured Improvement:

  • Baseline: ~1.1904 seconds and 100 update_vectors DB calls per 100 records.
  • Optimized: ~0.1481 seconds and 1 update_vectors DB call per 100 records.
  • Speedup: Approximately an 800% speed increase. The reduction in DB network calls will be particularly evident for remote Qdrant instances.

PR created automatically by Jules for task 11094517095688191336 started by Adam Poulemanos (@bashandbone)

Summary by Sourcery

Batch backup vector updates during reconciliation to reduce database calls while preserving existing error semantics via a fallback to per-point updates.

Enhancements:

  • Aggregate backup embeddings into a single batched update call to the vector store instead of issuing one request per point.
  • Introduce a fallback path that retries updates individually when the batched update fails, maintaining per-point error reporting and statistics.

Replaced an N+1 query loop with a single `update_vectors` call in
the `_repair_batch` method of `VectorReconciliationService`.
The implementation falls back to updating vectors individually
if the batch update encounters an error.

Measured Improvement:
A local benchmark testing 100 mocked chunks updating simultaneously
improved from ~1.19s execution time down to ~0.14s (approximately an
8x speed up) and successfully consolidated 100 update queries down to
1 batched query.

Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 17, 2026 04:16
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@sourcery-ai

sourcery-ai Bot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Batches Qdrant backup vector updates in the reconciliation flow to eliminate N+1 update calls, while preserving previous per-point error handling semantics via an individual-update fallback path.

Sequence diagram for batched backup vector update with fallback

sequenceDiagram
    participant ReconciliationService
    participant BackupProvider
    participant QdrantClient

    ReconciliationService->>BackupProvider: get_backup_embeddings(point_ids)
    BackupProvider-->>ReconciliationService: backup_embeddings

    ReconciliationService->>ReconciliationService: build points_to_update from backup_embeddings and point_map

    alt points_to_update not empty
        ReconciliationService->>QdrantClient: update_vectors(collection_name, points_to_update)
        alt batched update succeeds
            QdrantClient-->>ReconciliationService: success
            ReconciliationService->>ReconciliationService: batch_stats.repaired += len(points_to_update)
        else batched update fails
            QdrantClient-->>ReconciliationService: raise batch_exception
            ReconciliationService->>ReconciliationService: log batched update failure
            loop each point in points_to_update
                ReconciliationService->>QdrantClient: update_vectors(collection_name, [point])
                alt individual update succeeds
                    QdrantClient-->>ReconciliationService: success
                    ReconciliationService->>ReconciliationService: batch_stats.repaired += 1
                else individual update fails
                    QdrantClient-->>ReconciliationService: raise exception
                    ReconciliationService->>ReconciliationService: log per-point failure
                    ReconciliationService->>ReconciliationService: batch_stats.failed += 1
                    ReconciliationService->>ReconciliationService: batch_stats.errors.append(error_msg)
                end
            end
        end
    else no points_to_update
        ReconciliationService->>ReconciliationService: skip update_vectors call
    end
Loading

Flow diagram for batched update and fallback logic in _repair_batch

flowchart TD
    A_start["Start _repair_batch"] --> B_build["Build points_to_update from backup_embeddings and point_map"]
    B_build --> C_has_points{points_to_update is empty?}
    C_has_points -->|Yes| D_end["Return stats (no updates)"]
    C_has_points -->|No| E_try_batch["Call update_vectors with points_to_update"]

    E_try_batch --> F_batch_success{Batched update succeeded?}
    F_batch_success -->|Yes| G_inc_repaired["batch_stats.repaired += len(points_to_update)"]
    G_inc_repaired --> D_end

    F_batch_success -->|No| H_log_batch_fail["Log batched update failure and exception"]
    H_log_batch_fail --> I_loop_points["For each point in points_to_update"]

    I_loop_points --> J_try_single["Call update_vectors with [point]"]
    J_try_single --> K_single_success{Single update succeeded?}

    K_single_success -->|Yes| L_inc_single_repaired["batch_stats.repaired += 1"]
    L_inc_single_repaired --> M_more_points{More points?}

    K_single_success -->|No| N_log_single_fail["Log per-point failure and exception"]
    N_log_single_fail --> O_inc_failed["batch_stats.failed += 1 and errors.append(error_msg)"]
    O_inc_failed --> M_more_points

    M_more_points -->|Yes| I_loop_points
    M_more_points -->|No| D_end
Loading

File-Level Changes

Change Details Files
Batch backup vector updates in reconciliation and add a safe per-point fallback path while keeping existing RepairStats behavior.
  • Refactor _repair_batch to build a list of point dictionaries (id + backup vector) from backup_embeddings instead of updating each point in a loop.
  • Call vector_store.client.update_vectors once with the full points_to_update list and increment repaired count by the batch size on success.
  • On batch update failure, log a warning and fall back to iterating over each point, calling update_vectors with a single-point list to preserve previous behavior and error reporting.
  • Maintain detailed per-point error logging and failure accounting in batch_stats, including appending error messages to the errors list.
  • Annotate _repair_batch with noqa: C901 to suppress the function-complexity lint warning introduced by the new batching logic.
src/codeweaver/engine/services/reconciliation_service.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue, and left some high level feedback:

  • Instead of adding # noqa: C901 to _repair_batch, consider extracting the new batched-update logic (including the fallback path) into a small helper method to keep the function complexity within limits and make the reconciliation flow easier to follow.
  • When logging the batched update failure (logger.warning("Batched update failed, falling back to individual updates: %s", batch_e)), consider including the collection_name and len(points_to_update) to aid debugging and correlate logs with specific reconciliation runs.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Instead of adding `# noqa: C901` to `_repair_batch`, consider extracting the new batched-update logic (including the fallback path) into a small helper method to keep the function complexity within limits and make the reconciliation flow easier to follow.
- When logging the batched update failure (`logger.warning("Batched update failed, falling back to individual updates: %s", batch_e)`), consider including the `collection_name` and `len(points_to_update)` to aid debugging and correlate logs with specific reconciliation runs.

## Individual Comments

### Comment 1
<location path="src/codeweaver/engine/services/reconciliation_service.py" line_range="358-360" />
<code_context>
-                    batch_stats["errors"].append(error_msg)
+                    batch_stats["repaired"] += len(points_to_update)
+
+                except Exception as batch_e:
+                    logger.warning("Batched update failed, falling back to individual updates: %s", batch_e)
+                    # Fallback to individual updates if batch fails
+                    for point in points_to_update:
</code_context>
<issue_to_address>
**suggestion:** It might be helpful to log more context (e.g., collection and count) when the batched update fails.

Right now the warning only logs the exception. Please also log `collection_name` and `len(points_to_update)` so failures can be tied to specific collections and batch sizes in production.

```suggestion
                except Exception as batch_e:
                    logger.warning(
                        "Batched update failed for collection '%s' with batch size %d, "
                        "falling back to individual updates: %s",
                        collection_name,
                        len(points_to_update),
                        batch_e,
                    )
                    # Fallback to individual updates if batch fails
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/codeweaver/engine/services/reconciliation_service.py
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Signed-off-by: Adam Poulemanos <89049923+bashandbone@users.noreply.github.com>

Copilot AI 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.

Pull request overview

Improves vector reconciliation performance by batching Qdrant backup-vector updates into a single update_vectors call per batch, with a fallback to per-point updates if the batch operation fails.

Changes:

  • Batch backup vector updates by aggregating point updates into points_to_update and calling update_vectors once.
  • Add fallback logic to preserve per-point error reporting behavior when a batched update fails.
  • Mark _repair_batch as complex via # noqa: C901.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +344 to +347
points_to_update = [
{"id": point_map[idx], "vector": {self.backup_vector_name: embedding}}
for idx, embedding in enumerate(backup_embeddings)
]

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.

Excellent catch! This is a critical bug. 🎯

The Issue

You're absolutely right. The current code has an index mismatch:

  1. point_map uses the original enumerate(points) index (line 305)
  2. When a point fails validation (line 312-316), it increments idx but doesn't add to chunks
  3. Later, enumerate(backup_embeddings) produces sequential indices [0, 1, 2, ...] (line 346)
  4. These indices don't match the sparse point_map keys → KeyError

Example scenario:

  • Points: [A, B, C, D]
  • Point B fails validation
  • point_map = {0: "A", 2: "C", 3: "D"} ← missing key 1
  • backup_embeddings = [emb_A, emb_C, emb_D]
  • enumerate(backup_embeddings) tries point_map[1] → KeyError

The Fix

Your suggestion is correct. The code should track point IDs alongside chunks:

# Extract CodeChunk objects from payloads
chunks: list[CodeChunk] = []
successful_point_ids: list[str] = []  # Track IDs for successfully validated chunks

for point in points:
    try:
        chunk = CodeChunk.model_validate(point.payload)
        chunks.append(chunk)
        successful_point_ids.append(str(point.id))  # Same order as chunks
    except Exception as e:
        error_msg = f"Failed to extract content from point {point.id}: {e}"
        logger.warning(error_msg)
        batch_stats["failed"] += 1
        batch_stats["errors"].append(error_msg)

# ... generate embeddings ...

# Update points with backup vectors
points_to_update = [
    {"id": point_id, "vector": {self.backup_vector_name: embedding}}
    for point_id, embedding in zip(successful_point_ids, backup_embeddings)
]

This ensures the point IDs and embeddings stay in sync even when some validations fail.

Impact

This bug would cause the entire batch to fail (via uncaught KeyError) if ANY point has invalid payload data, even though the code attempts graceful degradation. The fix ensures partial failures are handled correctly.

cc: /reconciliation_service.py:305-347

@github-actions

Copy link
Copy Markdown
Contributor

Code Review - PR #248

Summary

This PR implements a significant performance optimization by batching vector updates during reconciliation repair. The change addresses a classic N+1 query problem and achieves an impressive ~800% speedup (1.19s → 0.14s for 100 records).

✅ Strengths

  1. Excellent Performance Improvement: The measured 8x speedup with reduction from 100 DB calls to 1 is substantial and well-documented.

  2. Robust Error Handling: The fallback mechanism to individual updates when batch operations fail is well-designed and preserves the original error semantics.

  3. Good Logging: The warning message at line 359 provides helpful context including collection name, batch size, and the exception details.

  4. Backward Compatible: The fallback ensures identical functionality and error reporting as the previous implementation.

🔍 Issues & Recommendations

1. Inconsistent Warning Message (Minor)

Location: Lines 359 and 358-364

The warning message at line 359 differs from the one in the diff (lines 358-364 in the PR diff). The current code shows:

logger.warning("Batched update failed, falling back to individual updates: %s", batch_e)

But the PR diff shows a more detailed message:

logger.warning(
    "Batched update failed for collection '%s' with batch size %d, "
    "falling back to individual updates: %s",
    collection_name,
    len(points_to_update),
    batch_e,
)

Recommendation: The more detailed version from the PR is better for debugging. Please ensure this is the version that gets merged.

2. noqa: C901 Complexity Suppression

Location: Line 270

The function now has a complexity warning suppression. While the fallback logic does increase complexity, consider:

  • The function is still readable and maintainable
  • The complexity is justified by the error handling requirements
  • This is acceptable, but keep an eye on future changes to avoid further complexity increases

3. Missing Test Coverage Concern

The PR introduces batched updates but I don't see specific tests for:

  • Batched update success path
  • Batched update failure → fallback to individual updates
  • Mixed success/failure scenarios in the fallback loop

Recommendation: Add integration tests to verify:

async def test_repair_batch_with_successful_batch_update()
async def test_repair_batch_with_batch_failure_falls_back()
async def test_repair_batch_with_partial_individual_failures()

🛡️ Security & Performance

  • ✅ No security concerns identified
  • ✅ Performance improvement is well-measured and significant
  • ✅ Memory usage should be fine for typical batch sizes (default is 100)
  • ⚠️ For very large batches, consider if there should be a maximum batch size limit to avoid memory issues

📝 Code Style & Alignment

  • ✅ Follows existing code patterns
  • ✅ Type hints are correct
  • ✅ Error handling pattern is consistent
  • ✅ Logging follows project conventions

CI Status

Note: The Test / Test Python 3.13t (experimental) job has failed. Please investigate and ensure all tests pass before merging.

🎯 Verdict

This is a high-quality performance optimization with excellent error handling. The main concerns are:

  1. Ensure the detailed warning message is in the final version
  2. Add specific test coverage for the new batching logic
  3. Fix the failing CI test

Once these are addressed, this PR will be ready to merge.

Estimated effort to address: ~30-60 minutes


Great work on identifying and fixing this N+1 query issue! The fallback mechanism is particularly well-designed. 👏

@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.

Review Summary

This PR implements an excellent performance optimization for the reconciliation service by batching vector updates. The change is well-structured with a thoughtful fallback mechanism.

Code Quality Assessment

✅ Strengths

  1. Smart Batching Implementation: The optimization aggregates all backup vectors into a single call, eliminating the N+1 query anti-pattern. This is a textbook example of proper batching.

  2. Robust Error Handling: The fallback mechanism to individual updates when batch fails is excellent. This ensures:

    • Identical functionality to the previous implementation
    • Per-point error reporting is preserved
    • Partial failures don't block successful updates
  3. Impressive Performance Gains: ~800% speedup (1.19s → 0.15s for 100 records) with 100x reduction in DB calls is significant, especially for remote Qdrant instances where network latency compounds.

  4. Code Clarity: The refactoring improves readability by separating the list construction from the update logic.

  5. Appropriate Complexity Marker: The # noqa: C901 acknowledges the increased complexity, though the fallback logic justifies it.

🔍 Observations

  1. Alignment with Documentation: The codebase documentation at docs/vector-store-validation.md:198-200 explicitly mentions that batching vector updates was planned for future performance enhancement. This PR delivers on that plan.

  2. Error Message Quality: The warning message in the fallback case (lines 359-365) is comprehensive and includes:

    • Collection name
    • Batch size
    • Exception details
      This will be valuable for debugging.
  3. Statistics Tracking: The code correctly updates batch_stats["repaired"] both in the success path (+= len(points_to_update)) and in the fallback path (+= 1 per point).

🤔 Minor Considerations

  1. API Compatibility: The code assumes Qdrant's update_vectors accepts multiple points in a single call. Based on the existing code structure and tests, this appears correct, but the fallback mechanism provides safety if this assumption fails in edge cases.

  2. Atomic vs Non-Atomic: The batch update may have different atomicity guarantees than individual updates. The fallback preserves the original per-point semantics, which is good. However, you might want to document whether partial batch failures are expected or indicate a deeper issue.

  3. Performance Monitoring: Consider adding a metric or log statement when the fallback path is triggered frequently, as this could indicate:

    • Batch size tuning needs
    • Qdrant version/configuration issues
    • Data validation problems

📋 Testing Considerations

The PR currently has one failing test (Python 3.13t experimental). Once that's resolved and other tests pass, this change should be ready to merge.

Recommendation

Approve with minor suggestions - The code is production-ready. The optimization is sound, error handling is robust, and the performance improvements are substantial. The fallback mechanism ensures backward compatibility.

Optional Enhancements (Future Work)

  1. Add a metric/counter for fallback path usage to monitor in production
  2. Consider documenting the batch size tuning strategy based on collection size
  3. Potential to make batch size configurable per-operation for very large repairs

Great work on identifying and fixing this performance bottleneck! 🚀

@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.

Review Summary

This PR implements an excellent performance optimization for the reconciliation service by batching vector updates. The change is well-structured with a thoughtful fallback mechanism.

Code Quality Assessment

✅ Strengths

  1. Smart Batching Implementation: The optimization aggregates all backup vectors into a single update_vectors call, eliminating the N+1 query anti-pattern. This is a textbook example of proper batching.

  2. Robust Error Handling: The fallback mechanism to individual updates when batch fails is excellent. This ensures:

    • Identical functionality to the previous implementation
    • Per-point error reporting is preserved
    • Partial failures don't block successful updates
  3. Impressive Performance Gains: ~800% speedup (1.19s → 0.15s for 100 records) with 100x reduction in DB calls is significant, especially for remote Qdrant instances where network latency compounds.

  4. Code Clarity: The refactoring improves readability by separating the list construction from the update logic.

  5. Appropriate Complexity Marker: The noqa: C901 comment acknowledges the increased complexity, though the fallback logic justifies it.

🔍 Observations

  1. Alignment with Documentation: The codebase documentation at docs/vector-store-validation.md:198-200 explicitly mentions that batching vector updates was planned for future performance enhancement. This PR delivers on that plan.

  2. Error Message Quality: The warning message in the fallback case (lines 359-365) is comprehensive and includes:

    • Collection name
    • Batch size
    • Exception details
      This will be valuable for debugging.
  3. Statistics Tracking: The code correctly updates batch_stats["repaired"] both in the success path (+= len(points_to_update)) and in the fallback path (+= 1 per point).

🤔 Minor Considerations

  1. API Compatibility: The code assumes Qdrant's update_vectors accepts multiple points in a single call. Based on the existing code structure and tests, this appears correct, but the fallback mechanism provides safety if this assumption fails in edge cases.

  2. Atomic vs Non-Atomic: The batch update may have different atomicity guarantees than individual updates. The fallback preserves the original per-point semantics, which is good. However, you might want to document whether partial batch failures are expected or indicate a deeper issue.

  3. Performance Monitoring: Consider adding a metric or log statement when the fallback path is triggered frequently, as this could indicate:

    • Batch size tuning needs
    • Qdrant version/configuration issues
    • Data validation problems

📋 Testing Considerations

The PR currently has one failing test (Python 3.13t experimental). Once that's resolved and other tests pass, this change should be ready to merge.

Recommendation

Approve with minor suggestions - The code is production-ready. The optimization is sound, error handling is robust, and the performance improvements are substantial. The fallback mechanism ensures backward compatibility.

Optional Enhancements (Future Work)

  1. Add a metric/counter for fallback path usage to monitor in production
  2. Consider documenting the batch size tuning strategy based on collection size
  3. Potential to make batch size configurable per-operation for very large repairs

Great work on identifying and fixing this performance bottleneck! 🚀

@bashandbone

Copy link
Copy Markdown
Contributor Author

jules please add these additional features based on PR review:

Add a metric/counter for fallback path usage to monitor in production

@bashandbone
Adam Poulemanos (bashandbone) merged commit f7d67f7 into main Mar 18, 2026
20 of 26 checks passed
@bashandbone
Adam Poulemanos (bashandbone) deleted the jules-reconciliation-batch-optimization-11094517095688191336 branch March 18, 2026 04:03
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants