Skip to main content

Overview

Like a data warehouse that separates hot OLAP storage from cold archival tiers, Mixpeek’s storage model lets you pay for serving capacity only when you need it. Storage tiering in MVS automatically manages where your vectors live based on access patterns. MVS separates hot storage (in-memory, sub-10ms) from warm storage (object-storage-backed, ~100ms) — all within a single vector store. This gives you:
  • Durability: vectors are always persisted to object storage, surviving restarts without re-processing
  • Cost efficiency: idle collections automatically move to cheaper storage tiers
  • Transparent search: warm collections remain searchable with no configuration changes

Architecture

Ingest → MVS warm tier (canonical, always written first)
       → MVS hot tier (serving layer, promoted automatically)

Search → Active collections: MVS hot (~8ms)
       → Cold collections: MVS warm (~100ms)
       → Results merged transparently
Every vector is written to the warm (object-storage) tier before the hot tier during ingestion. The warm tier is always the source of truth.

Lifecycle States

Collections have three lifecycle states:
StateMVS HotMVS WarmSearch LatencyCost
ActiveYesYes~8msFull
ColdNoYes~100ms~90% less
ArchivedNoNoN/AMetadata only
  • Active (default): Vectors in both hot and warm tiers — fastest search
  • Cold: Vectors evicted from hot tier, served from warm tier — slower but significantly cheaper
  • Archived: Vectors deleted from all tiers — only MongoDB metadata remains

Transitioning Collections

API

from mixpeek import Mixpeek

client = Mixpeek(api_key="your-api-key")

# Move to cold storage (evict from hot tier)
client.collections.transition_lifecycle(
    collection_id="col_abc123",
    lifecycle_state="cold",
    async_transition=False,  # Block until done
)

# Rehydrate back to hot tier
client.collections.transition_lifecycle(
    collection_id="col_abc123",
    lifecycle_state="active",
)

# Archive (permanent — cannot be undone)
client.collections.transition_lifecycle(
    collection_id="col_abc123",
    lifecycle_state="archived",
)

Valid Transitions

FromToDescription
ActiveColdEvict from hot tier
ActiveArchivedDelete all vectors
ColdActiveRehydrate to hot tier
ColdArchivedDelete warm tier vectors
ArchivedTerminal state
Archived is permanent. Once archived, vectors cannot be recovered. You would need to re-ingest documents to restore the collection.

Async vs Sync

By default, transitions run asynchronously via Celery and return a task_id for tracking. Set async_transition: false to block until completion — useful for scripts and testing.

Tiering Rules

You can configure automatic tiering rules on each collection. In V1, rules are stored but not enforced — manual transitions only. Enforcement via Celery Beat is planned.
# Configure auto-eviction after 30 days of inactivity
client.collections.update("col_abc123", {
    "tiering_rules": [
        {"rule_type": "auto_evict", "enabled": True, "threshold_days": 30},
        {"rule_type": "auto_archive", "enabled": False, "threshold_days": 90},
        {"rule_type": "auto_rehydrate", "enabled": True, "threshold_days": None},
    ]
})
RuleDescription
auto_evictMove to cold after N days of query inactivity
auto_archiveArchive after N days in cold storage
auto_rehydrateRehydrate when a retriever targets the collection
When a retriever targets multiple collections and some are cold, Mixpeek automatically:
  1. Queries MVS hot tier for active collections
  2. Queries MVS warm tier for cold collections
  3. Merges results by score and returns top-k
This is completely transparent — your retriever configuration doesn’t change.
Cold search uses brute-force similarity (cosine/dot/euclidean) over the MVS warm tier. Expect ~100ms latency vs ~8ms for the hot tier. For latency-sensitive workloads, keep collections active.

Search Behavior by Tier

Not all search capabilities are available in every tier. The table below shows what works where:
CapabilityActive (MVS Hot)Cold (MVS Warm)Notes
Semantic search~10ms~100msBrute-force in cold
Keyword/BM25YesNoRehydrate first
Attribute filtersFullExact-match onlyComplex filters ignored in cold
Group-byYesNoRehydrate first
Sparse vectorsYesNoRehydrate first
Score orderingYesYesScores identical across tiers
Result fusion (RRF)YesYesMerged transparently
When a retriever targets a mix of active and cold collections, results are fused using reciprocal rank fusion (RRF). Score ordering is preserved — the same document returns the same score regardless of tier.

Source Tier Attribution

Every search result includes a _source_tier field indicating which storage tier served it. The stage statistics also include a source_tiers breakdown showing how many results came from each tier.
result = client.retrievers.execute(
    retriever_id="ret_abc123",
    inputs={"query": "quarterly revenue"},
)

# Check which tier served each result
for doc in result.results:
    print(f"{doc.document_id}: served from {doc._source_tier}")
    # e.g. "doc_123: served from active"
    # e.g. "doc_456: served from cold"

# Check tier breakdown in stage metadata
for stage in result.stage_statistics.stages:
    tiers = stage.metadata.get("source_tiers", {})
    print(f"Stage '{stage.name}': {tiers}")
    # e.g. "Stage 'feature_search': {'active': 8, 'cold': 2}"

Warnings and Error Handling

Storage tiering surfaces clear errors and warnings when search behavior is affected.

Archived collections

If a retriever targets a collection that has been archived, the API returns a 400 error with the list of archived collection IDs:
{
  "error": {
    "message": "Cannot search archived collections",
    "type": "BadRequestError",
    "details": {
      "archived_collection_ids": ["col_abc123", "col_def456"]
    }
  }
}

Partially archived retrievers

When a retriever targets a mix of active/cold and archived collections, the archived collections are skipped and a warning is included in the stage metadata:
{
  "stage_statistics": {
    "stages": [
      {
        "name": "feature_search",
        "metadata": {
          "warnings": [
            "Skipped archived collections: col_abc123"
          ],
          "source_tiers": { "active": 8, "cold": 2 }
        }
      }
    ]
  }
}

Cold search failures

If a warm-tier query fails for a cold collection, results are returned from the remaining collections with a warning (rather than failing the entire request):
{
  "metadata": {
    "warnings": ["Cold search failed for collection col_xyz: timeout"]
  }
}

Empty rehydrate

When rehydrating a collection that has no vectors in the warm tier (e.g., it was archived and re-created), the response includes a warning:
{
  "lifecycle_state": "active",
  "warning": "No vectors found in warm tier to rehydrate"
}

Limitations

  • Reduced capabilities in cold tier: Keyword/BM25 search, group-by, and sparse vectors are not available for cold collections — see the capability matrix above. Rehydrate to active to restore full functionality.
  • Cold search latency: ~100ms vs ~8ms for active collections (brute-force similarity over MVS warm tier)
  • No automatic TTL in V1: Lifecycle transitions are manual only — tiering rules are stored but not enforced yet

Studio

Navigate to Collections > [collection] > Storage tab to:
  • View current lifecycle state and vector counts
  • Transition between tiers with confirmation dialogs
  • Configure tiering rules (stored for future enforcement)
  • See storage breakdown across MVS tiers (hot vs warm)

Best Practices

Cold-tier idle collections to reduce costs. MVS warm storage is ~90% cheaper than keeping vectors in the hot tier.
  • Set auto_evict to 30 days for collections that see periodic traffic
  • Keep latency-sensitive production collections active
  • Use async_transition: true for large collections (millions of vectors)
  • Monitor the Storage tab in Studio to identify candidates for cold-tiering