Iteration 8 的主要工作仍然是围绕着 OpenDAL 展开,有三件事情比较有意思。
首先是多读了部分数据导致读取性能下降的 BUG: Improvement: Avoid reading unnecessary data。
Databend 在 benchmark 的过程中发现 opendal 会读取比预想的要更多的数据:
let r = o.reader();
let buf = vec[0;4*1024*1024];
r.read_exact(buf).await;
理论上应该只从 S3 获取 4MB 的数据,但实际上会下载将近 4.5MB 的数据,其中多余的部分数据会在这个请求销毁的时候被一同丢弃掉。Databend 平均每次请求的大小大约为 256KB,opendal 多读取的数据经常会超出一倍左右。
背后的原因是 opendal 并不知道用户会如何使用这个 reader ,所以在实现的时候总是使用当前 reader 的 size 来发送请求,这就使得 reader 会从服务器端获取比用户预期更多的数据。
let op = OpRead {
path: self.path.to_string(),
offset: Some(self.current_offset()),
size: self.current_size(),
};
为了解决这个问题,OpenDAL 引入了 limited_reader
proposal,通过更明确的语义避免用户错误地多读数据。
其次是新增加的 ObserveReader 抽象,databend 需要统计每次 read 读取的 size 和花费的时间。size 比较好做,简单的 callback 就能做好,但是花费的时间就稍微麻烦一点,需要能够允许用户在每次读取的前后都能正确的统计时间,还需要能够排除 Poll::Pending
上的开销。
ObserveReader
通过 ReadEvent
来实现这个功能:
/// ReadEvent will emitted by `ObserveReader`.
#[derive(Copy, Clone, Debug)]
pub enum ReadEvent {
/// `poll_read` has been called.
Started,
/// `poll_read` returns `Pending`, we are back to the runtime.
Pending,
/// `poll_read` returns `Ready(Ok(n))`, we have read `n` bytes of data.
Read(usize),
/// `poll_read` returns `Ready(Err(e))`, we will have an `ErrorKind` here.
Error(std::io::ErrorKind),
}
impl<R, F> futures::AsyncRead for ObserveReader<R, F>
where
R: AsyncRead + Send + Unpin,
F: FnMut(ReadEvent),
{
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<std::io::Result<usize>> {
(self.f)(ReadEvent::Started);
match Pin::new(&mut self.r).poll_read(cx, buf) {
Poll::Ready(Ok(n)) => {
(self.f)(ReadEvent::Read(n));
Poll::Ready(Ok(n))
}
Poll::Ready(Err(e)) => {
(self.f)(ReadEvent::Error(e.kind()));
Poll::Ready(Err(e))
}
Poll::Pending => {
(self.f)(ReadEvent::Pending);
Poll::Pending
}
}
}
}
每当 ObserveReader
出现状态切换的时候,就会回调一下用户传入的 callback 函数。在 PR dal_context: Use ObserveReader to calculate metrics 中,我使用 ObserveReader
为 databend 增加了时间统计的支持。
最后是 S3 匿名访问的问题。由于 aws-sdk 不支持匿名访问的功能,所以被迫自己搞了一些 Hack,通过修改 AWS SDK 的 Middleware,实现了在没有读取到密钥时直接发送未签名的请求。在这个功能的加持下,databend 能够直接从一个公开的 S3 Bucket 中直接加载数据,非常适合用来做 demo。
这个周期推动 OpenDAL 进行了一轮迭代,上线了 main 分支的文档网站 https://opendal.databend.rs/opendal/,方便用户查看还没有正式 release 的 API。花了不少时间补全了所有公开 API 的文档和样例,现在访问 docs.rs 终于不是光秃秃的一片了。
现在 OpenDAL 对外的接口基本上稳定,接下来计划增加服务器端加密的支持,然后把能力扩展到更多的服务,只支持 S3 怎么能叫做 OpenDAL 呢!此外还有个重点话题是可观察性,通过为 OpenDAL 增加完善的 logging,tracing,metrics 支持,用户能够知道 OpenDAL 的内部状态,从而做出更好的决策。
除了 Databend 社区之外,这个周期还给 tikv 旗下的 minitrace 和 minstant 水了一些 PR。
minitrace 是一个超快的 tracing 库,从 benchmark 的结果看能比 tokio-tracing 快十倍,在收集的 span 特别多的时候差距能拉大到 100 倍以上。我帮助 minitrace 修复了 contributing guide 中的 dead link,增加了简单的开发入门指导,此外还在 PR deps: Reduce version requirements 中统一了依赖的版本规则,将 v0.x.y
统一成了 v0.x
,放松了一些对版本的要求。
minstant 是 minitrace 的依赖,是 std::time::Instant
的高性能替代,在支持的平台上会使用 CPU 中的 TSC,比 std 中的实现快一倍。我在 PR ci: Say goodbye to travis 中删掉了已经不再工作的 travis CI 的配置(时代的眼泪)。
接下来聊聊外卷的话题。
今年的 1/7 我上线了 Xuanwo's Note,然后从 1/18 开始我以差不多每个工作日一篇的速度分享今天学到的东西,内容囊括了方方面面:从 Rust 相关到 Linux 的小技巧。
最开始的想法是通过这种方式逼迫自己每天去学习,去分享一些新的东西,最大化的利用已经开源出来的 Xuanwo's Note。但是我很快发现我收获的价值要比分享出去的更多:各位推友从各自不同的角度向我回馈了完全超出我知识边界的内容。推友们有些指出了分享内容的不足,有些在内容的基础上进行了进一步的完善。
典型的例子是 2022-04: Iteration 5 汇报 中提到的
最让我开心的是某天关于 Futures 的分享还启发了参考资料中的作者 @JmPotat0,帮助他将自己的文章补充的更完整,形成了一个正向循环。
我越来越相信这种外卷的方式跟内卷是有本质区别的:内卷产生的是无谓的内耗,而外卷却可以创造更多的价值。每个人有自己各自不同的知识来源和工作背景,对同一个技术话题也有自己完全不同的角度。如果大家能够互相学习,共同进步,相信会是一件非常有意思的事情。从我的角度看,这是开源精神在抽象意义上的延伸:自己的知识开源出去,让所有人都可以获取,可以参与改进,反过来改进自己的知识结构与体系,形成一个正向循环。
今天你外卷了吗?