# 2023-18: 将 OpenDAL KV 性能提升 1000%

这期周报分享一个 Apache OpenDAL 的低垂果实：通过去除额外的复制开销将 OpenDAL KV 性能提升 1000%！

## 背景

[OpenDAL](https://github.com/apache/incubator-opendal) 在很久之前增加了 [Kv Adapter](https://opendal.apache.org/docs/rust/opendal/raw/adapters/kv/index.html)：通过将 Key-Value 存储后端的常用 GET/SET 操作抽象出来以大幅度简化对接一个 kv service 的成本。维护者需要简单的实现 GET/SET/DELETE 等函数就能够对接形如 Redis/HashMap 这样的存储后端：

```rust
#[async_trait]
pub trait Adapter: Send + Sync + Debug + Unpin + 'static {
    async fn get(&self, path: &str) -> Result<Option<Vec<u8>>>;

    async fn set(&self, path: &str, value: &[u8]) -> Result<()>;

    async fn delete(&self, path: &str) -> Result<()>;
}
```

但是对于纯内存的数据结构来说，Kv Adapter 并不是零开销的：

- 写入的时候需要对数据进行额外的多次复制
- 读取时需要将所有的数据都复制出来

不仅如此，现在的 Kv Adapter 只能存储字节流，无法存储额外的 metadata，使得其使用场景受到了不应该的限制。

## 改进

Issue [kv: Use Bytes as value to avoid copy between read/write](https://github.com/apache/incubator-opendal/issues/1392) 提出可以在 map 中存储 `Bytes` 而不是 `Vec<u8>` 来避免额外的复制，而 [Alternative implementation for memory backend](https://github.com/apache/incubator-opendal/issues/1524) 则更进一步的指出我们可以在 map 中存储一个自定义的数据结构，里面可以附加上的 metadata。

结合以上两个思路，我提出了新的 [Typed Kv Adapter](https://opendal.apache.org/docs/rust/opendal/raw/adapters/typed_kv/index.html)：

```rust
#[derive(Debug, Clone)]
pub struct Value {
    pub metadata: Metadata,
    pub value: Bytes,
}

#[async_trait]
pub trait Adapter: Send + Sync + Debug + Unpin + 'static {
    async fn get(&self, path: &str) -> Result<Option<Value>>;

    async fn set(&self, path: &str, value: Value) -> Result<()>;

    async fn delete(&self, path: &str) -> Result<()>;
}
```

在数据结构中引入自行设计的 `Value` 结构体，存储支持零开销复制的 `Bytes` 而不是 `Vec<u8>`，使得我们的 Adapter 不需要额外的复制就能够读写数据。

## 迁移 moka

将一个 service 从 `kv::Adapter` 迁移至 `typed_kv::Adapter` 非常简单，我们只需要将其定义声明修改即可：

```diff
- inner: SegmentedCache<String, Vec<u8>>,
+ inner: SegmentedCache<String, typed_kv::Value>,
```

然后修改对应 Adapter 的接口，比如：

```diff
- async fn get(&self, path: &str) -> Result<Option<Vec<u8>>>
+ async fn get(&self, path: &str) -> Result<Option<typed_kv::Value>>
```

## 效果

在演示 PR 中，我将 moka 从 `kv::Adapter` 迁移到了 `typed_kv::Adapter`。在所有的基准测试中，读写性能都有了显著提高。其中，最低的 read 提升了 `10%`，最高的提升达到了 `1519.8%`；write 部分受益于零开销实现的 `Bytes` 类型，在 `service_moka_write_once/16.0 MiB` 上取得了 `166323%` 的性能提升。

## 下一步计划

接下来，OpenDAL 将把所有的内存后端迁移到 `typed_kv::Adapter`，并增加更多的 kv 后端支持。然后可以进行一次横向的 bytes 读写性能比较，看看在 OpenDAL 的标准使用场景下，看看哪个内存后端性能更强～
