1use std::sync::Arc;
7use std::sync::atomic::{AtomicBool, Ordering};
8
9use arc_swap::ArcSwap;
10use parking_lot::RwLock;
11
12use crate::directories::DirectoryWriter;
13use crate::dsl::Schema;
14use crate::error::Result;
15
16use super::Searcher;
17use super::searcher::SearcherResources;
18
19struct SearcherState<D: DirectoryWriter + 'static> {
25 searcher: Arc<Searcher<D>>,
26 segment_ids: Vec<String>,
27 publication_id: u64,
28}
29
30struct ReloadGuard<'a>(&'a AtomicBool);
34
35impl Drop for ReloadGuard<'_> {
36 fn drop(&mut self) {
37 self.0.store(false, Ordering::Release);
38 }
39}
40
41pub struct IndexReader<D: DirectoryWriter + 'static> {
42 schema: Arc<Schema>,
44 segment_manager: Arc<crate::merge::SegmentManager<D>>,
46 state: ArcSwap<SearcherState<D>>,
48 resources: SearcherResources,
50 last_reload_check: RwLock<std::time::Instant>,
52 reload_check_interval: std::time::Duration,
54 reloading: AtomicBool,
56}
57
58impl<D: DirectoryWriter + 'static> IndexReader<D> {
59 pub async fn from_segment_manager(
64 schema: Arc<Schema>,
65 segment_manager: Arc<crate::merge::SegmentManager<D>>,
66 term_cache_blocks: usize,
67 reload_interval_ms: u64,
68 ) -> Result<Self> {
69 const STANDALONE_STORE_CACHE_BYTES: usize = 32 * 1024 * 1024;
70 let resources = SearcherResources::new(
71 term_cache_blocks,
72 STANDALONE_STORE_CACHE_BYTES,
73 crate::default_search_threads(),
74 4,
75 )?;
76 Self::from_segment_manager_with_resources(
77 schema,
78 segment_manager,
79 reload_interval_ms,
80 resources,
81 )
82 .await
83 }
84
85 pub(crate) async fn from_segment_manager_with_resources(
88 schema: Arc<Schema>,
89 segment_manager: Arc<crate::merge::SegmentManager<D>>,
90 reload_interval_ms: u64,
91 resources: SearcherResources,
92 ) -> Result<Self> {
93 let initial_segment_ids = segment_manager.get_segment_ids().await;
95
96 let (reader, publication_id) =
97 Self::create_reader(&schema, &segment_manager, resources.clone()).await?;
98
99 Ok(Self {
100 schema,
101 segment_manager,
102 state: ArcSwap::from_pointee(SearcherState {
103 searcher: Arc::new(reader),
104 segment_ids: initial_segment_ids,
105 publication_id,
106 }),
107 resources,
108 last_reload_check: RwLock::new(std::time::Instant::now()),
109 reload_check_interval: std::time::Duration::from_millis(reload_interval_ms),
110 reloading: AtomicBool::new(false),
111 })
112 }
113
114 async fn create_reader(
118 schema: &Arc<Schema>,
119 segment_manager: &Arc<crate::merge::SegmentManager<D>>,
120 resources: SearcherResources,
121 ) -> Result<(Searcher<D>, u64)> {
122 let snapshot = segment_manager.acquire_snapshot().await;
123 let generation = snapshot.published_generation();
124 let snapshot_schema = generation
125 .as_ref()
126 .map(|generation| Arc::clone(&generation.schema))
127 .unwrap_or_else(|| Arc::clone(schema));
128 let trained = generation
129 .as_ref()
130 .and_then(|generation| generation.trained_vectors.clone())
131 .unwrap_or_else(|| Arc::new(crate::segment::TrainedVectorStructures::default()));
132 let publication_id = generation
133 .as_ref()
134 .map_or(0, |generation| generation.publication_id);
135
136 let searcher = Searcher::from_snapshot(
137 segment_manager.directory(),
138 snapshot_schema,
139 snapshot,
140 trained,
141 resources,
142 )
143 .await?;
144 Ok((searcher, publication_id))
145 }
146
147 pub fn set_reload_interval(&mut self, interval: std::time::Duration) {
149 self.reload_check_interval = interval;
150 }
151
152 pub async fn searcher(&self) -> Result<Arc<Searcher<D>>> {
157 let should_check = {
159 let last = self.last_reload_check.read();
160 last.elapsed() >= self.reload_check_interval
161 };
162
163 if should_check {
164 if self
166 .reloading
167 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
168 .is_ok()
169 {
170 let _reload_guard = ReloadGuard(&self.reloading);
171 self.do_reload_check().await?;
173 }
174 }
176
177 Ok(Arc::clone(&self.state.load().searcher))
179 }
180
181 async fn do_reload_check(&self) -> Result<()> {
183 *self.last_reload_check.write() = std::time::Instant::now();
184
185 let new_segment_ids = self.segment_manager.get_segment_ids().await;
187
188 let publication_id = self.segment_manager.publication_id();
190 let generation_changed = {
191 let state = self.state.load();
192 state.segment_ids != new_segment_ids || state.publication_id != publication_id
193 };
194
195 if generation_changed {
196 let old_count = self.state.load().segment_ids.len();
197 let new_count = new_segment_ids.len();
198 log::info!(
199 "[index_reload] index={} old_count={} new_count={}",
200 self.schema.index_label(),
201 old_count,
202 new_count
203 );
204 self.reload_with_segments(new_segment_ids).await?;
205 }
206 Ok(())
207 }
208
209 pub async fn reload(&self) -> Result<()> {
216 loop {
220 if self
221 .reloading
222 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
223 .is_ok()
224 {
225 break;
226 }
227 tokio::task::yield_now().await;
228 }
229 let _reload_guard = ReloadGuard(&self.reloading);
230 let new_segment_ids = self.segment_manager.get_segment_ids().await;
231
232 let publication_id = self.segment_manager.publication_id();
234 let generation_changed = {
235 let state = self.state.load();
236 state.segment_ids != new_segment_ids || state.publication_id != publication_id
237 };
238
239 if generation_changed {
240 self.reload_with_segments(new_segment_ids).await
241 } else {
242 log::debug!(
243 "[reload] index={} segments unchanged, skipping",
244 self.schema.index_label()
245 );
246 Ok(())
247 }
248 }
249
250 async fn reload_with_segments(&self, new_segment_ids: Vec<String>) -> Result<()> {
255 let existing_segments: Vec<Arc<crate::segment::SegmentReader>> =
257 self.state.load().searcher.segment_readers().to_vec();
258
259 let snapshot = self.segment_manager.acquire_snapshot().await;
260 let generation = snapshot.published_generation();
261 let schema = generation
262 .as_ref()
263 .map(|generation| Arc::clone(&generation.schema))
264 .unwrap_or_else(|| Arc::clone(&self.schema));
265 let trained = generation
266 .as_ref()
267 .and_then(|generation| generation.trained_vectors.clone())
268 .unwrap_or_else(|| Arc::new(crate::segment::TrainedVectorStructures::default()));
269 let publication_id = generation
270 .as_ref()
271 .map_or(0, |generation| generation.publication_id);
272
273 let new_reader = Searcher::from_snapshot_reuse(
274 self.segment_manager.directory(),
275 schema,
276 snapshot,
277 trained,
278 self.resources.clone(),
279 &existing_segments,
280 )
281 .await?;
282
283 self.state.store(Arc::new(SearcherState {
285 searcher: Arc::new(new_reader),
286 segment_ids: new_segment_ids,
287 publication_id,
288 }));
289
290 Ok(())
291 }
292
293 pub fn schema(&self) -> Arc<Schema> {
295 self.schema_arc()
296 }
297
298 pub fn schema_arc(&self) -> Arc<Schema> {
300 self.state.load().searcher.schema_arc()
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307
308 #[test]
309 fn reload_guard_releases_flag_on_unwind() {
310 let reloading = AtomicBool::new(true);
311 let result = std::panic::catch_unwind(|| {
312 let _guard = ReloadGuard(&reloading);
313 panic!("cancel reload");
314 });
315 assert!(result.is_err());
316 assert!(!reloading.load(Ordering::Acquire));
317 }
318
319 #[tokio::test]
320 async fn schema_publication_is_atomic_for_old_and_new_searchers() {
321 use crate::directories::RamDirectory;
322 use crate::dsl::{DenseVectorConfig, SchemaBuilder, VectorIndexAlter, VectorIndexType};
323
324 let mut builder = SchemaBuilder::default();
325 let field = builder.add_dense_vector_field_with_config(
326 "embedding",
327 true,
328 true,
329 DenseVectorConfig::ivf_tq(4, Some(2), 1),
330 );
331 let directory = RamDirectory::new();
332 let index = crate::Index::create(
333 directory.clone(),
334 builder.build(),
335 crate::IndexConfig::default(),
336 )
337 .await
338 .unwrap();
339 let reader = index.reader().await.unwrap();
340 let old_searcher = reader.searcher().await.unwrap();
341
342 let mut target = DenseVectorConfig::ivf_tq(4, Some(2), 1);
343 target.index_type = VectorIndexType::Scann;
344 target.tree_levels = Some(1);
345 target.soar = None;
346 let next_schema = Arc::new(
347 index
348 .schema_arc()
349 .with_vector_index_alter(field, VectorIndexAlter::Dense(target))
350 .unwrap(),
351 );
352 let update = index
353 .segment_manager()
354 .begin_vector_artifact_update()
355 .await
356 .unwrap();
357 index
358 .segment_manager()
359 .publish_vector_schema_only(&update, next_schema)
360 .await
361 .unwrap();
362 drop(update);
363
364 assert_eq!(
365 old_searcher
366 .schema()
367 .get_field_entry(field)
368 .unwrap()
369 .dense_vector_config
370 .as_ref()
371 .unwrap()
372 .index_type,
373 VectorIndexType::IvfTq
374 );
375 reader.reload().await.unwrap();
376 let new_searcher = reader.searcher().await.unwrap();
377 assert_eq!(
378 new_searcher
379 .schema()
380 .get_field_entry(field)
381 .unwrap()
382 .dense_vector_config
383 .as_ref()
384 .unwrap()
385 .index_type,
386 VectorIndexType::Scann
387 );
388
389 let reopened = crate::Index::open(directory, crate::IndexConfig::default())
390 .await
391 .unwrap();
392 assert_eq!(
393 reopened
394 .schema_arc()
395 .get_field_entry(field)
396 .unwrap()
397 .dense_vector_config
398 .as_ref()
399 .unwrap()
400 .index_type,
401 VectorIndexType::Scann
402 );
403 }
404}