1use crate::prolly::cid::Cid;
2use crate::prolly::content_graph::ContentObjectKind;
3use crate::prolly::error::Error;
4use crate::prolly::node::Node;
5use crate::prolly::proximity::accelerator::hnsw::storage::Manifest as HnswManifest;
6use crate::prolly::proximity::accelerator::pq::Manifest as PqManifest;
7use crate::prolly::proximity::accelerator::{
8 catalog::Manifest as CatalogManifest, composite::Manifest as CompositeManifest,
9};
10use crate::prolly::proximity::storage::quantized::ScalarQuantized;
11use crate::prolly::proximity::storage::vector::ExternalVector;
12use crate::prolly::proximity::storage::{Descriptor, ProximityNode};
13use crate::prolly::store::{AsyncStore, SyncStoreAsAsync};
14use crate::prolly::store::{BatchOp, NodePublication, Store};
15use std::collections::{HashMap, HashSet, VecDeque};
16use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
17use std::sync::{Arc, Condvar, Mutex};
18use std::task::{Poll, Waker};
19
20static NEXT_STORE_NAMESPACE: AtomicU64 = AtomicU64::new(1);
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23pub struct StoreCacheNamespace(u64);
24
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct SearchRuntimePolicy {
27 pub max_entries: usize,
28 pub max_bytes: usize,
29 pub authoritative_max_bytes: usize,
30 pub hnsw_max_bytes: usize,
31 pub pq_max_bytes: usize,
32}
33
34impl Default for SearchRuntimePolicy {
35 fn default() -> Self {
36 Self {
37 max_entries: 16_384,
38 max_bytes: 256 * 1024 * 1024,
39 authoritative_max_bytes: 128 * 1024 * 1024,
40 hnsw_max_bytes: 96 * 1024 * 1024,
41 pq_max_bytes: 32 * 1024 * 1024,
42 }
43 }
44}
45
46impl SearchRuntimePolicy {
47 pub fn validate(&self) -> Result<(), Error> {
48 if self.max_entries == 0 || self.max_bytes == 0 {
49 return Err(Error::InvalidProximityConfig {
50 reason: "search runtime entry and byte limits must be positive".to_owned(),
51 });
52 }
53 let partitions = self
54 .authoritative_max_bytes
55 .saturating_add(self.hnsw_max_bytes)
56 .saturating_add(self.pq_max_bytes);
57 if partitions < self.max_bytes {
58 return Err(Error::InvalidProximityConfig {
59 reason: "search runtime partition byte limits must cover the total limit"
60 .to_owned(),
61 });
62 }
63 Ok(())
64 }
65}
66
67#[derive(Clone)]
68pub struct SearchIo<S> {
69 store: S,
70 namespace: StoreCacheNamespace,
71 runtime: Arc<SearchRuntime>,
72 kind: ContentObjectKind,
73 physical_bytes_read: Arc<AtomicUsize>,
74 physical_reads: Arc<AtomicUsize>,
75 dimensions: Option<u32>,
76}
77
78impl<S> SearchIo<S> {
79 pub fn new(store: S, runtime: Arc<SearchRuntime>) -> Self {
80 Self {
81 store,
82 namespace: StoreCacheNamespace(NEXT_STORE_NAMESPACE.fetch_add(1, Ordering::Relaxed)),
83 runtime,
84 kind: ContentObjectKind::OrderedNode,
85 physical_bytes_read: Arc::new(AtomicUsize::new(0)),
86 physical_reads: Arc::new(AtomicUsize::new(0)),
87 dimensions: None,
88 }
89 }
90
91 pub fn namespace(&self) -> StoreCacheNamespace {
92 self.namespace
93 }
94
95 pub fn runtime(&self) -> &Arc<SearchRuntime> {
96 &self.runtime
97 }
98
99 pub fn store(&self) -> &S {
100 &self.store
101 }
102
103 pub fn physical_bytes_read(&self) -> usize {
104 self.physical_bytes_read.load(Ordering::Relaxed)
105 }
106
107 pub fn physical_reads(&self) -> usize {
108 self.physical_reads.load(Ordering::Relaxed)
109 }
110
111 pub fn with_proximity_dimensions(mut self, dimensions: u32) -> Self {
114 self.kind = ContentObjectKind::ProximityNode;
115 self.dimensions = Some(dimensions);
116 self
117 }
118
119 pub(crate) fn for_kind(&self, kind: ContentObjectKind) -> Self
120 where
121 S: Clone,
122 {
123 let mut binding = self.clone();
124 binding.kind = kind;
125 binding
126 }
127
128 pub(crate) fn for_kind_with_dimensions(&self, kind: ContentObjectKind, dimensions: u32) -> Self
129 where
130 S: Clone,
131 {
132 let mut binding = self.for_kind(kind);
133 binding.dimensions = Some(dimensions);
134 binding
135 }
136
137 pub fn sync_store_as_async(&self) -> SearchIo<SyncStoreAsAsync<S>>
141 where
142 S: Clone,
143 {
144 SearchIo {
145 store: SyncStoreAsAsync::new(self.store.clone()),
146 namespace: self.namespace,
147 runtime: Arc::clone(&self.runtime),
148 kind: self.kind,
149 physical_bytes_read: Arc::clone(&self.physical_bytes_read),
150 physical_reads: Arc::clone(&self.physical_reads),
151 dimensions: self.dimensions,
152 }
153 }
154}
155
156impl<S: Store + Clone> Store for SearchIo<S> {
157 type Error = S::Error;
158
159 fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
160 let Ok(cid) = <[u8; 32]>::try_from(key).map(Cid) else {
161 return self.store.get(key);
162 };
163 match self.runtime.load(self, self.kind, &cid, 2, |bytes| {
164 validate_cached_object(bytes, self.dimensions)
165 }) {
166 Ok(loaded) => Ok(Some(loaded.bytes.as_ref().to_vec())),
167 Err(Error::NotFound(_)) => Ok(None),
168 Err(Error::Store(error)) => match error.downcast::<S::Error>() {
169 Ok(error) => Err(*error),
170 Err(_) => self.store.get(key),
171 },
172 Err(_) => self.store.get(key),
173 }
174 }
175
176 fn get_shared(&self, key: &[u8]) -> Result<Option<Arc<[u8]>>, Self::Error> {
177 let Ok(cid) = <[u8; 32]>::try_from(key).map(Cid) else {
178 return self.store.get_shared(key);
179 };
180 match self.runtime.load(self, self.kind, &cid, 2, |bytes| {
181 validate_cached_object(bytes, self.dimensions)
182 }) {
183 Ok(loaded) => Ok(Some(loaded.bytes)),
184 Err(Error::NotFound(_)) => Ok(None),
185 Err(Error::Store(error)) => match error.downcast::<S::Error>() {
186 Ok(error) => Err(*error),
187 Err(_) => self.store.get_shared(key),
188 },
189 Err(_) => self.store.get_shared(key),
190 }
191 }
192
193 fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> {
194 self.store.put(key, value)
195 }
196
197 fn delete(&self, key: &[u8]) -> Result<(), Self::Error> {
198 self.store.delete(key)
199 }
200
201 fn batch(&self, ops: &[BatchOp]) -> Result<(), Self::Error> {
202 self.store.batch(ops)
203 }
204
205 fn batch_get_ordered(&self, keys: &[&[u8]]) -> Result<Vec<Option<Vec<u8>>>, Self::Error> {
206 let cids = keys
207 .iter()
208 .map(|key| <[u8; 32]>::try_from(*key).map(Cid))
209 .collect::<Result<Vec<_>, _>>();
210 let Ok(cids) = cids else {
211 return self.store.batch_get_ordered(keys);
212 };
213 match self.runtime.load_batch(self, &cids, self.kind, 2) {
214 Ok(values) => Ok(values
215 .into_iter()
216 .map(|value| value.map(|bytes| bytes.as_ref().to_vec()))
217 .collect()),
218 Err(Error::Store(error)) => match error.downcast::<S::Error>() {
219 Ok(error) => Err(*error),
220 Err(_) => self.store.batch_get_ordered(keys),
221 },
222 Err(_) => self.store.batch_get_ordered(keys),
223 }
224 }
225
226 fn batch_get_ordered_unique(
227 &self,
228 keys: &[&[u8]],
229 ) -> Result<Vec<Option<Vec<u8>>>, Self::Error> {
230 self.batch_get_ordered(keys)
231 }
232
233 fn batch_get_shared_ordered_unique(
234 &self,
235 keys: &[&[u8]],
236 ) -> Result<Vec<Option<Arc<[u8]>>>, Self::Error> {
237 let cids = keys
238 .iter()
239 .map(|key| <[u8; 32]>::try_from(*key).map(Cid))
240 .collect::<Result<Vec<_>, _>>();
241 let Ok(cids) = cids else {
242 return self.store.batch_get_shared_ordered_unique(keys);
243 };
244 match self.runtime.load_batch(self, &cids, self.kind, 2) {
245 Ok(values) => Ok(values),
246 Err(Error::Store(error)) => match error.downcast::<S::Error>() {
247 Ok(error) => Err(*error),
248 Err(_) => self.store.batch_get_shared_ordered_unique(keys),
249 },
250 Err(_) => self.store.batch_get_shared_ordered_unique(keys),
251 }
252 }
253
254 fn has_native_shared_reads(&self) -> bool {
255 true
256 }
257
258 fn prefers_batch_reads(&self) -> bool {
259 self.store.prefers_batch_reads()
260 }
261
262 fn supports_hints(&self) -> bool {
263 self.store.supports_hints()
264 }
265
266 fn prefers_rightmost_path_hints(&self) -> bool {
267 self.store.prefers_rightmost_path_hints()
268 }
269
270 fn get_hint(&self, namespace: &[u8], key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
271 self.store.get_hint(namespace, key)
272 }
273
274 fn put_hint(&self, namespace: &[u8], key: &[u8], value: &[u8]) -> Result<(), Self::Error> {
275 self.store.put_hint(namespace, key, value)
276 }
277
278 fn publish_nodes(&self, publication: NodePublication<'_>) -> Result<(), Self::Error> {
279 self.store.publish_nodes(publication)
280 }
281}
282impl<S: AsyncStore + Clone> AsyncStore for SearchIo<S>
283where
284 S::Error: Send + Sync,
285{
286 type Error = S::Error;
287
288 async fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
289 let Ok(cid) = <[u8; 32]>::try_from(key).map(Cid) else {
290 return self.store.get(key).await;
291 };
292 match self
293 .runtime
294 .load_async(self, self.kind, &cid, 2, |bytes| {
295 validate_cached_object(bytes, self.dimensions)
296 })
297 .await
298 {
299 Ok(loaded) => Ok(Some(loaded.bytes.as_ref().to_vec())),
300 Err(Error::NotFound(_)) => Ok(None),
301 Err(Error::Store(error)) => match error.downcast::<S::Error>() {
302 Ok(error) => Err(*error),
303 Err(_) => self.store.get(key).await,
304 },
305 Err(_) => self.store.get(key).await,
306 }
307 }
308
309 async fn get_shared(&self, key: &[u8]) -> Result<Option<Arc<[u8]>>, Self::Error> {
310 let Ok(cid) = <[u8; 32]>::try_from(key).map(Cid) else {
311 return self.store.get_shared(key).await;
312 };
313 match self
314 .runtime
315 .load_async(self, self.kind, &cid, 2, |bytes| {
316 validate_cached_object(bytes, self.dimensions)
317 })
318 .await
319 {
320 Ok(loaded) => Ok(Some(loaded.bytes)),
321 Err(Error::NotFound(_)) => Ok(None),
322 Err(Error::Store(error)) => match error.downcast::<S::Error>() {
323 Ok(error) => Err(*error),
324 Err(_) => self.store.get_shared(key).await,
325 },
326 Err(_) => self.store.get_shared(key).await,
327 }
328 }
329
330 async fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> {
331 self.store.put(key, value).await
332 }
333
334 async fn delete(&self, key: &[u8]) -> Result<(), Self::Error> {
335 self.store.delete(key).await
336 }
337
338 async fn batch(&self, ops: &[BatchOp<'_>]) -> Result<(), Self::Error> {
339 self.store.batch(ops).await
340 }
341
342 async fn batch_get_shared_ordered_unique(
343 &self,
344 keys: &[&[u8]],
345 ) -> Result<Vec<Option<Arc<[u8]>>>, Self::Error> {
346 let mut values = Vec::with_capacity(keys.len());
347 for key in keys {
348 values.push(self.get_shared(key).await?);
349 }
350 Ok(values)
351 }
352
353 fn has_native_shared_reads(&self) -> bool {
354 true
355 }
356
357 fn prefers_batch_reads(&self) -> bool {
358 self.store.prefers_batch_reads()
359 }
360
361 fn read_parallelism(&self) -> usize {
362 self.store.read_parallelism()
363 }
364
365 async fn publish_nodes(&self, publication: NodePublication<'_>) -> Result<(), Self::Error> {
366 self.store.publish_nodes(publication).await
367 }
368}
369
370#[derive(Clone, Copy, Debug, PartialEq, Eq)]
371enum Partition {
372 Authoritative,
373 Hnsw,
374 Pq,
375}
376
377#[derive(Clone, Debug, PartialEq, Eq, Hash)]
378struct CacheKey {
379 namespace: StoreCacheNamespace,
380 kind: ContentObjectKind,
381 cid: Cid,
382 decoder_version: u8,
383}
384
385struct CacheEntry {
386 bytes: Arc<[u8]>,
387 partition: Partition,
388 generation: u64,
389}
390
391#[derive(Default)]
392struct RuntimeState {
393 entries: HashMap<CacheKey, CacheEntry>,
394 access_log: VecDeque<(CacheKey, u64)>,
395 in_flight: HashSet<CacheKey>,
396 generation: u64,
397 bytes: usize,
398 authoritative_bytes: usize,
399 hnsw_bytes: usize,
400 pq_bytes: usize,
401 async_waiters: HashMap<CacheKey, Vec<Waker>>,
402}
403
404pub struct SearchRuntime {
405 policy: SearchRuntimePolicy,
406 state: Mutex<RuntimeState>,
407 wake: Condvar,
408}
409
410impl Default for SearchRuntime {
411 fn default() -> Self {
412 Self::new(SearchRuntimePolicy::default()).expect("default search runtime policy is valid")
413 }
414}
415
416impl SearchRuntime {
417 pub fn new(policy: SearchRuntimePolicy) -> Result<Self, Error> {
418 policy.validate()?;
419 Ok(Self {
420 policy,
421 state: Mutex::new(RuntimeState::default()),
422 wake: Condvar::new(),
423 })
424 }
425
426 pub fn clear(&self) {
427 let mut state = self
428 .state
429 .lock()
430 .unwrap_or_else(|poison| poison.into_inner());
431 state.entries.clear();
432 state.access_log.clear();
433 state.bytes = 0;
434 state.authoritative_bytes = 0;
435 state.hnsw_bytes = 0;
436 state.pq_bytes = 0;
437 }
438
439 pub(crate) fn load<S: Store, F>(
440 &self,
441 io: &SearchIo<S>,
442 kind: ContentObjectKind,
443 cid: &Cid,
444 decoder_version: u8,
445 validate: F,
446 ) -> Result<RuntimeLoad, Error>
447 where
448 F: FnOnce(&[u8]) -> Result<(), Error>,
449 {
450 let key = CacheKey {
451 namespace: io.namespace,
452 kind,
453 cid: cid.clone(),
454 decoder_version,
455 };
456 loop {
457 let mut state = self
458 .state
459 .lock()
460 .unwrap_or_else(|poison| poison.into_inner());
461 let generation = state.generation.wrapping_add(1);
462 state.generation = generation;
463 if let Some(entry) = state.entries.get_mut(&key) {
464 entry.generation = generation;
465 let bytes = entry.bytes.clone();
466 state.access_log.push_back((key.clone(), generation));
467 compact_access_log(&mut state);
468 return Ok(RuntimeLoad { bytes });
469 }
470 if state.in_flight.insert(key.clone()) {
471 break;
472 }
473 state = self
474 .wake
475 .wait(state)
476 .unwrap_or_else(|poison| poison.into_inner());
477 drop(state);
478 }
479
480 let loaded = io
481 .store
482 .get(cid.as_bytes())
483 .map_err(|error| Error::Store(Box::new(error)))
484 .and_then(|bytes| bytes.ok_or_else(|| Error::NotFound(cid.clone())))
485 .and_then(|bytes| {
486 let actual = Cid::from_bytes(&bytes);
487 if actual == *cid {
488 Ok(bytes)
489 } else {
490 Err(Error::CidMismatch {
491 expected: cid.clone(),
492 actual,
493 })
494 }
495 })
496 .and_then(|bytes| {
497 validate(&bytes)?;
498 Ok(bytes)
499 });
500 if let Ok(bytes) = &loaded {
501 io.physical_reads.fetch_add(1, Ordering::Relaxed);
502 io.physical_bytes_read
503 .fetch_add(bytes.len(), Ordering::Relaxed);
504 }
505 let mut state = self
506 .state
507 .lock()
508 .unwrap_or_else(|poison| poison.into_inner());
509 state.in_flight.remove(&key);
510 let result = loaded.map(|bytes| {
511 let bytes = Arc::<[u8]>::from(bytes.into_boxed_slice());
512 self.insert(&mut state, key.clone(), bytes.clone());
513 RuntimeLoad { bytes }
514 });
515 self.wake.notify_all();
516 if let Some(waiters) = state.async_waiters.remove(&key) {
517 for waiter in waiters {
518 waiter.wake();
519 }
520 }
521 result
522 }
523
524 fn load_batch<S: Store>(
525 &self,
526 io: &SearchIo<S>,
527 cids: &[Cid],
528 kind: ContentObjectKind,
529 decoder_version: u8,
530 ) -> Result<Vec<Option<Arc<[u8]>>>, Error> {
531 let keys = cids
532 .iter()
533 .cloned()
534 .map(|cid| CacheKey {
535 namespace: io.namespace,
536 kind,
537 cid,
538 decoder_version,
539 })
540 .collect::<Vec<_>>();
541 let mut results = vec![None; keys.len()];
542 let mut leaders = Vec::<usize>::new();
543 let mut waiters = Vec::<usize>::new();
544 {
545 let mut state = self
546 .state
547 .lock()
548 .unwrap_or_else(|poison| poison.into_inner());
549 for (index, key) in keys.iter().enumerate() {
550 let generation = state.generation.wrapping_add(1);
551 state.generation = generation;
552 if let Some(entry) = state.entries.get_mut(key) {
553 entry.generation = generation;
554 results[index] = Some(entry.bytes.clone());
555 state.access_log.push_back((key.clone(), generation));
556 compact_access_log(&mut state);
557 } else if state.in_flight.insert(key.clone()) {
558 leaders.push(index);
559 } else {
560 waiters.push(index);
561 }
562 }
563 }
564 if !leaders.is_empty() {
565 let store_keys = leaders
566 .iter()
567 .map(|index| cids[*index].as_bytes() as &[u8])
568 .collect::<Vec<_>>();
569 let loaded = match io.store.batch_get_ordered_unique(&store_keys) {
570 Ok(loaded) => loaded,
571 Err(error) => {
572 let mut state = self
573 .state
574 .lock()
575 .unwrap_or_else(|poison| poison.into_inner());
576 for index in &leaders {
577 state.in_flight.remove(&keys[*index]);
578 }
579 self.wake.notify_all();
580 return Err(Error::Store(Box::new(error)));
581 }
582 };
583 io.physical_reads.fetch_add(1, Ordering::Relaxed);
584 let mut state = self
585 .state
586 .lock()
587 .unwrap_or_else(|poison| poison.into_inner());
588 let mut async_wakers = Vec::new();
589 for index in &leaders {
590 state.in_flight.remove(&keys[*index]);
591 if let Some(waiters) = state.async_waiters.remove(&keys[*index]) {
592 async_wakers.extend(waiters);
593 }
594 }
595 self.wake.notify_all();
596 for waiter in async_wakers {
597 waiter.wake();
598 }
599 for (index, bytes) in leaders.iter().copied().zip(loaded) {
600 let key = &keys[index];
601 if let Some(bytes) = bytes {
602 let actual = Cid::from_bytes(&bytes);
603 if actual != key.cid {
604 self.wake.notify_all();
605 return Err(Error::CidMismatch {
606 expected: key.cid.clone(),
607 actual,
608 });
609 }
610 validate_cached_object(&bytes, io.dimensions)?;
611 io.physical_bytes_read
612 .fetch_add(bytes.len(), Ordering::Relaxed);
613 let bytes = Arc::<[u8]>::from(bytes.into_boxed_slice());
614 self.insert(&mut state, key.clone(), bytes.clone());
615 results[index] = Some(bytes);
616 }
617 }
618 }
619 for index in waiters {
620 results[index] = match self.load(io, kind, &cids[index], decoder_version, |bytes| {
621 validate_cached_object(bytes, io.dimensions)
622 }) {
623 Ok(loaded) => Some(loaded.bytes),
624 Err(Error::NotFound(_)) => None,
625 Err(error) => return Err(error),
626 };
627 }
628 Ok(results)
629 }
630 pub(crate) async fn load_async<S: AsyncStore, F>(
631 &self,
632 io: &SearchIo<S>,
633 kind: ContentObjectKind,
634 cid: &Cid,
635 decoder_version: u8,
636 validate: F,
637 ) -> Result<RuntimeLoad, Error>
638 where
639 S::Error: Send + Sync,
640 F: FnOnce(&[u8]) -> Result<(), Error>,
641 {
642 let key = CacheKey {
643 namespace: io.namespace,
644 kind,
645 cid: cid.clone(),
646 decoder_version,
647 };
648 let cached = futures_util::future::poll_fn(|context| {
649 let mut state = self
650 .state
651 .lock()
652 .unwrap_or_else(|poison| poison.into_inner());
653 let generation = state.generation.wrapping_add(1);
654 state.generation = generation;
655 if let Some(entry) = state.entries.get_mut(&key) {
656 entry.generation = generation;
657 let bytes = entry.bytes.clone();
658 state.access_log.push_back((key.clone(), generation));
659 compact_access_log(&mut state);
660 return Poll::Ready(Some(RuntimeLoad { bytes }));
661 }
662 if state.in_flight.insert(key.clone()) {
663 return Poll::Ready(None);
664 }
665 let waiters = state.async_waiters.entry(key.clone()).or_default();
666 if !waiters
667 .iter()
668 .any(|waiter| waiter.will_wake(context.waker()))
669 {
670 waiters.push(context.waker().clone());
671 }
672 Poll::Pending
673 })
674 .await;
675 if let Some(cached) = cached {
676 return Ok(cached);
677 }
678 let mut in_flight_guard = AsyncInFlightGuard {
679 runtime: self,
680 key: key.clone(),
681 armed: true,
682 };
683
684 let loaded = io
685 .store
686 .get(cid.as_bytes())
687 .await
688 .map_err(|error| Error::Store(Box::new(error)))
689 .and_then(|bytes| bytes.ok_or_else(|| Error::NotFound(cid.clone())))
690 .and_then(|bytes| {
691 let actual = Cid::from_bytes(&bytes);
692 if actual != *cid {
693 return Err(Error::CidMismatch {
694 expected: cid.clone(),
695 actual,
696 });
697 }
698 validate(&bytes)?;
699 Ok(bytes)
700 });
701 if let Ok(bytes) = &loaded {
702 io.physical_reads.fetch_add(1, Ordering::Relaxed);
703 io.physical_bytes_read
704 .fetch_add(bytes.len(), Ordering::Relaxed);
705 }
706 let mut state = self
707 .state
708 .lock()
709 .unwrap_or_else(|poison| poison.into_inner());
710 state.in_flight.remove(&key);
711 in_flight_guard.armed = false;
712 let result = loaded.map(|bytes| {
713 let bytes = Arc::<[u8]>::from(bytes.into_boxed_slice());
714 self.insert(&mut state, key.clone(), bytes.clone());
715 RuntimeLoad { bytes }
716 });
717 let waiters = state.async_waiters.remove(&key).unwrap_or_default();
718 self.wake.notify_all();
719 drop(state);
720 for waiter in waiters {
721 waiter.wake();
722 }
723 result
724 }
725
726 fn insert(&self, state: &mut RuntimeState, key: CacheKey, bytes: Arc<[u8]>) {
727 let partition = partition(key.kind);
728 let partition_limit = self.partition_limit(partition);
729 if bytes.len() > self.policy.max_bytes || bytes.len() > partition_limit {
730 return;
731 }
732 state.generation = state.generation.wrapping_add(1);
733 let generation = state.generation;
734 state.bytes = state.bytes.saturating_add(bytes.len());
735 *partition_bytes_mut(state, partition) =
736 partition_bytes(state, partition).saturating_add(bytes.len());
737 state.entries.insert(
738 key.clone(),
739 CacheEntry {
740 bytes,
741 partition,
742 generation,
743 },
744 );
745 state.access_log.push_back((key, generation));
746 while state.entries.len() > self.policy.max_entries
747 || state.bytes > self.policy.max_bytes
748 || partition_bytes(state, partition) > partition_limit
749 {
750 let Some((candidate, candidate_generation)) = state.access_log.pop_front() else {
751 break;
752 };
753 if state
754 .entries
755 .get(&candidate)
756 .is_some_and(|entry| entry.generation == candidate_generation)
757 {
758 let removed = state
759 .entries
760 .remove(&candidate)
761 .expect("checked cache entry");
762 state.bytes = state.bytes.saturating_sub(removed.bytes.len());
763 *partition_bytes_mut(state, removed.partition) =
764 partition_bytes(state, removed.partition).saturating_sub(removed.bytes.len());
765 }
766 }
767 compact_access_log(state);
768 }
769
770 fn partition_limit(&self, partition: Partition) -> usize {
771 match partition {
772 Partition::Authoritative => self.policy.authoritative_max_bytes,
773 Partition::Hnsw => self.policy.hnsw_max_bytes,
774 Partition::Pq => self.policy.pq_max_bytes,
775 }
776 }
777}
778struct AsyncInFlightGuard<'a> {
779 runtime: &'a SearchRuntime,
780 key: CacheKey,
781 armed: bool,
782}
783impl Drop for AsyncInFlightGuard<'_> {
784 fn drop(&mut self) {
785 if !self.armed {
786 return;
787 }
788 let mut state = self
789 .runtime
790 .state
791 .lock()
792 .unwrap_or_else(|poison| poison.into_inner());
793 state.in_flight.remove(&self.key);
794 let waiters = state.async_waiters.remove(&self.key).unwrap_or_default();
795 self.runtime.wake.notify_all();
796 drop(state);
797 for waiter in waiters {
798 waiter.wake();
799 }
800 }
801}
802
803pub(crate) struct RuntimeLoad {
804 pub bytes: Arc<[u8]>,
805}
806
807fn partition(kind: ContentObjectKind) -> Partition {
808 match kind {
809 ContentObjectKind::HnswManifest
810 | ContentObjectKind::HnswPage
811 | ContentObjectKind::CompositeAccelerator => Partition::Hnsw,
812 ContentObjectKind::ProductQuantization => Partition::Pq,
813 _ => Partition::Authoritative,
814 }
815}
816
817fn partition_bytes(state: &RuntimeState, partition: Partition) -> usize {
818 match partition {
819 Partition::Authoritative => state.authoritative_bytes,
820 Partition::Hnsw => state.hnsw_bytes,
821 Partition::Pq => state.pq_bytes,
822 }
823}
824
825fn partition_bytes_mut(state: &mut RuntimeState, partition: Partition) -> &mut usize {
826 match partition {
827 Partition::Authoritative => &mut state.authoritative_bytes,
828 Partition::Hnsw => &mut state.hnsw_bytes,
829 Partition::Pq => &mut state.pq_bytes,
830 }
831}
832
833fn compact_access_log(state: &mut RuntimeState) {
834 let maximum = state.entries.len().saturating_mul(8).saturating_add(1_024);
835 if state.access_log.len() <= maximum {
836 return;
837 }
838 let mut current = state
839 .entries
840 .iter()
841 .map(|(key, entry)| (key.clone(), entry.generation))
842 .collect::<Vec<_>>();
843 current.sort_by_key(|(_, generation)| *generation);
844 state.access_log = current.into();
845}
846
847fn validate_cached_object(bytes: &[u8], dimensions: Option<u32>) -> Result<(), Error> {
848 let magic = bytes
849 .get(..4)
850 .ok_or_else(|| Error::InvalidProximityObject {
851 kind: "runtime cache",
852 reason: "content object has no codec magic".to_owned(),
853 })?;
854 match magic {
855 b"CRAB" => Node::from_bytes(bytes).map(|_| ()),
856 b"PRXI" => Descriptor::decode(bytes).map(|_| ()),
857 b"PRXN" => ProximityNode::decode(
858 bytes,
859 dimensions.ok_or_else(|| Error::InvalidProximityObject {
860 kind: "runtime cache",
861 reason: "PRXN cache admission requires vector dimensions".to_owned(),
862 })?,
863 )
864 .map(|_| ()),
865 b"PRXV" => ExternalVector::decode(bytes).map(|_| ()),
866 b"PQS8" => ScalarQuantized::decode(bytes).map(|_| ()),
867 b"HNSW" => HnswManifest::decode(bytes).map(|_| ()),
868 b"PQPQ" => PqManifest::decode(bytes).map(|_| ()),
869 b"PCOM" => CompositeManifest::decode(bytes).map(|_| ()),
870 b"PACL" => CatalogManifest::decode(bytes).map(|_| ()),
871 _ => Err(Error::InvalidProximityObject {
872 kind: "runtime cache",
873 reason: "content codec is not cache-admissible".to_owned(),
874 }),
875 }
876}
877
878#[cfg(test)]
879mod tests {
880 use super::*;
881
882 fn key(label: &[u8], kind: ContentObjectKind) -> CacheKey {
883 CacheKey {
884 namespace: StoreCacheNamespace(7),
885 kind,
886 cid: Cid::from_bytes(label),
887 decoder_version: 2,
888 }
889 }
890
891 #[test]
892 fn weighted_cache_evicts_lru_bypasses_oversized_and_keeps_pinned_bytes_alive() {
893 let runtime = SearchRuntime::new(SearchRuntimePolicy {
894 max_entries: 2,
895 max_bytes: 16,
896 authoritative_max_bytes: 16,
897 hnsw_max_bytes: 16,
898 pq_max_bytes: 16,
899 })
900 .unwrap();
901 let first_key = key(b"first", ContentObjectKind::OrderedNode);
902 let second_key = key(b"second", ContentObjectKind::OrderedNode);
903 let third_key = key(b"third", ContentObjectKind::OrderedNode);
904 let oversized_key = key(b"oversized", ContentObjectKind::OrderedNode);
905 let first = Arc::<[u8]>::from(vec![1; 8]);
906 let pinned = first.clone();
907 let mut state = runtime.state.lock().unwrap();
908 runtime.insert(&mut state, first_key.clone(), first);
909 runtime.insert(&mut state, second_key.clone(), Arc::from(vec![2; 8]));
910 runtime.insert(&mut state, third_key.clone(), Arc::from(vec![3; 8]));
911 assert!(!state.entries.contains_key(&first_key));
912 assert!(state.entries.contains_key(&second_key));
913 assert!(state.entries.contains_key(&third_key));
914 assert_eq!(state.bytes, 16);
915 runtime.insert(&mut state, oversized_key.clone(), Arc::from(vec![4; 17]));
916 assert!(!state.entries.contains_key(&oversized_key));
917 assert_eq!(state.bytes, 16);
918 drop(state);
919 assert_eq!(pinned.as_ref(), &[1; 8]);
920 }
921}