Skip to content

Commit 1c8a717

Browse files
Release v0.3.0
* Bump version to 0.2.2 for development * feat(hnsw_index): add persistence integration and save method * feat(persistence): add persistence.rs for index save/load * chore(lib): register persistence and expose HNSWIndex save/load * chore(deps): update Cargo.toml for persistence and serialization * test(hnsw_index): add internal tests for save functionality * fix(hnsw): resolve HNSW graph dump layer compatibility issue * feat(persistence): integrate HNSW graph serialization in save workflow * test: add Phase 2 integration test for enhanced save method * 📄 docs(changelog): update for v0.2.2 release * feat(lib): export load_index function to Python module * feat(persistence): implement complete component loading infrastructure * feat(api): add load method to VectorDatabase class * test: add comprehensive component loading validation test * 📄 docs(changelog): update for v0.2.2 release * feat(deps): add anndists dependency for HNSW graph loading * feat(persistence): implement complete HNSW graph loading functionality * test: update test to recognize HNSW graph loading success * feat(persistence): implement complete HNSW graph loading functionality * 📄 docs(changelog): update for v0.2.2 release * feat: implement production-ready HNSW index with PQ, threading, and persistence support * feat: add comprehensive save/load system with 3-phase persistence and graph reconstruction * feat: implement high-performance Product Quantization with k-means++, ADC, and parallel training * test: add comprehensive persistence test suite with 7 scenarios covering PQ states, storage modes, and edge cases * 📦chore(release): Version bump: v0.2.1 → v0.3.0 * Remove outdated comments from file. * 📄 docs(readme): updated the content information * 📄 docs(readme): updated the content information * 📄 docs(readme): updated the content information * 📄 docs(readme): updated the content information * 📄 docs(readme): updated the content information * 📄 docs(readme): updated the content information * 📄 docs(readme): updated the content information * 📄 docs(readme): updated the content information * 📄 docs(readme): updated the content information * 📄 docs(readme): updated the content information * 📄 docs(readme): updated the content information * 📄 docs(readme): updated the content information * 📄 docs(readme): updated the content information * Update README.md * Update README.md * 📄 docs(readme): updated the content information * 📄 docs(changelog): update for v0.3.0 release * Add: latest uv.lock file for reproducible Python dependency management
1 parent 1a93da4 commit 1c8a717

22 files changed

Lines changed: 3302 additions & 276 deletions

CHANGELOG.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,77 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
---
99

10+
## [0.3.0] - 2025-08-06
11+
12+
### Added
13+
- `save()` method to `HNSWIndex` for persisting index state to disk via Python and Rust.
14+
- New `persistence.rs` module implementing index save/load logic, including manifest and file structure generation.
15+
- PyO3 bindings for persistence-related methods, exposing them to Python.
16+
- Internal unit tests for the `save` function to ensure correct file output and manifest validation.
17+
- HNSW graph structure persistence via native hnsw-rs file_dump() integration
18+
- Enhanced save workflow with Phase 2 graph serialization support
19+
- Comprehensive Phase 2 integration test suite for full persistence validation
20+
- Complete component loading infrastructure with helper functions for all ZeusDB file types
21+
- load() method to VectorDatabase class for loading saved indexes from disk
22+
- Comprehensive component validation and data consistency checking in load workflow
23+
- Python API integration for load_index function with proper PyO3 bindings
24+
- End-to-end test suite for component loading validation and error handling
25+
- Complete HNSW graph loading functionality using NoData pattern from hnsw-rs
26+
- anndists dependency for NoDist distance type compatibility
27+
- Phase 2 graph structure loading with validation and error handling
28+
- Full persistence roundtrip capability: save and load HNSW graph structures
29+
- Empty index handling with conditional graph file creation for zero-vector scenarios
30+
- Training state preservation with ID collection tracking during persistence
31+
- Storage mode awareness in persistence (quantized_only vs quantized_with_raw handling)
32+
- PQ centroids and codes serialization for complete quantization state preservation
33+
- Compression statistics and memory usage reporting in manifest files
34+
- Directory size calculation and file inventory tracking in manifest generation
35+
- rebuilding_from_persistence flag to prevent training ID contamination during reconstruction
36+
- Smart reconstruction approach using existing add() logic instead of complex graph deserialization
37+
- Thread-safe data access patterns during save operations with proper lock management
38+
39+
### Changed
40+
- Refactored `hnsw_index.rs` to integrate persistence logic and support serialization.
41+
- Updated `lib.rs` to register the persistence module and ensure all new methods are exposed to Python.
42+
- Enhanced error handling and docstrings for persistence operations.
43+
- Modified HNSW initialization to use fixed max_layer=16 for hnsw-rs dump compatibility
44+
- Updated manifest generation to include HNSW graph files (.hnsw.graph) and exclude data files (.hnsw.data)
45+
- Enhanced save_manifest() with graph file tracking and size calculation
46+
- Replaced placeholder load_index() with complete component loading implementation
47+
- Enhanced lib.rs module exports to include load_index function for Python access
48+
- Updated persistence.rs with comprehensive file loading and validation infrastructure
49+
- Extended persistence.rs with complete HNSW graph loading using HnswIo and ReloadOptions
50+
- Updated test suite to recognize and validate HNSW graph loading success
51+
- Enhanced quantization config validation to include training state and storage mode persistence
52+
- Modified PQ implementation to support set_trained() for persistence restoration
53+
- Updated index reconstruction to use "Simple Reconstruction" pattern for reliability
54+
- Refactored training threshold calculation to be self-healing during load operations
55+
- Enhanced error collection and reporting throughout persistence workflow
56+
57+
### Fixed
58+
- Improved reliability of index serialization and file output.
59+
- Addressed edge cases in directory creation and file writing during persistence.
60+
- Resolved critical "nb_layer != NB_MAX_LAYER" error preventing HNSW graph dumps
61+
- Fixed layer count compatibility issue between ZeusDB and hnsw-rs library requirements
62+
- Enabled successful HNSW graph structure serialization for graph files
63+
- Resolved Python binding compilation error for load_index function export
64+
- Fixed missing #[pyfunction] annotation preventing Python module integration
65+
- Established proper API consistency between save and load methods
66+
- Resolved anndists dependency issues for NoDist import compatibility
67+
- Fixed HNSW graph loading import paths for hnsw-rs v0.3.0+ compatibility
68+
- Resolved training ID loss during graph reconstruction by adding persistence rebuild flag
69+
- Fixed PQ training state restoration ensuring loaded instances are properly marked as trained
70+
- Corrected training progress calculation inconsistencies between save/load cycles
71+
- Addressed quantization state contamination during index reconstruction
72+
- Resolved thread safety issues in concurrent data access during persistence operations
73+
- Fixed storage mode detection and raw vector preservation based on configuration
74+
- Prevented training ID re-collection during persistence rebuild operations
75+
76+
### Removed
77+
<!-- Add removals/deprecations here -->
78+
79+
---
80+
1081
## [0.2.1] - 2025-07-30
1182

1283
### Added

README.md

Lines changed: 224 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
<!-- badges: end -->
2727

28-
<br/>
28+
<br />
2929

3030
## ℹ️ What is ZeusDB Vector Database?
3131

@@ -736,6 +736,229 @@ Quantization is ideal for production deployments with large vector datasets (100
736736

737737
<br/>
738738

739+
## 💾 Persistence
740+
741+
ZeusDB Vector Database provides production-ready persistence capabilities that allow you to save and restore your vector indexes to disk. This enables you to preserve your work, share indexes between systems, and implement backup strategies for production deployments.
742+
743+
The persistence system supports:
744+
745+
**Complete state preservation** – vectors, metadata, HNSW graph structure, and quantization models
746+
**Hybrid storage format** – efficient binary encoding for vectors with human-readable JSON for metadata
747+
**Quantization support** – seamlessly handles both raw and quantized storage modes
748+
**Training state recovery** – preserves PQ training progress and model parameters
749+
**Cross-platform compatibility** – indexes saved on one system can be loaded on another
750+
751+
<br/>
752+
753+
### 💾 Saving an Index - .save()
754+
755+
Use the `.save()` method to persist your index to a `.zdb` directory structure:
756+
757+
```python
758+
# Import the vector database module
759+
from zeusdb_vector_database import VectorDatabase
760+
import numpy as np
761+
762+
# Create and populate an index
763+
vdb = VectorDatabase()
764+
index = vdb.create("hnsw", dim=1536, space="cosine")
765+
766+
# Add some vectors
767+
vectors = np.random.random((1000, 1536)).astype(np.float32)
768+
data = {
769+
'vectors': vectors.tolist(),
770+
'ids': [f'doc_{i}' for i in range(1000)],
771+
'metadatas': [{'category': f'cat_{i%5}', 'index': i} for i in range(1000)]
772+
}
773+
index.add(data)
774+
775+
# Save the complete index to disk
776+
index.save("my_index.zdb")
777+
```
778+
779+
<br />
780+
781+
### 📂 Loading an Index - .load()
782+
783+
Use the .load() method to restore a previously saved index:
784+
785+
```python
786+
# Load the index from disk
787+
vdb = VectorDatabase()
788+
loaded_index = vdb.load("my_index.zdb")
789+
790+
# Verify the index loaded correctly
791+
print(f"Loaded index with {loaded_index.get_vector_count()} vectors")
792+
print(f"Index configuration: {loaded_index.info()}")
793+
794+
# Test search on loaded index
795+
query_vector = np.random.random(1536).tolist()
796+
results = loaded_index.search(query_vector, top_k=3)
797+
print(f"Search returned {len(results)} results")
798+
print(results)
799+
```
800+
801+
<br />
802+
803+
### 🗜️ Persistence with Product Quantization
804+
805+
Persistence seamlessly handles quantized indexes, preserving both the compression model and training state:
806+
807+
```python
808+
# Create index with quantization
809+
quantization_config = {
810+
'type': 'pq',
811+
'subvectors': 8,
812+
'bits': 8,
813+
'training_size': 1000,
814+
'storage_mode': 'quantized_only'
815+
}
816+
817+
vdb = VectorDatabase()
818+
index = vdb.create("hnsw", dim=1536, quantization_config=quantization_config)
819+
820+
# Add enough vectors to trigger PQ training
821+
vectors = np.random.random((2000, 1536)).astype(np.float32)
822+
data = {
823+
'vectors': vectors.tolist(),
824+
'ids': [f'vec_{i}' for i in range(2000)]
825+
}
826+
827+
add_result = index.add(data)
828+
print(f"Added {add_result.total_inserted} vectors")
829+
print(f"Training progress: {index.get_training_progress():.1f}%")
830+
print(f"Quantization active: {index.is_quantized()}")
831+
832+
# Save quantized index
833+
index.save("quantized_index.zdb")
834+
835+
# Load and verify quantization state is preserved
836+
loaded_index = vdb.load("quantized_index.zdb")
837+
print(f"Loaded quantization state: {loaded_index.is_quantized()}")
838+
print(f"Compression info: {loaded_index.get_quantization_info()}")
839+
```
840+
841+
<br/>
842+
843+
### 📁 Index Directory Structure
844+
The .save() method creates a structured directory containing all index components:
845+
846+
```
847+
my_index.zdb/
848+
├── manifest.json # Index metadata and file inventory
849+
├── config.json # HNSW configuration parameters
850+
├── mappings.bin # ID mappings (binary format)
851+
├── metadata.json # Vector metadata (JSON format)
852+
├── vectors.bin # Raw vectors (if applicable)
853+
├── quantization.json # PQ configuration (if enabled)
854+
├── pq_centroids.bin # Trained centroids (if PQ trained)
855+
├── pq_codes.bin # Quantized codes (if PQ active)
856+
└── hnsw_index.hnsw.graph # HNSW graph structure
857+
```
858+
859+
<br/>
860+
861+
### 🔄 Complete Save/Load Workflow
862+
Here's a comprehensive example showing the full persistence lifecycle:
863+
864+
```python
865+
from zeusdb_vector_database import VectorDatabase
866+
import numpy as np
867+
868+
# === PHASE 1: CREATE AND POPULATE INDEX ===
869+
vdb = VectorDatabase()
870+
original_index = vdb.create("hnsw", dim=1536, space="cosine", m=16)
871+
872+
# Add vectors with rich metadata
873+
np.random.seed(42) # For reproducible results
874+
vectors = np.random.random((500, 1536)).astype(np.float32)
875+
876+
data = {
877+
'vectors': vectors.tolist(),
878+
'ids': [f'doc_{i:03d}' for i in range(500)],
879+
'metadatas': [
880+
{
881+
'category': ['science', 'tech', 'health', 'finance'][i % 4],
882+
'priority': i % 10,
883+
'published': i % 2 == 0,
884+
'tags': ['important', 'featured'] if i % 5 == 0 else ['standard']
885+
}
886+
for i in range(500)
887+
]
888+
}
889+
890+
# Populate the index
891+
add_result = original_index.add(data)
892+
print(f"✅ Added {add_result.total_inserted} vectors")
893+
894+
# Add some index-level metadata
895+
original_index.add_metadata({
896+
"dataset": "demo_collection",
897+
"created_by": "data_team",
898+
"version": "1.0"
899+
})
900+
901+
# Test search before saving
902+
query_vector = vectors[0].tolist() # Use first vector as query
903+
original_results = original_index.search(query_vector, top_k=3)
904+
print(f"🔍 Original search found {len(original_results)} results")
905+
906+
# === PHASE 2: SAVE INDEX ===
907+
save_path = "demo_index.zdb"
908+
original_index.save(save_path)
909+
print(f"💾 Index saved to {save_path}")
910+
911+
# === PHASE 3: LOAD INDEX ===
912+
loaded_index = vdb.load(save_path)
913+
print(f"📂 Index loaded from {save_path}")
914+
915+
# === PHASE 4: VERIFY INTEGRITY ===
916+
# Check vector count
917+
assert loaded_index.get_vector_count() == original_index.get_vector_count()
918+
print(f"✅ Vector count verified: {loaded_index.get_vector_count()}")
919+
920+
# Check configuration
921+
assert loaded_index.info() == original_index.info()
922+
print(f"✅ Configuration verified: {loaded_index.info()}")
923+
924+
# Check metadata preservation
925+
original_meta = original_index.get_all_metadata()
926+
loaded_meta = loaded_index.get_all_metadata()
927+
#assert original_meta == loaded_meta
928+
print(f"Original meta fields: {len(original_meta)}, Loaded meta fields: {len(loaded_meta)}")
929+
print(f"✅ Index metadata verified: {len(loaded_meta)} fields")
930+
931+
# Test search consistency
932+
loaded_results = loaded_index.search(query_vector, top_k=3)
933+
assert len(loaded_results) == len(original_results)
934+
assert loaded_results[0]['id'] == original_results[0]['id']
935+
print("✅ Search consistency verified")
936+
937+
# Test filtering on loaded index
938+
filtered_results = loaded_index.search(
939+
query_vector,
940+
filter={'category': 'science', 'published': True},
941+
top_k=5
942+
)
943+
print(f"🔍 Filtered search found {len(filtered_results)} results")
944+
945+
print("\n🎉 Complete persistence workflow successful!")
946+
```
947+
948+
### ⚠️ Important Notes on Persistence
949+
- Directory Structure: The .save() method creates a directory, not a single file. Ensure you have write permissions for the target location.
950+
951+
- Cross-Platform: Saved indexes are portable between different operating systems and Python environments.
952+
953+
- Version Compatibility: Indexes include format version information for future compatibility checking.
954+
955+
- Memory Efficiency: The persistence format is optimized for both storage size and loading speed.
956+
957+
- Atomic Operations: Save operations are designed to be atomic - either the entire index saves successfully or the operation fails without partial corruption.
958+
959+
960+
<br />
961+
739962
## 🏷️ Metadata Filtering
740963

741964
ZeusDB supports rich metadata with full type fidelity. This means your metadata preserves the original Python data types (integers stay integers, floats stay floats, etc.) and enables powerful filtering capabilities.

0 commit comments

Comments
 (0)