Vector search on object storage
A self-hosted, multi-tenant hybrid search engine for teams that want vector and full-text retrieval on object storage. LanceDB provides the search layer; a tiered RAM + NVMe cache keeps hot traffic fast while inactive namespaces retain near-zero idle cost.
How it works
Firn stores every namespace under its own object-storage prefix using LanceDB as the storage engine. A tiered cache powered by foyer sits in front of the backend, serving repeated queries from RAM or NVMe in microseconds instead of milliseconds. Single-vector queries can also opt into semantic caching, where a near-duplicate query may reuse a previous top-k result when its vector clears your cosine-similarity threshold. Writes automatically invalidate both cache layers for the affected namespace, because the cache key carries the Lance table version and every committed write advances it.
The result: your data lives cheaply on object storage, but hot and near-duplicate queries can feel local. The /metrics endpoint shows exactly how many backend requests the exact and semantic cache layers are saving you.
Firn runs against AWS S3, MinIO, Cloudflare R2, Tigris, DigitalOcean Spaces, or native Google Cloud Storage. Switching backends is an environment-variable change; see the backend configuration recipes. The local quickstart uses a published Docker image and MinIO, so there is no Rust build step.
Production hardening includes optional bearer-token authentication, read/write and admin scope separation, rate limiting, Prometheus metrics, and background operation tracking. See configuration and deployment.
Documentation
Quickstart
Docker Compose up, first upsert, first query, and check your metrics.
API Reference
The complete API surface with request and response schemas, status codes, and curl examples.
Configuration
Environment variables for object-storage backends (S3-family and native GCS), cache sizing, bind address, and logging.
Architecture
Tiered storage, cache invalidation, namespace isolation, and the query and write paths.
Deployment
Run locally with Docker Compose, deploy to production with the multi-stage Dockerfile, against AWS S3, MinIO, Cloudflare R2, Tigris, DigitalOcean Spaces, or native Google Cloud Storage.
Monitoring
Prometheus metrics, PromQL examples, alerting rules, and Grafana dashboard guidance.
Who Firn is for
Firn is designed for self-hosted and BYO-cloud search: multi-tenant SaaS products, private RAG systems, and teams replacing an always-on OpenSearch, Elasticsearch, Vespa, or custom vector-search service where idle cost and operational weight matter.
It is not a hosted SaaS product and it does not try to replace every search platform. OpenSearch and Elasticsearch have broader operational ecosystems; Vespa has deeper ranking and real-time serving capabilities. Firn's focus is a smaller, deployable service with explicit object-storage economics, hybrid retrieval, and namespace isolation.
Start in minutes
git clone https://github.com/gordonmurray/firnflow
cd firnflow
docker compose up -d
curl http://localhost:3000/health
Then follow the quickstart to insert vectors, run hybrid search, and verify cache savings. For embedded Python use, install firn from PyPI.
Performance on real AWS S3
Benchmarked with 100,000 vectors at 1536 dimensions (OpenAI embedding size) against eu-west-1 S3.
| Phase | Path | p50 latency |
|---|---|---|
| Linear scan | Cold (S3) | 25.14 s |
| Linear scan | Warm (cache) | 66 µs |
| IVF_PQ indexed | Cold (S3) | 979 ms |
| IVF_PQ indexed | Warm (cache) | 72 µs |
Without an index, each cache miss costs 25 seconds. With IVF_PQ, that drops to under 1 second. For repeated queries the cache serves the stored result without re-running the search, bringing latency to microseconds regardless of index type.
Key technologies
- LanceDB - vector and BM25 search engine that runs natively on object storage.
- foyer - hybrid cache (RAM + NVMe) with LFU/LRU eviction policies.
- axum - async Rust HTTP framework.
- Prometheus - native metrics for cache hits, misses, and object-storage cost savings.