第4章:容器与缓存机制
概述
Lance 的容器抽象层为整个系统提供了统一的数据取编接口。而缓存管理旨在优化内存访问性能。本章讨论容器的设计、缓存策略、内存管理。
容器抽象设计
什么是容器(Container)?
Container 是 Lance 中统一的数据加载接口,便于内部采取数据的各种形式(整数、浮点数、字符串等)。
rust
pub trait Container: Send + Sync {
// 转换为任何位置
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
// 子小截断
fn take(&mut self) -> Self;
}
pub enum ArrayContainer {
Int32Array(Vec<i32>),
Float32Array(Vec<f32>),
Float64Array(Vec<f64>),
BinaryArray(Vec<Vec<u8>>),
Utf8Array(Vec<String>),
ListArray(Vec<Vec<u8>>), // 序列化后的数据
}
为什么需要容器抽象?
- 统一接口:Arrow 提供了 Array trait,但 Lance 需要更低优先级的接口
- 性能优化:可以选择最有效的内存读取方式
- 跨域互操作:可以序列化数据元素
容器实现示例
rust
impl Container for Vec<i32> {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl Container for Vec<f32> {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[derive(Serialize, Deserialize)]
pub struct SerializedContainer {
pub type_id: String,
pub data: Vec<u8>,
}
缓存机制
什么是缓存(Cache)?
Lance 使用分层缓存来统一设置缓存时间。
rust
pub trait Cache: Send + Sync {
// 获取缓存项
fn get(&self, key: &str) -> Option<Arc<dyn Any>>;
// 设置缓存项
fn put(&self, key: String, value: Arc<dyn Any>) -> Result<()>;
// 缓存大小
fn size(&self) -> usize;
}
pub struct CacheConfig {
pub max_size: usize,
pub ttl: Duration,
pub eviction_policy: EvictionPolicy,
}
pub enum EvictionPolicy {
LRU, // 最近未使用优先淘汰
LFU, // 最少使用频率优先淘汰
FIFO, // 先入先出
}
LRU 缓存实现
rust
pub struct LruCache {
cache: DashMap<String, (Arc<dyn Any>, Instant)>,
max_size: usize,
ttl: Duration,
}
impl LruCache {
pub fn new(max_size: usize, ttl: Duration) -> Self {
Self {
cache: DashMap::new(),
max_size,
ttl,
}
}
pub fn get(&self, key: &str) -> Option<Arc<dyn Any>> {
// 检查 TTL 是否过期
if let Some(entry) = self.cache.get(key) {
let (value, created_at) = entry.value();
if created_at.elapsed() < self.ttl {
return Some(value.clone());
} else {
drop(entry);
self.cache.remove(key);
}
}
None
}
pub fn put(&self, key: String, value: Arc<dyn Any>) -> Result<()> {
if self.cache.len() >= self.max_size {
// 淘汰缓存空间
self.evict_one();
}
self.cache.insert(key, (value, Instant::now()));
Ok(())
}
fn evict_one(&self) {
// 按时间戳排序,淘汰最旧的
if let Some((key, _)) = self.cache.iter().min_by_key(|entry| {
entry.value().1 // 按时间戳排序
}).map(|entry| (entry.key().clone(), entry.value().clone())) {
self.cache.remove(&key);
}
}
}
真实世界场景
场景 1:向量索引页面缓存
rust
pub struct VectorIndexPageCache {
page_cache: Arc<LruCache>,
}
impl VectorIndexPageCache {
pub async fn read_page(
&self,
page_id: u64,
file_path: &str,
) -> Result<Arc<Vec<u8>>> {
// 先查缓存
let cache_key = format!("page_{}", page_id);
if let Some(cached) = self.page_cache.get(&cache_key) {
return Ok(Arc::new(
cached.downcast_ref::<Vec<u8>>().unwrap().clone()
));
}
// 查缓未中,则从文件加载
let data = read_page_from_file(file_path, page_id).await?;
let data_arc = Arc::new(data);
// 存入缓存
self.page_cache.put(cache_key, data_arc.clone())?;
Ok(data_arc)
}
}
场景 2:Schema 元数据缓存
rust
pub struct SchemaMetadataCache {
cache: Arc<LruCache>,
}
impl SchemaMetadataCache {
pub async fn get_schema(
&self,
dataset_id: &str,
version: u64,
) -> Result<Arc<Schema>> {
let cache_key = format!("schema_{}_{}", dataset_id, version);
if let Some(cached) = self.cache.get(&cache_key) {
return Ok(Arc::new(
cached.downcast_ref::<Schema>().unwrap().clone()
));
}
// 不在缓存中,从 Manifest 加载
let schema = load_schema_from_manifest(dataset_id, version).await?;
let schema_arc = Arc::new(schema);
self.cache.put(cache_key, schema_arc.clone())?;
Ok(schema_arc)
}
}
多级缓存机制
rust
pub enum CacheLevel {
L1, // 本地内存,缓存大小 100MB
L2, // 本地磁盘,缓存大小 1GB
L3, // 远程缓存,缓存大小 10GB
}
pub struct MultiLevelCache {
l1: Arc<LruCache>,
l2: Arc<LruCache>,
l3: Arc<LruCache>,
}
impl MultiLevelCache {
pub fn get(&self, key: &str) -> Option<Arc<dyn Any>> {
// 逐级查询
if let Some(value) = self.l1.get(key) {
return Some(value);
}
if let Some(value) = self.l2.get(key) {
// 上升 L1
let _ = self.l1.put(key.to_string(), value.clone());
return Some(value);
}
if let Some(value) = self.l3.get(key) {
// 上升 L1 和 L2
let _ = self.l1.put(key.to_string(), value.clone());
let _ = self.l2.put(key.to_string(), value.clone());
return Some(value);
}
None
}
}
总结
Lance 的容器与缓存机制提供了:
- 统一接口:Container trait
- 灵活缓存:LRU/LFU/FIFO 策略
- 多级缓存:L1/L2/L3 分层机制
- TTL 支持:自动失效
- 一致性保证:缓存一致性管理
下一章讨论 Lance 的文件格式详解。