Skip to main content

lattice_embed/backfill/
types.rs

1//! Backfill types: routing decisions, phases, and configuration.
2
3use serde::{Deserialize, Serialize};
4
5use crate::model::EmbeddingModel;
6
7/// Routing decision for an embedding request during migration.
8///
9/// Determines which model should handle an embedding operation based on
10/// the current migration state and the nature of the request.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum EmbeddingRoute {
13    /// Use the current (legacy) model -- no migration active or migration
14    /// incomplete for this operation type.
15    Legacy,
16    /// Use the new (target) model -- migration complete or sufficient
17    /// progress for query routing.
18    Target,
19    /// Embed with both models (dual-write) -- during active migration
20    /// for new documents to ensure both indexes stay current.
21    DualWrite,
22}
23
24/// Phase of the migration lifecycle for routing decisions.
25///
26/// Distinguishes between normal operation, active migration, and the
27/// post-cutover rollback window where dual-writing continues to enable
28/// safe rollback without data loss.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum RoutingPhase {
32    /// Normal operation, no migration active.
33    Stable,
34    /// Active migration in progress.
35    Migrating,
36    /// Post-cutover rollback window (still dual-writing).
37    ///
38    /// During this phase, queries use the target model but writes go to
39    /// both models. This enables instant rollback without losing atoms
40    /// created after cutover.
41    RollbackWindow,
42}
43
44/// Configuration for embedding request routing.
45///
46/// Separates query routing from write routing to enable true rollback.
47/// During the rollback window after cutover, queries use the new model
48/// but writes continue to both models, ensuring atoms created after
49/// cutover are present in both indexes.
50///
51/// # Example
52///
53/// ```rust
54/// use lattice_embed::backfill::{EmbeddingRoutingConfig, RoutingPhase};
55/// use lattice_embed::EmbeddingModel;
56///
57/// let config = EmbeddingRoutingConfig {
58///     query_model: EmbeddingModel::BgeBaseEnV15,
59///     write_models: vec![EmbeddingModel::BgeSmallEnV15, EmbeddingModel::BgeBaseEnV15],
60///     phase: RoutingPhase::RollbackWindow,
61///     migration_id: Some("mig-001".to_string()),
62/// };
63///
64/// assert!(config.write_models.len() > 1); // dual-write active
65/// ```
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct EmbeddingRoutingConfig {
68    /// Which model to query against.
69    pub query_model: EmbeddingModel,
70    /// Which models to write to (may be multiple for dual-write).
71    pub write_models: Vec<EmbeddingModel>,
72    /// Current routing phase.
73    pub phase: RoutingPhase,
74    /// Migration ID if in migration/rollback phase.
75    pub migration_id: Option<String>,
76}
77
78/// Configuration for the backfill coordinator.
79///
80/// Controls batch sizing, concurrency, dual-write behavior, and the
81/// threshold at which queries switch to the target model.
82///
83/// # Example
84///
85/// ```rust
86/// use lattice_embed::backfill::BackfillConfig;
87///
88/// let config = BackfillConfig {
89///     batch_size: 200,
90///     max_concurrent: 8,
91///     dual_write: true,
92///     target_query_threshold: 0.9,
93///     rollback_window_secs: 3600, // 1 hour
94/// };
95///
96/// assert_eq!(config.batch_size, 200);
97/// ```
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct BackfillConfig {
100    /// Batch size for backfill operations.
101    pub batch_size: usize,
102    /// Maximum concurrent backfill batches.
103    pub max_concurrent: usize,
104    /// Whether new documents should be dual-written during migration.
105    pub dual_write: bool,
106    /// Minimum progress fraction before allowing target-only queries `[0.0, 1.0]`.
107    ///
108    /// When the migration progress reaches this threshold, query routing
109    /// switches from legacy to target embeddings.
110    pub target_query_threshold: f64,
111    /// Duration in seconds to continue dual-writing after cutover.
112    ///
113    /// During the rollback window, queries use the new target model but
114    /// writes continue to both models. This ensures atoms created after
115    /// cutover exist in both indexes, enabling instant rollback without
116    /// data loss.
117    ///
118    /// Default: 86400 seconds (24 hours).
119    pub rollback_window_secs: u64,
120}
121
122impl Default for BackfillConfig {
123    fn default() -> Self {
124        Self {
125            batch_size: 100,
126            max_concurrent: 4,
127            dual_write: true,
128            target_query_threshold: 0.8,
129            rollback_window_secs: 86400, // 24 hours
130        }
131    }
132}