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