Skip to main content

prolly/prolly/proximity/search/
runtime.rs

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    /// Bind proximity decoder context so authoritative PRXN objects can be
112    /// validated before shared cache admission, including through AsyncStore.
113    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    /// Adapt a synchronous search I/O binding for native async traversal while
138    /// retaining the same cache namespace, partition, counters, and decoder
139    /// context. Shared reads remain backend-owned through `SyncStoreAsAsync`.
140    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}