Prism Hybrid Search Engine
Production guide for operating Prism - a hybrid search engine combining full-text (BM25) and vector search (HNSW) for AI/RAG applications.
Components
| Binary | Purpose |
|---|---|
prism-server | HTTP API server (main process) |
prism-cli | Collection management, backup, benchmarks |
prism-import | Migrate from Elasticsearch |
Starting & Stopping
# Start with config prism-server -c /etc/prism/prism.toml # Override bind address prism-server -c prism.toml --host 0.0.0.0 -p 3080 # Debug mode RUST_LOG=debug prism-server # JSON logging (production) LOG_FORMAT=json prism-server
Signals:
- •
SIGTERM/SIGINT: Graceful shutdown - •
SIGHUP: Reload configuration (encryption keys, etc.)
Health check:
curl http://localhost:3080/health
# {"status": "ok"}
Configuration (prism.toml)
Minimal
[server] bind_addr = "127.0.0.1:3080" [storage] data_dir = "/var/lib/prism"
Production
[server]
bind_addr = "0.0.0.0:3080"
[server.tls]
enabled = true
bind_addr = "0.0.0.0:3443"
cert_path = "/etc/prism/cert.pem"
key_path = "/etc/prism/key.pem"
[server.cors]
enabled = true
origins = ["https://app.example.com"]
[storage]
data_dir = "/var/lib/prism"
[observability]
log_format = "json"
log_level = "info"
metrics_enabled = true
[security]
enabled = true
[[security.api_keys]]
key = "${PRISM_ADMIN_KEY}"
name = "admin"
roles = ["admin"]
[[security.api_keys]]
key = "${PRISM_APP_KEY}"
name = "app"
roles = ["reader"]
[security.roles.admin.collections]
"*" = ["read", "write", "delete", "admin"]
[security.roles.reader.collections]
"*" = ["read"]
[security.audit]
enabled = true
Creating Collections
Schemas are YAML files in <data_dir>/schemas/. Server loads them on startup.
Text-Only Collection
# /var/lib/prism/schemas/articles.yaml
collection: articles
backends:
text:
fields:
- name: id
type: string
stored: true
indexed: true
- name: title
type: text
- name: content
type: text
- name: category
type: string
Hybrid Collection (Text + Vector)
# /var/lib/prism/schemas/knowledge.yaml
collection: knowledge
backends:
text:
fields:
- name: id
type: string
- name: content
type: text
vector:
embedding_field: content_vector
dimension: 384
distance: cosine
embedding_generation:
enabled: true
model: all-MiniLM-L6-v2
source_field: content
target_field: content_vector
hybrid:
default_strategy: rrf
rrf_k: 60
After adding schema: Restart server or wait for hot-reload.
Verify:
curl http://localhost:3080/admin/collections curl http://localhost:3080/collections/knowledge/schema
Indexing Documents
Via API
curl -X POST http://localhost:3080/collections/articles/documents \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PRISM_ADMIN_KEY" \
-d '[
{"id": "1", "title": "First Article", "content": "..."},
{"id": "2", "title": "Second Article", "content": "..."}
]'
Via CLI (Bulk)
# JSONL format: one JSON object per line prism-cli document import -c articles -f articles.jsonl --batch-size 500
Searching
Basic Search
curl -X POST http://localhost:3080/collections/articles/search \
-H "Content-Type: application/json" \
-d '{"query": "search terms", "limit": 10}'
Hybrid Search (with Highlighting)
curl -X POST http://localhost:3080/collections/knowledge/search \
-d '{
"query": "machine learning concepts",
"limit": 10,
"merge_strategy": "rrf",
"highlight": {
"fields": ["content"],
"pre_tag": "<em>",
"post_tag": "</em>",
"fragment_size": 150
}
}'
Tune Hybrid Weights
{
"query": "specific keyword",
"merge_strategy": "weighted",
"text_weight": 0.7,
"vector_weight": 0.3
}
| Use Case | text_weight | vector_weight |
|---|---|---|
| Exact terms (code, IDs) | 0.7-0.8 | 0.2-0.3 |
| Balanced | 0.5 | 0.5 |
| Semantic (Q&A, chat) | 0.2-0.3 | 0.7-0.8 |
Multi-Collection Search
# Wildcard patterns
curl -X POST "http://localhost:3080/logs-*/_search" -d '{"query": "error"}'
# Comma-separated
curl -X POST "http://localhost:3080/logs-2026-01,logs-2026-02/_search" -d '{"query": "error"}'
Aggregations
curl -X POST http://localhost:3080/collections/products/aggregate \
-d '{
"query": "*",
"aggregations": [
{"name": "total", "type": "count"},
{"name": "by_category", "type": "terms", "field": "category", "size": 10},
{"name": "price_stats", "type": "stats", "field": "price"},
{"name": "price_histogram", "type": "histogram", "field": "price", "interval": 50}
]
}'
Storage Backends
Backend Types
| Backend | When to Use |
|---|---|
local | Single node, development (default) |
s3 | Cloud storage, durability, scaling |
cached | L1 local + L2 S3 (best for production) |
compressed | Reduce storage (LZ4 fast, Zstd balanced) |
encrypted | Compliance, multi-tenant, sensitive data |
Local (Default)
[storage] backend = "local" data_dir = "/var/lib/prism"
S3
[unified_storage] backend = "s3" [unified_storage.s3] bucket = "my-prism-bucket" region = "us-east-1"
Credentials: AWS environment variables, IAM role, or explicit config.
Cached (L1 Local + L2 S3)
[unified_storage] backend = "cached" [unified_storage.s3] bucket = "my-bucket" region = "us-east-1" [unified_storage.cache] l1_path = "/var/lib/prism/cache" l1_max_size_gb = 50 write_through = true
Compressed
[storage] backend = "compressed" [storage.compressed] algorithm = "zstd" # or "lz4", "zstd:9" min_size = 1024 [storage.compressed.inner] backend = "local" data_dir = "/var/lib/prism"
Encrypted
[storage] backend = "encrypted" [storage.encrypted] key_source = "env" key_env_var = "PRISM_ENCRYPTION_KEY" [storage.encrypted.inner] backend = "local" data_dir = "/var/lib/prism"
Set key: export PRISM_ENCRYPTION_KEY="<64 hex chars>"
Layered (Encrypted + Compressed + S3)
[storage] backend = "encrypted" [storage.encrypted] key_source = "env" key_env_var = "PRISM_ENCRYPTION_KEY" [storage.encrypted.inner] backend = "compressed" algorithm = "zstd" [storage.encrypted.inner.inner] backend = "s3" [storage.encrypted.inner.inner.s3] bucket = "secure-bucket" region = "us-east-1"
Encryption Operations
Generate Key
curl -X POST http://localhost:3080/_admin/encryption/generate-key
# {"key": "a1b2c3d4...64 hex chars", "algorithm": "AES-256-GCM"}
Save this key securely! Prism never stores keys.
Encrypted Export (Runtime Key)
Export collection with key provided at runtime (no restart needed):
curl -X POST http://localhost:3080/_admin/export/encrypted \
-H "Content-Type: application/json" \
-d '{
"collection": "sensitive-data",
"key": "<64 hex chars>",
"output_path": "/backup/sensitive.enc"
}'
Encrypted Import
curl -X POST http://localhost:3080/_admin/import/encrypted \
-H "Content-Type: application/json" \
-d '{
"input_path": "/backup/sensitive.enc",
"key": "<64 hex chars>",
"target_collection": "sensitive-restored"
}'
Backup & Restore
Export Formats
| Format | File | Use Case |
|---|---|---|
| Portable | .prism.jsonl | Cross-version, human-readable |
| Snapshot | .tar.zst | Fast, same-version only |
| Encrypted | .enc | Secure cloud storage |
CLI Backup
# Portable (cross-version safe) prism-cli collection export articles -o articles.prism.jsonl # Snapshot (fast, same-version) prism-cli collection export articles -o articles.tar.zst --format snapshot # Restore prism-cli collection restore -f articles.prism.jsonl prism-cli collection restore -f articles.tar.zst --format snapshot
Encrypted Cloud Backup Script
#!/bin/bash
KEY_FILE="/secure/prism-key.txt"
BUCKET="s3://my-backups/prism"
# Generate or read key
if [ ! -f "$KEY_FILE" ]; then
curl -s -X POST http://localhost:3080/_admin/encryption/generate-key | jq -r .key > "$KEY_FILE"
chmod 600 "$KEY_FILE"
fi
KEY=$(cat "$KEY_FILE")
DATE=$(date +%Y-%m-%d)
for collection in $(curl -s http://localhost:3080/admin/collections | jq -r '.[]'); do
curl -s -X POST http://localhost:3080/_admin/export/encrypted \
-d "{\"collection\": \"$collection\", \"key\": \"$KEY\", \"output_path\": \"/tmp/$collection.enc\"}"
aws s3 cp "/tmp/$collection.enc" "$BUCKET/$DATE/$collection.enc"
rm "/tmp/$collection.enc"
done
Disaster Recovery (Disk Full)
When disk unexpectedly fills:
- •
Generate key:
bashcurl -X POST http://localhost:3080/_admin/encryption/generate-key > /secure/emergency-key.json
- •
Export to external storage:
bashKEY=$(jq -r .key /secure/emergency-key.json) for c in logs metrics events; do curl -X POST http://localhost:3080/_admin/export/encrypted \ -d "{\"collection\": \"$c\", \"key\": \"$KEY\", \"output_path\": \"/mnt/external/$c.enc\"}" done - •
Delete local to free space:
bashprism-cli collection delete logs metrics events
- •
Restore when ready:
bashfor c in logs metrics events; do curl -X POST http://localhost:3080/_admin/import/encrypted \ -d "{\"input_path\": \"/mnt/external/$c.enc\", \"key\": \"$KEY\"}" done
Migration
Between Storage Backends (Local → S3)
# 1. Export all collections for c in $(curl -s http://localhost:3080/admin/collections | jq -r '.[]'); do prism-cli document export -c "$c" -o "backup-$c.jsonl" done # 2. Stop server, update prism.toml to S3 backend # 3. Start server, re-import for f in backup-*.jsonl; do c=$(echo "$f" | sed 's/backup-\(.*\)\.jsonl/\1/') prism-cli document import -c "$c" -f "$f" done
Between Prism Versions
Use portable format for cross-version compatibility:
# Old version prism-cli collection export myindex -o myindex.prism.jsonl # New version prism-cli collection restore -f myindex.prism.jsonl
From Elasticsearch
prism-import --source http://elasticsearch:9200 --index logs-* --target http://localhost:3080
Monitoring
Enable Metrics
[observability] metrics_enabled = true log_format = "json" log_level = "info"
Prometheus Scrape
scrape_configs:
- job_name: 'prism'
static_configs:
- targets: ['prism:3080']
metrics_path: /metrics
scrape_interval: 15s
Key Metrics
| Metric | What to Watch |
|---|---|
prism_search_duration_seconds | p99 latency spikes |
prism_search_total{status="error"} | Error rate |
prism_embedding_cache_hits_total | Low hit rate = high embedding cost |
prism_index_documents_total | Indexing throughput |
Grafana Queries
# Search p99 latency
histogram_quantile(0.99, sum(rate(prism_search_duration_seconds_bucket[5m])) by (le))
# Error rate percentage
100 * sum(rate(prism_search_total{status="error"}[5m])) / sum(rate(prism_search_total[5m]))
# Cache hit ratio
sum(rate(prism_embedding_cache_hits_total[5m])) /
(sum(rate(prism_embedding_cache_hits_total[5m])) + sum(rate(prism_embedding_cache_misses_total[5m])))
Alerting Rules
groups:
- name: prism
rules:
- alert: PrismHighLatency
expr: histogram_quantile(0.99, sum(rate(prism_search_duration_seconds_bucket[5m])) by (le)) > 1
for: 5m
labels:
severity: warning
- alert: PrismHighErrorRate
expr: sum(rate(prism_search_total{status="error"}[5m])) / sum(rate(prism_search_total[5m])) > 0.05
for: 5m
labels:
severity: critical
- alert: PrismDown
expr: up{job="prism"} == 0
for: 1m
labels:
severity: critical
Clustering
Deployment Modes
| Mode | Use Case |
|---|---|
| Single node | Development, small datasets |
| Federation | Read scaling, simple HA |
| Cluster | Full distributed, auto-failover |
Node Discovery
[federation.discovery] backend = "static" # or "dns", "gossip" nodes = ["node1:3000", "node2:3000"] # For Kubernetes backend = "dns" dns_name = "prism-headless.default.svc.cluster.local"
Inter-Node Communication
[cluster.transport] transport = "quic" bind_port = 7000 cert_path = "/etc/prism/cluster-cert.pem" key_path = "/etc/prism/cluster-key.pem"
Replication
[collection.replication] factor = 2 min_replicas_for_write = 1 # Zone-aware placement [node] id = "node-1" zone = "eu-west-1a"
Index Maintenance
Optimize (Merge Segments)
prism-cli index optimize -c articles
When to run:
- •After bulk imports
- •After many deletes
- •Weekly/monthly maintenance
- •When search performance degrades
Cache Management
# View stats prism-cli cache-stats -p /var/lib/prism/cache # Clear old entries prism-cli cache-clear -p /var/lib/prism/cache --older-than-days 30 # Clear all prism-cli cache-clear -p /var/lib/prism/cache
Troubleshooting
Server Won't Start
# Check config syntax prism-server -c prism.toml --check # Verbose logging RUST_LOG=debug prism-server -c prism.toml
Common causes:
- •Port already in use
- •Invalid TOML syntax
- •Schema validation errors
- •Missing directories
Slow Searches
- •
Check segment count:
bashprism-cli collection inspect -n articles -v
Many segments = run optimize
- •
Check cache hit rate:
bashcurl http://localhost:3080/stats/cache
Low hit rate = cold cache or missing cache config
- •
Benchmark:
bashprism-cli benchmark -c articles -q queries.txt -r 50
Decryption Failed
- •Wrong key (keys must match exactly)
- •Truncated/corrupted file
- •Key must be 64 hex characters (256 bits)
Schema Conflicts on Import
# Delete conflicting collection first prism-cli collection delete myindex # Then restore prism-cli collection restore -f backup.prism.jsonl
Out of Memory
- •Reduce batch sizes for imports
- •Enable compression backend
- •Increase resource limits
- •Check vector dimension (384d = ~2KB/doc, 1536d = ~8KB/doc)
API Quick Reference
| Endpoint | Method | Description |
|---|---|---|
/health | GET | Health check |
/metrics | GET | Prometheus metrics |
/admin/collections | GET | List collections |
/collections/:c/schema | GET | Get schema |
/collections/:c/stats | GET | Collection stats |
/collections/:c/search | POST | Search |
/collections/:c/documents | POST | Index documents |
/collections/:c/documents/:id | GET | Get document |
/collections/:c/aggregate | POST | Aggregations |
/collections/:c/_suggest | POST | Autocomplete |
/collections/:c/_mlt | POST | More like this |
/:collections/_search | POST | Multi-collection search |
/_admin/export/encrypted | POST | Encrypted export |
/_admin/import/encrypted | POST | Encrypted import |
/_admin/encryption/generate-key | POST | Generate AES key |
Environment Variables
| Variable | Description |
|---|---|
RUST_LOG | Log level (info, debug, warn, prism=debug) |
LOG_FORMAT | pretty or json |
PRISM_ENCRYPTION_KEY | AES-256 key (64 hex chars) |
PRISM_ADMIN_KEY | Admin API key |
AWS_ACCESS_KEY_ID | S3 credentials |
AWS_SECRET_ACCESS_KEY | S3 credentials |
AWS_REGION | S3 region |