Every `Store` built its own connection with its own caches and discarded them on close, so the index a vector query loads was refetched by the next connection. On object storage that first fetch dominates: measured on a ~500k-chunk 2560-dim corpus over a ~200ms link, the first query cost ~41s and the second ~3s, and a new connection reusing the session cost ~7s instead of ~47s. `connect_lancedb` now passes a process-wide session, keyed on the configured cache sizes so a caller asking for different sizes gets its own. Also sets `read_consistency_interval`, defaulting to 30s. It was None, meaning a connection never re-checked for other processes' writes. Per-call connections hid that; a shared session makes connections long-lived enough for a reader to go stale against the ingester. All three settings reject negatives at the config boundary. A negative cache size raises OverflowError and a negative interval panics inside Lance, so neither is catchable further in. Zero stays valid for both: no cache, and check on every read. The routing tests now assert the kwargs they care about rather than the full call signature, since every connection carries the two new kwargs.
193 lines
9.9 KiB
Markdown
193 lines
9.9 KiB
Markdown
# Database and Storage
|
|
|
|
## Local Storage
|
|
|
|
By default, `haiku.rag` uses a local LanceDB database:
|
|
|
|
```yaml
|
|
storage:
|
|
data_dir: /path/to/data # Empty = use default platform location
|
|
auto_vacuum: true # Enable automatic vacuuming after operations
|
|
vacuum_retention_seconds: 86400 # Cleanup threshold in seconds
|
|
```
|
|
|
|
- **data_dir**: Directory for local database storage. When empty, uses platform-specific default locations
|
|
- **auto_vacuum**: When enabled (default), automatically runs vacuum after document create/update/delete operations and database rebuilds. Background vacuums are throttled to at most one every 5 minutes, so sustained ingestion does not trigger continuous compaction, and a final vacuum runs when the client closes. Set to `false` to disable automatic vacuuming and rely on manual `haiku-rag vacuum` commands only. Disabling can help avoid potential crashes in high-concurrency scenarios
|
|
- **vacuum_retention_seconds**: When vacuum runs, old table versions older than this threshold are removed. Default: 86400 seconds (1 day). Set to 0 for aggressive cleanup (removes all old versions immediately)
|
|
|
|
!!! warning "Vacuum Retention Threshold"
|
|
The `vacuum_retention_seconds` value should be larger than the typical time it takes to process and write a document. If a concurrent operation is in progress while vacuum runs, setting this value too low can cause race conditions where vacuum removes table versions that an in-flight operation still needs. The default of 86400 seconds (1 day) is conservative and safe for most use cases.
|
|
|
|
### Vacuum Memory Requirements
|
|
|
|
Vacuum compacts small data files into larger ones. LanceDB targets roughly one million rows per fragment, which a `documents` table holding multi-megabyte docling blobs never reaches, so each vacuum that follows new documents re-merges the whole existing fragment rather than only the new ones. Peak memory therefore scales with the total size of the `documents` table, not with how much was added.
|
|
|
|
Measured peak resident memory is about 5x the size of the `documents` table's data files. An 8.8 GB table peaked at 48.7 GB. Plan for **6x the size of `documents/` on disk** as available RAM, or the vacuum will be killed by the OOM killer partway through.
|
|
|
|
Check the current size with:
|
|
|
|
```bash
|
|
du -sh /path/to/database.lancedb/documents.lance
|
|
```
|
|
|
|
If that number times six exceeds available RAM, use one of:
|
|
|
|
- Reduce `images_scale` (see [Image Settings](processing.md#image-settings)). Rendered page rasters dominate the size of `documents`, and their byte cost falls with the square of the scale factor.
|
|
- Set `generate_page_images: false` if visual grounding through `visualize_chunk()` is not needed. This removes page rasters entirely.
|
|
- Set `auto_vacuum: false` and run `haiku-rag vacuum` manually when the machine is otherwise idle, so the peak does not land alongside ingestion.
|
|
|
|
This is an upstream limitation rather than a `haiku.rag` setting. Compaction bounds itself by row count instead of bytes, and LanceDB's async API exposes no batch size or fragment target to override it. Tracked at [lancedb/lancedb#2325](https://github.com/lancedb/lancedb/issues/2325). The requirement above will drop once compaction batches by bytes.
|
|
|
|
## Database Creation
|
|
|
|
Databases must be explicitly created before use:
|
|
|
|
**CLI:**
|
|
```bash
|
|
# Create in default location (see Configuration File Locations below)
|
|
haiku-rag init
|
|
|
|
# Create at custom path
|
|
haiku-rag init --db /path/to/database.lancedb
|
|
```
|
|
|
|
**Python:**
|
|
```python
|
|
# Create at custom path
|
|
async with HaikuRAG("/path/to/database.lancedb", create=True) as client:
|
|
...
|
|
|
|
# Create in default location
|
|
async with HaikuRAG(create=True) as client:
|
|
...
|
|
```
|
|
|
|
The [default location](index.md#configuration-file-locations) is platform-specific (e.g., `~/Library/Application Support/haiku.rag/` on macOS).
|
|
|
|
Operations on non-existent databases raise `FileNotFoundError`. This prevents accidental database creation from typos or misconfigured paths.
|
|
|
|
## Remote Storage
|
|
|
|
For remote storage, use the `lancedb` settings with various backends:
|
|
|
|
```yaml
|
|
# LanceDB Cloud
|
|
lancedb:
|
|
uri: db://your-database-name
|
|
api_key: your-api-key
|
|
region: us-west-2 # optional
|
|
|
|
# Amazon S3
|
|
lancedb:
|
|
uri: s3://my-bucket/my-table
|
|
storage_options:
|
|
region: us-east-1
|
|
|
|
# Amazon S3 with explicit credentials
|
|
lancedb:
|
|
uri: s3://my-bucket/my-table
|
|
storage_options:
|
|
aws_access_key_id: YOUR_ACCESS_KEY
|
|
aws_secret_access_key: YOUR_SECRET_KEY
|
|
region: us-east-1
|
|
|
|
# S3-compatible (SeaweedFS, Tigris, etc.)
|
|
lancedb:
|
|
uri: s3://my-bucket/my-table
|
|
storage_options:
|
|
endpoint: http://localhost:8333
|
|
aws_access_key_id: YOUR_ACCESS_KEY
|
|
aws_secret_access_key: YOUR_SECRET_KEY
|
|
region: us-east-1
|
|
allow_http: "true"
|
|
|
|
# Azure Blob Storage
|
|
lancedb:
|
|
uri: az://my-container/my-table
|
|
|
|
# Google Cloud Storage
|
|
lancedb:
|
|
uri: gs://my-bucket/my-table
|
|
|
|
# HDFS
|
|
lancedb:
|
|
uri: hdfs://namenode:port/path/to/table
|
|
```
|
|
|
|
- **LanceDB Cloud** (`db://`): Requires `api_key` and `region`. Table optimization and indexing are managed server-side.
|
|
- **Object storage** (`s3://`, `gs://`, `az://`, `hdfs://`): Uses `storage_options` for credentials and endpoint configuration. Authentication can also be provided via environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, etc.) or cloud provider SDK defaults (AWS CLI, Azure CLI, gcloud).
|
|
- **S3-compatible stores** (MinIO, Tigris, etc.): Set `endpoint` in `storage_options`. When using `http://` endpoints, also set `allow_http: "true"`.
|
|
|
|
The `storage_options` keys are case-insensitive and passed directly to the underlying object store library. Available keys depend on the backend. See the [LanceDB storage docs](https://lancedb.com/docs/storage/) for details.
|
|
|
|
**Note:** Table optimization is automatically handled by LanceDB Cloud (`db://` URIs) and is disabled for better performance. For object storage backends (S3, Azure, GCS), optimization and vector indexing are still performed normally.
|
|
|
|
### Caching and Read Consistency
|
|
|
|
```yaml
|
|
lancedb:
|
|
read_consistency_interval_seconds: 30 # null to never re-check
|
|
index_cache_size_bytes: 536870912 # null for the LanceDB default
|
|
metadata_cache_size_bytes: 268435456
|
|
```
|
|
|
|
- **read_consistency_interval_seconds**: how often a connection checks for writes from another process. `null` never checks, so a long-lived reader never sees the ingester's writes. `0` checks on every read.
|
|
- **index_cache_size_bytes** / **metadata_cache_size_bytes**: sizes for the caches held by the LanceDB session, which is shared across every connection in the process. The first vector query loads the index into it, so on object storage the cache is what stops the next connection refetching it. Size it for the total set of indexes a process keeps warm, against the memory available to it.
|
|
|
|
### Deployment Pattern: One Writer, Many Readers
|
|
|
|
LanceDB on S3 supports **exactly one writer + N readers** per database URI. Multiple writers against the same URI can race on the manifest commit and corrupt state. This is a LanceDB property, not something `haiku.rag` enforces.
|
|
|
|
The recommended layout for production is "different buckets, same account, separate IAM roles per process":
|
|
|
|
- **Ingestion process** — IAM role with `s3:Get/List` on the documents bucket and `s3:Get/Put/Delete` on the LanceDB bucket. Runs `haiku-ingester serve` (with `ingester.sources[type=s3]` pointing at the documents bucket). Exactly one such process per LanceDB URI.
|
|
- **Consumer processes** (1..N) — IAM role with `s3:Get/List` on the LanceDB bucket only. Run `haiku-rag --read-only mcp`, the chat TUI, etc. They never see the documents bucket.
|
|
|
|
Each process picks up its own credentials from the AWS default chain (env vars, IAM instance role, AWS profile), so no credentials are hard-coded in the configuration files.
|
|
|
|
## Vector Indexing
|
|
|
|
Configure vector search settings:
|
|
|
|
```yaml
|
|
search:
|
|
vector_index_metric: cosine # cosine, l2, or dot
|
|
vector_refine_factor: 30 # Re-ranking factor for accuracy
|
|
```
|
|
|
|
For search behavior settings (`limit`, `max_context_chars`), see [Search and Question Answering](qa.md#search-settings).
|
|
|
|
- **vector_index_metric**: Distance metric for vector similarity:
|
|
- `cosine`: Cosine similarity (default, best for most embeddings)
|
|
- `l2`: Euclidean distance
|
|
- `dot`: Dot product similarity
|
|
- **vector_refine_factor**: Improves accuracy when using a vector index by retrieving `refine_factor * limit` candidates (using approximate search) and re-ranking them with exact distances. Higher values increase accuracy but slow down queries. Default: 30
|
|
- **Only applies with a vector index** - has no effect on brute-force search, which already returns exact results
|
|
|
|
!!! note
|
|
Vector indexes are only necessary for large datasets with over 100,000 chunks. For smaller datasets, LanceDB's brute-force kNN search provides exact results with good performance. Only create an index if you notice search performance degradation on large datasets.
|
|
|
|
**Index creation:**
|
|
|
|
Vector indexes are **not created automatically** during document ingestion to avoid slowing down the process. After you've added documents (at least 256 chunks required), create the index manually:
|
|
|
|
```bash
|
|
haiku-rag create-index
|
|
```
|
|
|
|
This command:
|
|
- Checks if you have enough data (minimum 256 chunks)
|
|
- Creates an IVF_PQ index for fast approximate nearest neighbor (ANN) search
|
|
- Uses LanceDB's automatic parameter calculation based on your dataset size and vector dimensions
|
|
|
|
**Re-indexing:**
|
|
|
|
Indexes are not automatically updated when you add new documents. After adding a significant amount of new data:
|
|
|
|
```bash
|
|
haiku-rag create-index # Rebuilds the index with all data
|
|
```
|
|
|
|
Searches still work with stale indexes - LanceDB uses the index for old data (fast ANN) and brute-force kNN for new unindexed rows, then combines the results. However, performance degrades as more unindexed data accumulates.
|
|
|
|
For datasets with fewer than 256 chunks, searches use brute-force kNN scans (exact nearest neighbors, 100% recall) which work well for small datasets but don't scale beyond a few hundred thousand vectors.
|