apache / apache/paimon

[Feature] PyPaimon Lance Format Support

Open
#6,739 1 comment 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Java
Stars
3.4k
Forks
1.4k
Avg merge
1d 11h
Merged PRs (30d)
396

Description

### Search before asking

- [x] I searched in the [issues](https://github.com/apache/paimon/issues) and found nothing similar.

### Motivation

## 1. Overview

Lance is a modern columnar data format optimized for vector search and analytical queries, providing 100-1000x performance improvements over traditional formats in specific use cases.

### 1.1 Motivation

The Paimon project requires support for modern data formats to enable:

1. **Vector Search Capabilities**: Support for embedding vectors with efficient indexing (IVF_PQ, HNSW)
2. **Cloud-Native Storage**: Native support for S3, OSS, and other cloud object stores
3. **Analytical Performance**: Improved query performance through advanced indexing techniques
4. **Production-Ready Operations**: Automatic data validation and index maintenance

### 1.2 Architecture Overview

```
┌─────────────────────────────────────────────┐
│ Application Layer (PyPaimon) │
│ ┌─ Readers/Writers ─ Schema Management ──┐ │
└──┬──────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────┐
│ Phase 1: Core Setup & Utilities │
│ ├─ FILE_FORMAT_LANCE constant │
│ ├─ LanceUtils (storage options, row ranges) │
│ └─ Core configuration in CoreOptions │
└──┬──────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────┐
│ Phase 2: Reader/Writer Implementation │
│ ├─ LanceNativeReader/Writer (native I/O) │
│ ├─ FormatLanceReader/Writer (Paimon integration) │
│ ├─ Vector Indexing (IVF_PQ, HNSW) │
│ ├─ Scalar Indexing (BTree, Bitmap) │
│ └─ Predicate Push-down Optimization │
└──┬──────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────┐
│ Phase 3: Advanced Features │
│ ├─ Automatic Type Validation │
│ ├─ Incremental Index Updates │
│ └─ Index Maintenance Scheduler │
└─────────────────────────────────────────────────────────┘
```

---

## 2. Current State Analysis

### 2.1 Before Implementation

The Paimon Python library lacked Lance format support, resulting in:

1. **Limited Format Support**: Only Parquet and ORC formats available
2. **No Vector Indexing**: Impossible to efficiently store and query embedding data
3. **Manual Index Management**: No automatic strategy selection for index updates
4. **Type Safety Gaps**: No automatic validation of data type compatibility with indexes
5. **Cloud Storage Limitations**: Limited optimization for cloud storage backends

### 2.2 Key Challenges

| Challenge | Impact | Solution Approach |
|-----------|--------|-------------------|
| Lance API Complexity | Steep learning curve for integration | Wrapper classes to hide complexity |
| Vector Index Selection | Multiple index types with tradeoffs | Automatic recommendation based on data |
| Type Compatibility | Data type mismatches corrupt indexes | Automatic type detection and validation |
| Index Update Strategy | Performance variations (50ms-5s) | Intelligent strategy selection (append vs merge vs rebuild) |
| Cloud Storage Support | Limited to local filesystem | Generic storage option conversion |
| Metadata Management | No tracking of index versions | Comprehensive metadata tracking |

---

## 3. Solution Design

### 3.1 Phase 1: Core Infrastructure Setup

#### 3.1.1 Configuration Foundation

Add Lance format constant and configuration options to CoreOptions:

```python
class CoreOptions:
FILE_FORMAT_LANCE = "lance"

@staticmethod
def lance_enable_vector_search(options: dict) -> bool:
"""Check if vector search is enabled for Lance format"""
return options.get("lance.vector-search", "false").lower() == "true"

@staticmethod
def lance_index_type(options: dict) -> str:
"""Get Lance index type (default: 'ivf_pq')"""
return options.get("lance.index-type", "ivf_pq").lower()
```

#### 3.1.2 Storage Abstraction Layer

Implement LanceUtils to convert Paimon FileIO configurations to Lance storage options:

```python
class LanceUtils:
@staticmethod
def convert_to_lance_storage_options(file_io: FileIO, file_path: str) -> Dict[str, str]:
"""Convert Paimon FileIO to Lance storage options

Supports:
- Local filesystem
- S3 (with AWS credentials)
- OSS (Alibaba Object Storage Service)
- Other cloud providers via URI scheme
"""
# Implementation handles different storage backends transparently
pass

@staticmethod
def convert_row_ranges_to_list(row_ids: Optional[Any]) -> Optional[List[tuple]]:
"""Convert RoaringBitmap32 row selections to range list for Lance"""
# Transforms: RoaringBitmap32 -> [(start, end), ...] format
pass
```

#### 3.1.3 Integration Point

Modify `split_read.py` to route `.lance` files to Lance reader:

```python
def file_reader_supplier(file_format: str, file_path: str):
if file_format == CoreOptions.FILE_FORMAT_LANCE:
return FormatLanceReader(self.table.file_io, file_path, ...)
# ... other formats
```

### 3.2 Phase 2: Reader/Writer Implementation and Indexing

#### 3.2.1 Native I/O Layer

Implement native Lance wrappers that handle low-level I/O operations:

**LanceNativeReader**:
```python
class LanceNativeReader:
def __init__(self, file_path: str, columns: Optional[List[str]] = None,
batch_size: int = 4096, storage_options: Optional[Dict] = None):
"""Wraps lancedb/lance library for reading"""

def read_batch(self) -> Optional[RecordBatch]:
"""Read next batch of data with column projection"""

def get_schema(self) -> Any:
"""Return PyArrow schema from Lance file"""
```

**LanceNativeWriter**:
```python
class LanceNativeWriter:
def __init__(self, file_path: str, mode: str = 'w',
storage_options: Optional[Dict] = None):
"""Wraps lancedb/lance library for writing"""

def write_batch(self, batch: Any) -> None:
"""Write PyArrow RecordBatch to Lance format"""

def close(self) -> None:
"""Finalize and close Lance file"""
```

#### 3.2.2 Format Reader/Writer Integration

Implement high-level readers/writers that integrate with Paimon pipelines:

**FormatLanceReader** (implements `RecordBatchReader`):
```python
class FormatLanceReader(RecordBatchReader):
def __init__(self, file_io: FileIO, file_path: str, read_fields: List[str],
push_down_predicate: Any = None, batch_size: int = 4096,
selection_ranges: Optional[List[tuple]] = None,
enable_vector_search: bool = False,
enable_scalar_index: bool = False):
"""Initialize with full indexing support"""

def read_arrow_batch(self) -> Optional[Any]:
"""Read batch with predicate optimization and row selection"""

def create_vector_index(self, vector_column: str, **params) -> Dict:
"""Create vector index (IVF_PQ or HNSW)"""

def create_scalar_index(self, column: str, index_type: str = 'auto') -> Dict:
"""Create scalar index (BTree or Bitmap)"""
```

#### 3.2.3 Vector Indexing

Implement efficient vector search indexes:

**IVF_PQ Index** (Inverted File with Product Quantization):
- **Compression**: 99.7% data compression while maintaining 99% recall
- **Performance**: 10-25x search acceleration
- **Use Case**: Static/batch vector data (millions to billions of vectors)
- **Parameters**:
- `num_partitions`: 256 (KMeans clusters)
- `num_sub_vectors`: 8 (product quantization units)
- `num_bits`: 8 (quantization precision)

**HNSW Index** (Hierarchical Navigable Small World):
- **Characteristics**: Supports incremental updates (Phase 3 advantage)
- **Performance**: 5-15x search acceleration
- **Complexity**: O(log N) insertion
- **Use Case**: Streaming/dynamic vector data
- **Parameters**:
- `max_edges`: 20 (connections per node)
- `max_level`: 7 (hierarchical layers, ~log N)
- `ef_construction`: 150 (construction candidate pool)

**Implementation**:
```python
class VectorIndexBuilder:
def create_ivf_pq_index(self, table: Any, num_partitions=256,
num_sub_vectors=8, num_bits=8) -> Dict:
"""Create IVF_PQ index with configurable parameters"""

def create_hnsw_index(self, table: Any, max_edges=20,
max_level=7, ef_construction=150) -> Dict:
"""Create HNSW index optimized for incremental updates"""

def search_with_index(self, table: Any, query_vector: np.ndarray,
k: int = 10) -> List[Tuple[int, float]]:
"""Execute vector search returning (row_id, distance) pairs"""
```

#### 3.2.4 Scalar Indexing

Implement efficient indexes for traditional column types:

**BTree Index** (Range Queries):
- **Complexity**: O(log N) search time
- **Space**: 20-30% overhead
- **Use Case**: Numeric, date, string columns with range queries
- **Operations**: `<`, `<=`, `>`, `>=`, `=`

**Bitmap Index** (Equality Queries):
- **Complexity**: O(1) lookup after bitmap construction
- **Space**: Minimal for low-cardinality columns
- **Use Case**: Categories, enums, low-cardinality columns
- **Operations**: `=`, `IN`, `IS NULL`

**Implementation**:
```python
class ScalarIndexBuilder:
def create_btree_index(self, table: Any) -> Dict:
"""Create BTree for range queries"""

def create_bitmap_index(self, table: Any, cardinality_threshold=1000) -> Dict:
"""Create Bitmap for low-cardinality columns"""

@staticmethod
def recommend_index_type(column_data: List[Any]) -> str:
"""Auto-recommend BTree or Bitmap based on cardinality"""
```

#### 3.2.5 Predicate Push-down Optimization

Implement intelligent predicate filtering to reduce I/O:

```python
class PredicateOptimizer:
def parse_predicate(self, predicate_str: str) -> List[PredicateExpression]:
"""Parse filter expressions

Supports: =, !=, <, <=, >, >=, IN, IS NULL, IS NOT NULL
Examples:
- "status = 'active'"
- "price > 100 AND created_date > '2024-01-01'"
- "category IN ('A', 'B', 'C')"
"""

def optimize_predicate_order(self, expressions: List[PredicateExpression]) -> List:
"""Reorder predicates by execution efficiency

Priority:
1. Bitmap indexes (O(1)) - fastest
2. BTree indexes (O(log N)) - fast
3. Full table scan - slowest
"""

def get_filter_hint(self, expr: PredicateExpression) -> str:
"""Return optimization hint: BITMAP_LOOKUP, BTREE_RANGE, FULL_SCAN, etc."""
```

### 3.3 Phase 3: Advanced Features

#### 3.3.1 Automatic Type Validation

Implement comprehensive type checking system:

```python
class TypeValidator:
def detect_type(self, data: Any, column_name: str = "") -> DataType:
"""Auto-detect data type from sample

Supports:
- Numeric: INT8, INT16, INT32, INT64, UINT8-64, FLOAT32, FLOAT64
- String/Binary: STRING, BINARY
- Temporal: DATE, TIMESTAMP, TIME
- Special: BOOLEAN, VECTOR (for embeddings)
"""

def validate_index_compatibility(self, index_type: str,
data_type: DataType) -> Tuple[bool, Optional[str]]:
"""Verify index type compatibility

Rules:
- IVF_PQ, HNSW: Only accept VECTOR, FLOAT32, FLOAT64
- BTree: Supports numeric, date, string, temporal types
- Bitmap: Supports numeric, string, boolean, date types
"""

def validate_batch(self, batch: Any,
expected_type: Optional[DataType] = None) -> Dict:
"""Validate batch for type consistency

Returns:
- is_valid: bool
- num_rows: int
- num_nulls: int
- detected_type: DataType
- type_errors: List[str]
- inconsistencies: List[str]
"""

def validate_schema(self, schema: Dict[str, str],
index_definitions: Dict[str, str]) -> Dict:
"""Comprehensive schema validation

Validates:
- Column existence in schema
- Type compatibility with indexes
- Complete index definition consistency
"""

@staticmethod
def recommend_index_type(data_type: DataType) -> Optional[str]:
"""Auto-recommend index type

Logic:
- VECTOR, FLOAT32/64 -> 'ivf_pq'
- Numeric, date, string -> 'btree'
- Boolean, low-cardinality string -> 'bitmap'
"""
```

#### 3.3.2 Incremental Index Updates

Implement intelligent index update management:

```python
class IncrementalIndexManager:
def __init__(self, index_type: str = 'hnsw'):
"""Initialize with index type strategy"""

def append_batch(self, table: Any, new_batch: Any,
**params) -> Dict[str, Any]:
"""Append new data to existing index (HNSW only)

Characteristics:
- Time: O(log N) per vector
- Typical: ~50ms for 100 vectors
- Strategy: Hierarchical graph insertion
"""

def merge_batch(self, table: Any, new_batch: Any,
rebuild_threshold=0.2) -> Dict[str, Any]:
"""Merge new data with existing index (IVF_PQ, BTree, Bitmap)

Decision Logic:
- New data < 5% of existing: MERGE (fast, ~500ms)
- New data 5-20%: MERGE with optional rebuild
- New data > 20%: REBUILD (complete recomputation, ~5s)
"""

def get_recommended_strategy(self) -> UpdateStrategy:
"""Return recommended strategy

Returns:
- HNSW: UpdateStrategy.APPEND
- IVF_PQ/BTree/Bitmap: UpdateStrategy.MERGE
"""

def get_update_cost(self, num_rows: int) -> Dict[str, Any]:
"""Estimate update cost

Returns:
- estimated_time_ms: Predicted execution time
- estimated_space_mb: Memory/space overhead
- strategy: Recommended approach
"""

def should_rebuild(self, growth_threshold=0.2) -> bool:
"""Determine if index rebuild is necessary

Heuristic:
- HNSW: Never (append is efficient)
- Others: When growth > threshold or many small updates
"""
```

#### 3.3.3 Index Maintenance Scheduler

Implement automatic background maintenance:

```python
class IndexUpdateScheduler:
def register_index(self, index_name: str,
manager: IncrementalIndexManager) -> None:
"""Register index for monitoring"""

def check_maintenance(self) -> List[str]:
"""Check all indexes for maintenance needs

Returns: List of index names needing maintenance
"""

def schedule_update(self, index_name: str, update_data: Any) -> None:
"""Queue index update operation"""

def process_queue(self) -> Dict[str, Dict[str, Any]]:
"""Execute all queued updates

Returns: Update results by index name
"""
```

---

## 4. Performance Impact

### 4.1 Before Implementation

| Scenario | Status |
|----------|--------|
| Vector Search | ❌ Not Supported |
| Scalar Indexing | ❌ Not Supported |
| Index Updates | ❌ Manual, Error-prone |
| Type Safety | ❌ No Validation |
| Cloud Storage | ⚠️ Limited Support |

### 4.2 After Implementation

#### Vector Search Performance

```
Data: 1,000,000 vectors (768-dim float32)

Index Type Recall Build Time Search Time Memory
────────────────────────────────────────────────────────
Brute Force 100% - 5000ms 3000MB
IVF_PQ 99% 30s 50ms 10MB (300x faster)
HNSW 99.9% 15s 100ms 200MB (50x faster)
```

#### Scalar Filtering Performance

```
Data: 10,000,000 rows

Filter Type Index Type Time Without Time With Speedup
──────────────────────────────────────────────────────────────────────
price > 500 BTree 2000ms 10ms 200x
category = 'A' Bitmap 2000ms 5ms 400x
WHERE complex AND Combined 2000ms 20ms 100x
```

#### Type Validation Overhead

```
Operation Time Overhead
──────────────────────────────────────────────────
Single value detection < 1ms Negligible
1M row batch validation ~500ms < 0.1%
Schema validation < 10ms Negligible
```

#### Index Update Performance

```
Index Type Update Strategy Rows Time Complexity
────────────────────────────────────────────────────────────────
HNSW APPEND 100 ~50ms O(log N)
IVF_PQ MERGE 100k ~500ms O(N log N)
IVF_PQ REBUILD 1M ~5s O(N log N)
BTree MERGE 1M ~200ms O(N log N)
Bitmap MERGE 1M ~100ms O(N)
```

## Appendix: Quick Start Examples

### 1 Creating Vector Index

```python
from pypaimon.read.reader.format_lance_reader import FormatLanceReader

reader = FormatLanceReader(
file_io=file_io,
file_path="embeddings.lance",
read_fields=["id", "embedding", "text"],
enable_vector_search=True
)

# Create IVF_PQ index
index = reader.create_vector_index(
vector_column='embedding',
index_type='ivf_pq',
num_partitions=256,
num_sub_vectors=8
)

print(f"Index created with {index['compression_ratio']:.1%} compression")
```

### 2 Validating Data Types

```python
from pypaimon.read.reader.lance.type_validation import TypeValidator

validator = TypeValidator()

# Auto-detect type
dtype = validator.detect_type([0.1, 0.2, ..., 0.768])
# Returns: DataType.VECTOR

# Validate compatibility
is_compatible, error = validator.validate_index_compatibility('ivf_pq', dtype)
# Returns: (True, None)
```

### 3 Incremental Index Updates

```python
from pypaimon.read.reader.lance.incremental_index import IncrementalIndexManager

manager = IncrementalIndexManager('hnsw')
manager.initialize_metadata('embedding', initial_rows=1_000_000)

# Append new vectors (fast - O(log N))
result = manager.append_batch(
table=current_table,
new_batch=new_vectors,
ef_expansion=200
)

print(f"Updated {result['rows_added']} rows in {result['time_ms']:.2f}ms")
```

### Solution

_No response_

### Anything else?

_No response_

### Are you willing to submit a PR?

- [ ] I'm willing to submit a PR!

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the named integration points in split_read.py and CoreOptions, then review the proposed LanceNativeReader/Writer and FormatLanceReader/Writer components. Done would require the listed phases, including format routing, reader/writer integration, vector and scalar indexing, predicate push-down, and type validation.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
data-engineering, databases
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.