diff --git a/core/dr-catalog/src/merge.rs b/core/dr-catalog/src/merge.rs index fcc4947..2ecdb58 100644 --- a/core/dr-catalog/src/merge.rs +++ b/core/dr-catalog/src/merge.rs @@ -1081,6 +1081,8 @@ fn match_faces(tx: &Connection) -> Result, C type Boxed = (i64, f32, f32, f32, f32); + ensure_face_box_index(tx); + // Local faces, grouped by the photograph's cross-device id. let mut local: std::collections::HashMap<(i64, String), Vec> = std::collections::HashMap::new(); @@ -1153,6 +1155,30 @@ fn match_faces(tx: &Connection) -> Result, C Ok(map) } +/// The index the local half of [`match_faces`] is read from: every column +/// it asks of a face, so the walk touches no `faces` row. +/// +/// A face row is eight kilobytes and `model_id` sits past the embedding, so +/// reading it opened the row's overflow pages: 38 ms of every sync pass on +/// the reference library, to read 19,000 boxes. From this index, 8 ms. The +/// other device's half has no such index to use -- it is a snapshot made by +/// whatever build that device runs -- but its rows have had their crops +/// stripped, which is most of their width. +/// +/// Created on first use rather than by a migration, like +/// `keywords::ensure_term_index` and for the reason given there: a new +/// schema version makes older builds refuse this catalog's snapshot, and an +/// extra index is invisible to them. The first merge after an upgrade pays +/// for building it, once. A failure is logged and the merge goes on reading +/// rows, as it did before. +fn ensure_face_box_index(tx: &Connection) { + if let Err(e) = tx.execute_batch( + "CREATE INDEX IF NOT EXISTS main.faces_box ON faces(image_id, model_id, x, y, w, h);", + ) { + log::warn!("merge: could not create faces_box: {e}"); + } +} + /// Intersection over union of two `(x, y, w, h)` boxes. fn iou(a: (f32, f32, f32, f32), b: (f32, f32, f32, f32)) -> f32 { let x0 = a.0.max(b.0);