1use std::collections::HashMap;
5use std::sync::Arc;
6
7use lance_core::cache::{CacheBackend, LanceCache, QuickCacheBackend};
8use lance_core::deepsize::DeepSizeOf;
9use lance_core::{Error, Result};
10use lance_index::IndexType;
11use lance_io::object_store::ObjectStoreRegistry;
12use lance_io::spill::{LocalSpillStore, SpillStore};
13
14use crate::dataset::{DEFAULT_INDEX_CACHE_SIZE, DEFAULT_METADATA_CACHE_SIZE};
15use crate::session::caches::GlobalMetadataCache;
16use crate::session::index_caches::GlobalIndexCache;
17
18use self::index_extension::IndexExtension;
19
20pub(crate) mod caches;
21pub mod index_caches;
22pub(crate) mod index_extension;
23
24#[derive(Clone)]
38pub struct Session {
39 pub(crate) index_cache: GlobalIndexCache,
46
47 pub(crate) metadata_cache: caches::GlobalMetadataCache,
53
54 pub(crate) index_extensions: HashMap<(IndexType, String), Arc<dyn IndexExtension>>,
55
56 store_registry: Arc<ObjectStoreRegistry>,
57
58 spill_store: Arc<dyn SpillStore>,
59}
60
61impl DeepSizeOf for Session {
62 fn deep_size_of_children(&self, context: &mut lance_core::deepsize::Context) -> usize {
63 let mut size = 0;
64 size += self.index_cache.deep_size_of_children(context);
66 size += self.metadata_cache.deep_size_of_children(context);
67 for ext in self.index_extensions.values() {
68 size += ext.deep_size_of_children(context);
69 }
70 size
71 }
72}
73
74impl std::fmt::Debug for Session {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 f.debug_struct("Session")
77 .field(
78 "index_cache",
79 &format!("IndexCache(items={})", self.index_cache.0.approx_size(),),
80 )
81 .field(
82 "file_metadata_cache",
83 &format!("LanceCache(items={})", self.metadata_cache.0.approx_size(),),
84 )
85 .field(
86 "index_extensions",
87 &self.index_extensions.keys().collect::<Vec<_>>(),
88 )
89 .finish()
90 }
91}
92
93impl Session {
94 pub fn new(
106 index_cache_size: usize,
107 metadata_cache_size: usize,
108 store_registry: Arc<ObjectStoreRegistry>,
109 ) -> Self {
110 Self {
111 index_cache: GlobalIndexCache(LanceCache::with_backend(Arc::new(
112 QuickCacheBackend::with_capacity(index_cache_size),
113 ))),
114 metadata_cache: GlobalMetadataCache(LanceCache::with_backend(Arc::new(
115 QuickCacheBackend::with_capacity(metadata_cache_size),
116 ))),
117 index_extensions: HashMap::new(),
118 store_registry,
119 spill_store: Arc::new(LocalSpillStore::default()),
120 }
121 }
122
123 pub fn with_index_cache_backend(
128 index_cache_backend: Arc<dyn CacheBackend>,
129 metadata_cache_size: usize,
130 store_registry: Arc<ObjectStoreRegistry>,
131 ) -> Self {
132 Self {
133 index_cache: GlobalIndexCache(LanceCache::with_backend(index_cache_backend)),
134 metadata_cache: GlobalMetadataCache(LanceCache::with_backend(Arc::new(
135 QuickCacheBackend::with_capacity(metadata_cache_size),
136 ))),
137 index_extensions: HashMap::new(),
138 store_registry,
139 spill_store: Arc::new(LocalSpillStore::default()),
140 }
141 }
142
143 pub fn with_spill_store(mut self, store: Arc<dyn SpillStore>) -> Self {
156 self.spill_store = store;
157 self
158 }
159
160 pub fn spill_store(&self) -> &dyn SpillStore {
165 &*self.spill_store
166 }
167
168 pub fn register_index_extension(
177 &mut self,
178 name: String,
179 extension: Arc<dyn IndexExtension>,
180 ) -> Result<()> {
181 match extension.index_type() {
182 IndexType::Vector => {
183 if self
184 .index_extensions
185 .contains_key(&(IndexType::Vector, name.clone()))
186 {
187 return Err(Error::invalid_input(format!(
188 "{name} is already registered"
189 )));
190 }
191
192 if let Some(ext) = extension.to_vector() {
193 self.index_extensions
194 .insert((IndexType::Vector, name), ext.to_generic());
195 } else {
196 return Err(Error::invalid_input(format!(
197 "{name} is not a vector index extension"
198 )));
199 }
200 }
201 _ => {
202 return Err(Error::invalid_input(format!(
203 "scalar index extension is not support yet: {}",
204 extension.index_type()
205 )));
206 }
207 }
208
209 Ok(())
210 }
211
212 pub fn size_bytes(&self) -> u64 {
216 self.deep_size_of() as u64
219 }
220
221 pub fn approx_num_items(&self) -> usize {
226 self.index_cache.0.approx_size()
227 + self.metadata_cache.0.approx_size()
228 + self.index_extensions.len()
229 }
230
231 pub fn store_registry(&self) -> Arc<ObjectStoreRegistry> {
233 self.store_registry.clone()
234 }
235
236 pub fn file_metadata_cache(&self) -> &LanceCache {
238 &self.metadata_cache.0
239 }
240
241 pub async fn metadata_cache_stats(&self) -> lance_core::cache::CacheStats {
243 self.metadata_cache.0.stats().await
244 }
245
246 pub async fn index_cache_stats(&self) -> lance_core::cache::CacheStats {
248 self.index_cache.0.stats().await
249 }
250}
251
252impl Default for Session {
253 fn default() -> Self {
254 Self::new(
255 DEFAULT_INDEX_CACHE_SIZE,
256 DEFAULT_METADATA_CACHE_SIZE,
257 Arc::new(ObjectStoreRegistry::default()),
258 )
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265 use lance_core::cache::UnsizedCacheKey;
266 use lance_index::vector::VectorIndex;
267 use std::borrow::Cow;
268 use tokio::io::AsyncWriteExt;
269
270 struct TestUnsizedKey(&'static str);
271 impl UnsizedCacheKey for TestUnsizedKey {
272 type ValueType = dyn VectorIndex;
273 fn key(&self) -> Cow<'_, str> {
274 Cow::Borrowed(self.0)
275 }
276
277 fn type_name() -> &'static str {
278 "TestUnsized"
279 }
280 }
281
282 #[tokio::test]
283 async fn test_disable_index_cache() {
284 let no_cache = Session::new(0, 0, Default::default());
285 assert!(
286 no_cache
287 .index_cache
288 .get_unsized_with_key(&TestUnsizedKey("abc"))
289 .await
290 .is_none()
291 );
292 }
293
294 #[tokio::test]
295 async fn test_default_session_has_spill_store() {
296 let session = Session::default();
297 let (mut writer, _spill) = session.spill_store().new_spill().await.unwrap();
299 writer.write_all(b"scratch").await.unwrap();
300 lance_io::traits::Writer::shutdown(writer.as_mut())
301 .await
302 .unwrap();
303 }
304
305 #[tokio::test]
306 async fn test_custom_spill_store_injected() {
307 let capped = Arc::new(LocalSpillStore::with_cap(50).unwrap());
308 let session = Session::default().with_spill_store(capped);
309
310 let (mut writer, _spill) = session.spill_store().new_spill().await.unwrap();
311 let io_err = writer.write_all(&[0u8; 51]).await.unwrap_err();
314 let err: lance_core::Error = io_err.into();
315 assert!(
316 matches!(
317 err,
318 lance_core::Error::DiskCapExceeded { cap_bytes: 50, .. }
319 ),
320 "expected DiskCapExceeded, got {err}"
321 );
322 }
323}