hermes_core/index/
reader.rs1use 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}
28
29struct ReloadGuard<'a>(&'a AtomicBool);
33
34impl Drop for ReloadGuard<'_> {
35 fn drop(&mut self) {
36 self.0.store(false, Ordering::Release);
37 }
38}
39
40pub struct IndexReader<D: DirectoryWriter + 'static> {
41 schema: Arc<Schema>,
43 segment_manager: Arc<crate::merge::SegmentManager<D>>,
45 state: ArcSwap<SearcherState<D>>,
47 resources: SearcherResources,
49 last_reload_check: RwLock<std::time::Instant>,
51 reload_check_interval: std::time::Duration,
53 reloading: AtomicBool,
55}
56
57impl<D: DirectoryWriter + 'static> IndexReader<D> {
58 pub async fn from_segment_manager(
63 schema: Arc<Schema>,
64 segment_manager: Arc<crate::merge::SegmentManager<D>>,
65 term_cache_blocks: usize,
66 reload_interval_ms: u64,
67 ) -> Result<Self> {
68 Self::from_segment_manager_with_cache_blocks(
69 schema,
70 segment_manager,
71 term_cache_blocks,
72 term_cache_blocks,
73 reload_interval_ms,
74 )
75 .await
76 }
77
78 pub async fn from_segment_manager_with_cache_blocks(
80 schema: Arc<Schema>,
81 segment_manager: Arc<crate::merge::SegmentManager<D>>,
82 term_cache_blocks: usize,
83 store_cache_blocks: usize,
84 reload_interval_ms: u64,
85 ) -> Result<Self> {
86 let resources = SearcherResources::new(
87 term_cache_blocks,
88 store_cache_blocks,
89 crate::default_search_threads(),
90 )?;
91 Self::from_segment_manager_with_resources(
92 schema,
93 segment_manager,
94 reload_interval_ms,
95 resources,
96 )
97 .await
98 }
99
100 pub(crate) async fn from_segment_manager_with_resources(
103 schema: Arc<Schema>,
104 segment_manager: Arc<crate::merge::SegmentManager<D>>,
105 reload_interval_ms: u64,
106 resources: SearcherResources,
107 ) -> Result<Self> {
108 let initial_segment_ids = segment_manager.get_segment_ids().await;
110
111 let reader = Self::create_reader(&schema, &segment_manager, resources.clone()).await?;
112
113 Ok(Self {
114 schema,
115 segment_manager,
116 state: ArcSwap::from_pointee(SearcherState {
117 searcher: Arc::new(reader),
118 segment_ids: initial_segment_ids,
119 }),
120 resources,
121 last_reload_check: RwLock::new(std::time::Instant::now()),
122 reload_check_interval: std::time::Duration::from_millis(reload_interval_ms),
123 reloading: AtomicBool::new(false),
124 })
125 }
126
127 async fn create_reader(
131 schema: &Arc<Schema>,
132 segment_manager: &Arc<crate::merge::SegmentManager<D>>,
133 resources: SearcherResources,
134 ) -> Result<Searcher<D>> {
135 let trained = segment_manager.trained();
137 let trained_centroids = trained
138 .as_ref()
139 .map(|t| t.centroids.clone())
140 .unwrap_or_default();
141
142 let snapshot = segment_manager.acquire_snapshot().await;
144
145 Searcher::from_snapshot(
146 segment_manager.directory(),
147 Arc::clone(schema),
148 snapshot,
149 trained_centroids,
150 resources,
151 )
152 .await
153 }
154
155 pub fn set_reload_interval(&mut self, interval: std::time::Duration) {
157 self.reload_check_interval = interval;
158 }
159
160 pub async fn searcher(&self) -> Result<Arc<Searcher<D>>> {
165 let should_check = {
167 let last = self.last_reload_check.read();
168 last.elapsed() >= self.reload_check_interval
169 };
170
171 if should_check {
172 if self
174 .reloading
175 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
176 .is_ok()
177 {
178 let _reload_guard = ReloadGuard(&self.reloading);
179 self.do_reload_check().await?;
181 }
182 }
184
185 Ok(Arc::clone(&self.state.load().searcher))
187 }
188
189 async fn do_reload_check(&self) -> Result<()> {
191 *self.last_reload_check.write() = std::time::Instant::now();
192
193 let new_segment_ids = self.segment_manager.get_segment_ids().await;
195
196 let segments_changed = {
198 let state = self.state.load();
199 state.segment_ids != new_segment_ids
200 };
201
202 if segments_changed {
203 let old_count = self.state.load().segment_ids.len();
204 let new_count = new_segment_ids.len();
205 log::info!(
206 "[index_reload] old_count={} new_count={}",
207 old_count,
208 new_count
209 );
210 self.reload_with_segments(new_segment_ids).await?;
211 }
212 Ok(())
213 }
214
215 pub async fn reload(&self) -> Result<()> {
222 loop {
226 if self
227 .reloading
228 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
229 .is_ok()
230 {
231 break;
232 }
233 tokio::task::yield_now().await;
234 }
235 let _reload_guard = ReloadGuard(&self.reloading);
236 let new_segment_ids = self.segment_manager.get_segment_ids().await;
237
238 let segments_changed = {
240 let state = self.state.load();
241 state.segment_ids != new_segment_ids
242 };
243
244 if segments_changed {
245 self.reload_with_segments(new_segment_ids).await
246 } else {
247 log::debug!("[reload] segments unchanged, skipping");
248 Ok(())
249 }
250 }
251
252 async fn reload_with_segments(&self, new_segment_ids: Vec<String>) -> Result<()> {
257 let existing_segments: Vec<Arc<crate::segment::SegmentReader>> =
259 self.state.load().searcher.segment_readers().to_vec();
260
261 let trained = self.segment_manager.trained();
263 let trained_centroids = trained
264 .as_ref()
265 .map(|t| t.centroids.clone())
266 .unwrap_or_default();
267
268 let snapshot = self.segment_manager.acquire_snapshot().await;
269
270 let new_reader = Searcher::from_snapshot_reuse(
271 self.segment_manager.directory(),
272 Arc::clone(&self.schema),
273 snapshot,
274 trained_centroids,
275 self.resources.clone(),
276 &existing_segments,
277 )
278 .await?;
279
280 self.state.store(Arc::new(SearcherState {
282 searcher: Arc::new(new_reader),
283 segment_ids: new_segment_ids,
284 }));
285
286 Ok(())
287 }
288
289 pub fn schema(&self) -> &Schema {
291 &self.schema
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn reload_guard_releases_flag_on_unwind() {
301 let reloading = AtomicBool::new(true);
302 let result = std::panic::catch_unwind(|| {
303 let _guard = ReloadGuard(&reloading);
304 panic!("cancel reload");
305 });
306 assert!(result.is_err());
307 assert!(!reloading.load(Ordering::Acquire));
308 }
309}