Apache OpenDAL 在最近的版本中增加了 Adapter
的概念,将 OpenDAL 支持的服务范围从传统的文件系统和对象存储扩展到了诸如 Redis,Moka 等 Key-Value 存储服务上。今天这篇周报就介绍一下我们为什么要增加这个抽象,它解决了哪些问题,以及社区现在的进展。
背景
OpenDAL 旨在自由,无痛,高效的访问数据,其中自由意味着 OpenDAL 可以以相同的方式访问不同的存储服务。为了实现这一点,OpenDAL 实现了不少服务的支持,比如 azblob,fs,ftp,gcp,hdfs,ipfs,s3 等等。这些服务虽然接口和实现各有各的差异,但是他们共性是都有着类 POSIX 文件系统的抽象,OpenDAL 不需要额外的抽象层就能访问其中的数据。换言之,OpenDAL 对存储底层的操作对用户来说是透明的,离开 OpenDAL 用户也可以正常访问这些数据。基于这样的设计, OpenDAL 没有考虑过支持 Key-Value 服务:因为我们无法从 Key-Value 服务中读取到任何有意义的数据,其存储的数据结构高度依赖于应用的具体实现。
但是在后续社区的多次讨论中,我们逐渐发现在 Key-Value 服务的基础上定义一个自己的存储结构,并用来实现 OpenDAL 的接口同样是有价值的。Databend 提出了这样的需求:他希望 OpenDAL 能够实现一层缓存,可以将数据存储在不同的缓存服务上,比如内存,本地文件系统和 Redis 上,他们能够设置一定的淘汰策略,自动过期。换言之,Databend 希望 OpenDAL 能够支持易失性数据的存储,他们会被用作缓存和临时的数据存储,用户不会通过其他方式(比如 awscli)来访问它。在这种场景下, Key-Value 服务是一个黑箱,用户只通过 OpenDAL 来访问。既然不需要暴露给外部访问,OpenDAL 完全可以自行决定在 Key-Value 服务上的存储结构。
设计
过去 OpenDAL 的实现路径是非常简单的:
impl Accessor for s3::Backend { .. }
每个服务提供一个 Backend
结构体并实现 Accessor
接口即可。
但是 Key-Value 服务的接口都是非常相似的,为了避免重复实现相似逻辑,OpenDAL 引入了 Adapter:
impl<S> Accessor for kv::Backend<S> where S: kv::Adapter { .. }
impl kv::Adapter for redis::Adapter { .. }
OpenDAL 为所有的 kv::Backend<S: kv::Adapter>
实现了 Accessor,每个具体的 Key-Value 服务只需要实现 kv::Adapter
即可:
#[async_trait]
pub trait Adapter: Send + Sync + Debug + Clone + 'static {
/// Return the medata of this key value accessor.
fn metadata(&self) -> Metadata;
/// Fetch the next id.
///
/// - Returning id should never be zero.
/// - Returning id should never be reused.
async fn next_id(&self) -> Result<u64>;
/// Get a key from service.
///
/// - return `Ok(None)` if this key is not exist.
async fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>>;
/// Set a key into service.
async fn set(&self, key: &[u8], value: &[u8]) -> Result<()>;
/// Scan a range of keys.
///
/// If `scan` is not supported, we will disable the block split
/// logic. Only one block will be store for one file.
async fn scan(&self, prefix: &[u8]) -> Result<KeyStreamer> {
let _ = prefix;
Err(io::Error::new(
io::ErrorKind::Unsupported,
anyhow::anyhow!("scan operation is not supported"),
))
}
/// Delete a key from service.
///
/// - return `Ok(())` even if this key is not exist.
async fn delete(&self, key: &[u8]) -> Result<()>;
}
这里的 get
,set
和 delete
都是绝大多数 Key-Value 服务支持的接口,此外 kv::Adapter
还会根据是否支持 scan
来决定内部的具体实现。具体的存储格式目前还没有完全稳定下来,这里就不展开介绍了,感兴趣的同学可以直接查看代码~
进展
在 kv::Adapter
的基础上,OpenDAL 实现了以下 Key-Value 服务的支持:
接下来 Databend 社区会尝试使用这些 Service 作为缓存层来加速热点查询,并根据生产中的反馈来改进 OpenDAL 的实现~
社区还计划增加以下 Key-Value 服务的支持:
欢迎感兴趣的同学尝试~