Skip to content

LSM Vector index implementation - #2800

Closed
robfrank wants to merge 8 commits into
mainfrom
feat/2529-add-lsm-vector-index
Closed

LSM Vector index implementation#2800
robfrank wants to merge 8 commits into
mainfrom
feat/2529-add-lsm-vector-index

Conversation

@robfrank

Copy link
Copy Markdown
Collaborator

This pull request adds comprehensive documentation and analysis for the new LSMVectorIndex implementation in ArcadeDB. The changes include a detailed README for the vector index, outlining its architecture, usage, and roadmap, as well as an in-depth technical analysis of the existing LSM-tree index codebase and recommendations for vector index integration. These resources are intended to help both users and developers understand, use, and extend the LSMVectorIndex.

Documentation and Analysis Additions:

LSMVectorIndex Documentation:

  • Introduced LSMVECTORINDEX_README.md, providing a complete quick start, architecture overview, feature checklist, performance characteristics, configuration options, test coverage, troubleshooting, future roadmap, and developer/architect learning resources. The README also maps all related documentation and source files for easy navigation.

Technical Analysis of LSM-tree Index:

  • Added LSM_ANALYSIS_SUMMARY.txt with a comprehensive breakdown of the LSM-tree index implementation in ArcadeDB. This includes class responsibilities, storage architecture, transaction integration, concurrency, serialization, and design patterns. The analysis concludes with concrete recommendations for implementing the new LSMTreeVectorIndex, ensuring alignment with established patterns and highlighting vector-specific considerations.

@robfrank robfrank added this to the 25.11.1 milestone Nov 17, 2025
@robfrank robfrank linked an issue Nov 17, 2025 that may be closed by this pull request
4 tasks
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @robfrank, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a robust and well-documented LSM-based vector indexing system for ArcadeDB, enabling efficient storage and retrieval of vector embeddings. The implementation follows a phased approach, with the initial phases delivering core functionality, SQL/Java API integration, and a solid architectural foundation for future enhancements like HNSW acceleration. Accompanying the code are extensive documentation and a detailed analysis of the existing LSM-tree index, ensuring clarity for both users and developers.

Highlights

  • New LSM Vector Index: Introduced a new LSM-based vector indexing system (LSMVectorIndex) for ArcadeDB, designed for efficient vector similarity search.
  • Comprehensive Documentation: Added extensive documentation, including a detailed README, an implementation journey guide, and a final status report, covering architecture, usage, and future roadmap.
  • Technical Analysis of LSM-Tree: Provided an in-depth technical analysis of the existing LSM-tree index codebase, outlining its architecture, design patterns, and offering recommendations for integrating new index types.
  • SQL and Java API Integration: Enabled SQL syntax for creating vector indexes with configurable metadata (dimensions, similarity, HNSW parameters) and exposed a comprehensive Java API for index creation, vector insertion, and K-Nearest Neighbor (KNN) search.
  • Hybrid Search and Compaction: Implemented a two-level LSM design with mutable (in-memory) and compacted (read-optimized) components, supporting hybrid KNN search across both and orchestrating K-way merge compaction for deduplication and atomic component swapping.
  • HNSW Framework Integration: Integrated JVector's HNSW (Hierarchical Navigable Small World) framework into the compacted index, providing a foundation for future O(log n) approximate KNN search performance.
  • Robust Testing: Included 13 comprehensive integration tests covering index creation, vector CRUD operations, compaction, JSON serialization, and KNN search across various distance metrics and hybrid scenarios.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

robfrank and others added 6 commits November 17, 2025 11:12
…Mutable

- Implement VectorSerializer for binary serialization of vectors and RID sets
- Add disk storage infrastructure to LSMVectorIndexMutable with ComponentFile support
- Initialize page buffering system for future disk writes
- Add disk initialization with graceful fallback to in-memory if disk I/O fails
- Update close() method to flush disk buffers
- Add initializeDiskStorage() for future file I/O integration
- Maintains 100% backward compatibility with existing API

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
…est data

Fix LSMVectorIndex factory handler parameter ordering:
- docType now receives getTypeName() (was getIndexName)
- property now receives getPropertyName() (was getFilePath)
- name now receives getIndexName() (was getTypeName)
- filePath now receives getFilePath() (was getPropertyName)

Fix testKNNSearchAllDistanceMetrics test design:
- Use unique property names for each metric (embedding_cosine, embedding_euclidean, etc.)
- Use unique RIDs for each metric test to avoid data contamination
- Use orthogonal test vectors (v1=[1,0,0], v2=[0,1,0]) for better metric distinction
- Previous test data (parallel vectors) caused COSINE similarity ties

Results:
- All 12 LSMVectorIndexTest tests now pass (previously 3 failures)
- All 20 VectorSerializerTest tests still pass (no regressions)
- Factory now correctly initializes all index parameters

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
@robfrank
robfrank force-pushed the feat/2529-add-lsm-vector-index branch from a876c10 to 7f5fae8 Compare November 17, 2025 10:12
@mergify

mergify Bot commented Nov 17, 2025

Copy link
Copy Markdown
Contributor

🧪 CI Insights

Here's what we observed from your CI run for c014978.

🟢 All jobs passed!

But CI Insights is watching 👀

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a comprehensive implementation of an LSM-based vector index, LSMVectorIndex, for ArcadeDB. The changes are extensive, covering not only the core indexing logic with mutable and compacted components but also schema integration, a dedicated builder, a compactor, and numerous tests. The implementation is well-structured and follows the LSM-tree architecture principles. My review focuses on a few key areas: a critical bug in the sorting logic for the Euclidean distance metric, an incomplete implementation of the remove operation which could lead to data inconsistency with disk persistence, a missing feature for search filtering in the HNSW-accelerated path, and the use of hardcoded filtering thresholds in KNN search, which could impact usability.

// Then sort by distance/similarity based on metric type:
// - EUCLIDEAN: lower distance is better → ascending sort
// - COSINE/DOT_PRODUCT: higher similarity is better → descending sort
if ("EUCLIDEAN".equals(similarityFunction)) {

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.

high

There is a bug in the sorting logic for the KNN search results. The similarityFunction is an enum of type VectorSimilarityFunction, but it's being compared to a string literal "EUCLIDEAN". This comparison will always evaluate to false, causing the sorting logic for the Euclidean distance to be skipped and falling through to the descending sort, which is incorrect for a distance metric where lower values are better.

Suggested change
if ("EUCLIDEAN".equals(similarityFunction)) {
if (similarityFunction == VectorSimilarityFunction.EUCLIDEAN) {

Comment on lines +245 to +257
public void remove(final float[] vector, final RID rid) {
if (vector == null || rid == null)
return; // Skip removal silently for null values

final VectorKey key = new VectorKey(vector);
final Set<RID> rids = vectorToRIDs.get(key);
if (rids != null && rids.remove(rid)) {
totalEntries--;
if (rids.isEmpty()) {
vectorToRIDs.remove(key);
}
}
}

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.

high

The remove operation only updates the in-memory vectorToRIDs map. It does not account for the disk-based persistence layer. If a vector has been flushed to disk, removing it from the in-memory map will not prevent it from being reloaded from disk on a subsequent startup or during compaction. This could lead to "resurrected" data and inconsistencies. To fix this, a tombstone mechanism should be implemented for on-disk entries, so that they can be properly purged during compaction.

Comment on lines +671 to +687
final List<LSMVectorIndexMutable.VectorSearchResult> filtered = new ArrayList<>();
for (final LSMVectorIndexMutable.VectorSearchResult result : results) {
final boolean shouldInclude = switch (similarityFunction) {
case COSINE -> result.distance > 0.5f; // More than 50% similar
case DOT_PRODUCT -> result.distance > 0.0f; // Positive correlation
case EUCLIDEAN -> true; // For EUCLIDEAN, include all (distance-based)
};

if (shouldInclude) {
filtered.add(result);
if (filtered.size() >= k)
break;
}
}

return filtered;
} finally {

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.

medium

The KNN search results are being filtered by a hardcoded similarity threshold (e.g., > 0.5f for COSINE). This might be unexpected for users, as it can cause the method to return fewer than k results, even when more are available but do not meet this internal threshold. It would be better to return all k nearest neighbors and let the user decide how to filter them, or to make this threshold a configurable parameter of the search.

Comment on lines +206 to +212
public List<LSMVectorIndexMutable.VectorSearchResult> knnSearch(
final float[] queryVector, final int k,
final com.arcadedb.index.lsm.LSMVectorIndex.IgnoreVertexCallback ignoreCallback) {
// TODO: Implement filtering support in HNSW search
// For now, delegate to HNSW search without filtering
return knnSearch(queryVector, k);
}

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.

medium

The knnSearch method that accepts an ignoreCallback for filtering is not fully implemented. It currently delegates to the non-filtering version, and the ignoreCallback parameter is ignored. This represents a loss of functionality compared to the parent class LSMVectorIndexCompacted, which does support filtering. The TODO comment acknowledges this, but it's an important feature to implement for consistency and utility.

Comment on lines +350 to +365
final List<VectorSearchResult> filtered = new ArrayList<>();
for (final VectorSearchResult result : results) {
boolean shouldInclude = switch (similarityFunction) {
case COSINE -> result.distance > 0.5f; // More than 50% similar
case DOT_PRODUCT -> result.distance > 0.0f; // Positive correlation
case EUCLIDEAN -> true; // For EUCLIDEAN, include all (distance-based, not similarity)
};

if (shouldInclude) {
filtered.add(result);
if (filtered.size() >= k)
break;
}
}

return filtered;

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.

medium

Similar to the main LSMVectorIndex class, this knnSearch implementation filters results based on a hardcoded threshold (e.g., > 0.5f for COSINE). This can lead to fewer than k results being returned, which might be confusing for users. Consider removing this filtering logic to ensure the top k neighbors are always returned, or make the threshold configurable.

@codacy-production

Copy link
Copy Markdown

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
+1.23% 65.55%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (62efaed) 73287 46758 63.80%
Head commit (9f32958) 70456 (-2831) 45821 (-937) 65.03% (+1.23%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#2800) 1071 702 65.55%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

See your quality gate settings    Change summary preferences

@robfrank

robfrank commented Dec 9, 2025

Copy link
Copy Markdown
Collaborator Author

supersed by #2816

@robfrank robfrank closed this Dec 9, 2025
@lvca
lvca deleted the feat/2529-add-lsm-vector-index branch July 3, 2026 20:27
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.

Implement new vector index using JVector library

1 participant